diff --git a/.env b/.env index f07c5f4..691923c 100644 --- a/.env +++ b/.env @@ -28,3 +28,7 @@ APP_SECRET= # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" ###< doctrine/doctrine-bundle ### + +###> symfony/mailer ### +MAILER_DSN=null://null +###< symfony/mailer ### diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cdd1bf..2032b2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,8 @@ jobs: *.cache-to=type=gha,scope=${{github.ref}},mode=max - name: Start services run: docker compose up --wait --no-build + - name: Coding Style + run: docker compose exec -T php vendor/bin/php-cs-fixer check --diff --show-progress=none - name: Check HTTP reachability run: curl -v --fail-with-body http://localhost - name: Check HTTPS reachability diff --git a/.gitignore b/.gitignore index bd689a6..803f32b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/frankenphp/data + ### Generated by gibo (https://github.com/simonwhitaker/gibo) ### https://raw.github.com/github/gitignore/6eeebe6f49678aacd8311ce079842c971b3ebe96/Symfony.gitignore @@ -32,7 +34,6 @@ /bin/* !bin/console !bin/symfony_requirements -/vendor/ # Assets and user uploads /web/bundles/ @@ -40,7 +41,6 @@ # PHPUnit /app/phpunit.xml -/phpunit.xml # Build data /build/ diff --git a/.idea/TijdVoorDeTest.iml b/.idea/TijdVoorDeTest.iml index e692dff..361b0bf 100644 --- a/.idea/TijdVoorDeTest.iml +++ b/.idea/TijdVoorDeTest.iml @@ -131,7 +131,14 @@ + + + + + + + diff --git a/.idea/php-test-framework.xml b/.idea/php-test-framework.xml index 47e8f38..59f6b0c 100644 --- a/.idea/php-test-framework.xml +++ b/.idea/php-test-framework.xml @@ -5,7 +5,7 @@ - + diff --git a/.idea/php.xml b/.idea/php.xml index 94b2aab..0e6eef6 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -162,6 +162,13 @@ + + + + + + + @@ -252,8 +259,7 @@ - - + diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index bf379e0..603199b 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -3,6 +3,7 @@ declare(strict_types=1); use PhpCsFixer\Config; use PhpCsFixer\Finder; +use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; $finder = (new Finder()) ->in(__DIR__) @@ -10,6 +11,7 @@ $finder = (new Finder()) ; return (new Config()) + ->setParallelConfig(ParallelConfigFactory::detect()) ->setRules([ '@Symfony' => true, '@Symfony:risky' => true, diff --git a/Justfile b/Justfile index b32596f..0c8c506 100644 --- a/Justfile +++ b/Justfile @@ -25,3 +25,10 @@ translations: fix-cs: docker compose exec php vendor/bin/php-cs-fixer fix + docker compose exec php vendor/bin/twig-cs-fixer fix + +rector *args: + docker compose exec php vendor/bin/rector {{ args }} + +phpstan *args: + docker compose exec php vendor/bin/phpstan analyse {{ args }} diff --git a/compose.override.yaml b/compose.override.yaml index ea5d8ca..8a7596f 100644 --- a/compose.override.yaml +++ b/compose.override.yaml @@ -8,13 +8,12 @@ services: - ./:/app - ./frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro - ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro - # If you develop on Mac or Windows you can remove the vendor/ directory - # from the bind-mount for better performance by enabling the next line: - #- /app/vendor + - ./frankenphp/data:/data environment: MERCURE_EXTRA_DIRECTIVES: demo # See https://xdebug.org/docs/all_settings#mode XDEBUG_MODE: "${XDEBUG_MODE:-off}" + MAILER_DSN: "smtp://mailer:1025" extra_hosts: # Ensure that host.docker.internal is correctly defined on Linux - host.docker.internal:host-gateway @@ -27,4 +26,15 @@ services: database: ports: - "5432:5432" -###< doctrine/doctrine-bundle ### + ###< doctrine/doctrine-bundle ### + + ###> symfony/mailer ### + mailer: + image: axllent/mailpit + ports: + - "1025" + - "8025" + environment: + MP_SMTP_AUTH_ACCEPT_ANY: 1 + MP_SMTP_AUTH_ALLOW_INSECURE: 1 + ###< symfony/mailer ### diff --git a/compose.prod.yaml b/compose.prod.yaml index f0db05d..60358cc 100644 --- a/compose.prod.yaml +++ b/compose.prod.yaml @@ -8,3 +8,17 @@ services: APP_SECRET: ${APP_SECRET} MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} + labels: + - "traefik.enable=true" + - "traefik.http.routers.php.rule=Host(`tijdvoordetest.nl`)" + - "traefik.http.routers.php.entrypoints=websecure" + - "traefik.http.routers.php.tls.certresolver=marijndoeve" + - "traefik.http.services.php.loadbalancer.server.port=80" + networks: + - web + - internal +networks: + web: + external: true + internal: + external: false diff --git a/compose.yaml b/compose.yaml index f53e21f..56252d7 100644 --- a/compose.yaml +++ b/compose.yaml @@ -31,12 +31,12 @@ services: - target: 443 published: ${HTTP3_PORT:-443} protocol: udp - -# Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service -###> symfony/mercure-bundle ### -###< symfony/mercure-bundle ### - -###> doctrine/doctrine-bundle ### + + # Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service + ###> symfony/mercure-bundle ### + ###< symfony/mercure-bundle ### + + ###> doctrine/doctrine-bundle ### database: image: postgres:${POSTGRES_VERSION:-16}-alpine environment: @@ -45,22 +45,20 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} POSTGRES_USER: ${POSTGRES_USER:-app} healthcheck: - test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"] + test: [ "CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}" ] timeout: 5s retries: 5 start_period: 60s volumes: - database_data:/var/lib/postgresql/data:rw - # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! - # - ./docker/db/data:/var/lib/postgresql/data:rw -###< doctrine/doctrine-bundle ### + ###< doctrine/doctrine-bundle ### volumes: caddy_data: caddy_config: -###> symfony/mercure-bundle ### -###< symfony/mercure-bundle ### - -###> doctrine/doctrine-bundle ### + ###> symfony/mercure-bundle ### + ###< symfony/mercure-bundle ### + + ###> doctrine/doctrine-bundle ### database_data: -###< doctrine/doctrine-bundle ### + ###< doctrine/doctrine-bundle ### diff --git a/composer.json b/composer.json index 0e3876b..5b36996 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "doctrine/doctrine-bundle": "^2.14.0", "doctrine/doctrine-migrations-bundle": "^3.4.1", "doctrine/orm": "^3.3.2", - "easycorp/easyadmin-bundle": "^4.24.5", + "easycorp/easyadmin-bundle": "^4.24.6", "runtime/frankenphp-symfony": "^0.2.0", "symfony/asset": "7.2.*", "symfony/console": "7.2.*", @@ -21,24 +21,27 @@ "symfony/flex": "^2.5.0", "symfony/form": "7.2.*", "symfony/framework-bundle": "7.2.*", + "symfony/mailer": "7.2.*", "symfony/runtime": "7.2.*", "symfony/security-bundle": "7.2.*", "symfony/twig-bundle": "7.2.*", "symfony/uid": "7.2.*", "symfony/yaml": "7.2.*", - "thecodingmachine/safe": "^3.0.2" + "symfonycasts/verify-email-bundle": "^1.17", + "thecodingmachine/safe": "^3.1.0" }, "require-dev": { "doctrine/doctrine-fixtures-bundle": "^4.1", "friendsofphp/php-cs-fixer": "^3.75.0", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.11", + "phpstan/phpstan": "^2.1.12", "phpstan/phpstan-doctrine": "^2.0.2", "phpstan/phpstan-phpunit": "^2.0.6", "phpstan/phpstan-symfony": "^2.0.4", - "phpunit/phpunit": "^12.0.10", + "phpunit/phpunit": "^12.1.2", "rector/rector": "^2.0.11", "roave/security-advisories": "dev-latest", + "symfony/browser-kit": "7.2.*", "symfony/maker-bundle": "^1.62.1", "symfony/stopwatch": "7.2.*", "symfony/web-profiler-bundle": "7.2.*", @@ -73,8 +76,7 @@ "symfony/polyfill-php74": "*", "symfony/polyfill-php80": "*", "symfony/polyfill-php81": "*", - "symfony/polyfill-php82": "*", - "symfony/polyfill-php83": "*" + "symfony/polyfill-php82": "*" }, "scripts": { "auto-scripts": { diff --git a/composer.lock b/composer.lock index 1f36e03..953a496 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "45e62099d0459cf57cfcf61adbf716b7", + "content-hash": "1b0a1ad4f5e0358790434c7bd7828d65", "packages": [ { "name": "doctrine/collections", @@ -200,26 +200,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -239,9 +242,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/doctrine-bundle", @@ -1125,16 +1128,16 @@ }, { "name": "easycorp/easyadmin-bundle", - "version": "v4.24.5", + "version": "v4.24.6", "source": { "type": "git", "url": "https://github.com/EasyCorp/EasyAdminBundle.git", - "reference": "dcc64d06fc142f894667439e72837aa3742b5460" + "reference": "c5e4496c4c96d67385c48e285c463e0f5a8947e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/dcc64d06fc142f894667439e72837aa3742b5460", - "reference": "dcc64d06fc142f894667439e72837aa3742b5460", + "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/c5e4496c4c96d67385c48e285c463e0f5a8947e7", + "reference": "c5e4496c4c96d67385c48e285c463e0f5a8947e7", "shasum": "" }, "require": { @@ -1215,7 +1218,7 @@ ], "support": { "issues": "https://github.com/EasyCorp/EasyAdminBundle/issues", - "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.24.5" + "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.24.6" }, "funding": [ { @@ -1223,7 +1226,74 @@ "type": "github" } ], - "time": "2025-03-10T19:31:19+00:00" + "time": "2025-04-12T13:53:15+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" }, { "name": "psr/cache", @@ -3296,6 +3366,86 @@ ], "time": "2024-11-25T14:26:33+00:00" }, + { + "name": "symfony/mailer", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/f3871b182c44997cf039f3b462af4a48fb85f9d3", + "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^7.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-27T11:08:17+00:00" + }, { "name": "symfony/mime", "version": "v7.2.4", @@ -3925,6 +4075,82 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php83", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-php84", "version": "v1.31.0", @@ -5502,16 +5728,16 @@ }, { "name": "symfony/ux-twig-component", - "version": "v2.23.0", + "version": "v2.24.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-twig-component.git", - "reference": "f29033b95e93aea2d498dc40eac185ed14b07800" + "reference": "48a46e4c6215d41cc97ba8dff0cff21ea9b255a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/f29033b95e93aea2d498dc40eac185ed14b07800", - "reference": "f29033b95e93aea2d498dc40eac185ed14b07800", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/48a46e4c6215d41cc97ba8dff0cff21ea9b255a8", + "reference": "48a46e4c6215d41cc97ba8dff0cff21ea9b255a8", "shasum": "" }, "require": { @@ -5565,7 +5791,7 @@ "twig" ], "support": { - "source": "https://github.com/symfony/ux-twig-component/tree/v2.23.0" + "source": "https://github.com/symfony/ux-twig-component/tree/v2.24.0" }, "funding": [ { @@ -5581,7 +5807,7 @@ "type": "tidelift" } ], - "time": "2025-01-25T02:19:26+00:00" + "time": "2025-03-21T20:14:36+00:00" }, { "name": "symfony/validator", @@ -5912,17 +6138,63 @@ "time": "2025-03-03T07:12:39+00:00" }, { - "name": "thecodingmachine/safe", - "version": "v3.0.2", + "name": "symfonycasts/verify-email-bundle", + "version": "v1.17.3", "source": { "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "22ffad3248982a784f9870a37aeb2e522bd19645" + "url": "https://github.com/SymfonyCasts/verify-email-bundle.git", + "reference": "2cb1cd94ca7a65471563a5cb91ddf40e8433844e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/22ffad3248982a784f9870a37aeb2e522bd19645", - "reference": "22ffad3248982a784f9870a37aeb2e522bd19645", + "url": "https://api.github.com/repos/SymfonyCasts/verify-email-bundle/zipball/2cb1cd94ca7a65471563a5cb91ddf40e8433844e", + "reference": "2cb1cd94ca7a65471563a5cb91ddf40e8433844e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=8.1", + "symfony/config": "^5.4 | ^6.0 | ^7.0", + "symfony/dependency-injection": "^5.4 | ^6.0 | ^7.0", + "symfony/deprecation-contracts": "^2.2 | ^3.0", + "symfony/http-kernel": "^5.4 | ^6.0 | ^7.0", + "symfony/routing": "^5.4 | ^6.0 | ^7.0" + }, + "require-dev": { + "doctrine/orm": "^2.7", + "doctrine/persistence": "^2.0", + "symfony/framework-bundle": "^5.4 | ^6.0 | ^7.0", + "symfony/phpunit-bridge": "^5.4 | ^6.0 | ^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "SymfonyCasts\\Bundle\\VerifyEmail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Simple, stylish Email Verification for Symfony", + "support": { + "issues": "https://github.com/SymfonyCasts/verify-email-bundle/issues", + "source": "https://github.com/SymfonyCasts/verify-email-bundle/tree/v1.17.3" + }, + "time": "2024-12-09T18:44:25+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "e14ac96126e6c19ea9d1f4029abb51487f4cf2cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/e14ac96126e6c19ea9d1f4029abb51487f4cf2cf", + "reference": "e14ac96126e6c19ea9d1f4029abb51487f4cf2cf", "shasum": "" }, "require": { @@ -6032,7 +6304,7 @@ "description": "PHP core functions that throw exceptions instead of returning FALSE on error", "support": { "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v3.0.2" + "source": "https://github.com/thecodingmachine/safe/tree/v3.1.0" }, "funding": [ { @@ -6048,7 +6320,7 @@ "type": "github" } ], - "time": "2025-02-19T19:23:00+00:00" + "time": "2025-04-12T06:41:26+00:00" }, { "name": "twig/extra-bundle", @@ -6944,6 +7216,73 @@ ], "time": "2025-03-31T18:40:42+00:00" }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.0", @@ -7230,16 +7569,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.11", + "version": "2.1.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" + "reference": "96dde49e967c0c22812bcfa7bda4ff82c09f3b0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/96dde49e967c0c22812bcfa7bda4ff82c09f3b0c", + "reference": "96dde49e967c0c22812bcfa7bda4ff82c09f3b0c", "shasum": "" }, "require": { @@ -7284,7 +7623,7 @@ "type": "github" } ], - "time": "2025-03-24T13:45:00+00:00" + "time": "2025-04-16T13:19:18+00:00" }, { "name": "phpstan/phpstan-doctrine", @@ -7483,16 +7822,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "12.1.0", + "version": "12.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d331a5ced3d9a2b917baa9841b2211e72f9e780d" + "reference": "05c33d01a856f9f62488d144bafddc3d7b7a4ebb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d331a5ced3d9a2b917baa9841b2211e72f9e780d", - "reference": "d331a5ced3d9a2b917baa9841b2211e72f9e780d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/05c33d01a856f9f62488d144bafddc3d7b7a4ebb", + "reference": "05c33d01a856f9f62488d144bafddc3d7b7a4ebb", "shasum": "" }, "require": { @@ -7548,7 +7887,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.1.0" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.1.2" }, "funding": [ { @@ -7556,7 +7895,7 @@ "type": "github" } ], - "time": "2025-03-17T13:56:07+00:00" + "time": "2025-04-03T14:34:39+00:00" }, { "name": "phpunit/php-file-iterator", @@ -7805,16 +8144,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.0.10", + "version": "12.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6075843014de23bcd6992842d69ca99d25d6a433" + "reference": "6f2775cc4b7b19ba5a411c188e855eb0cc78a711" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6075843014de23bcd6992842d69ca99d25d6a433", - "reference": "6075843014de23bcd6992842d69ca99d25d6a433", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6f2775cc4b7b19ba5a411c188e855eb0cc78a711", + "reference": "6f2775cc4b7b19ba5a411c188e855eb0cc78a711", "shasum": "" }, "require": { @@ -7828,7 +8167,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.1.0", + "phpunit/php-code-coverage": "^12.1.2", "phpunit/php-file-iterator": "^6.0.0", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", @@ -7850,7 +8189,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "12.0-dev" + "dev-main": "12.1-dev" } }, "autoload": { @@ -7882,7 +8221,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.0.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.1.2" }, "funding": [ { @@ -7898,7 +8237,7 @@ "type": "tidelift" } ], - "time": "2025-03-23T16:03:59+00:00" + "time": "2025-04-08T08:05:27+00:00" }, { "name": "react/cache", @@ -8491,16 +8830,17 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "0b51a6c830ba6dd6c63715ceded239a62bf2274f" + "reference": "6c54d20ae795b83ecf3f826311d7f488cd1ef005" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0b51a6c830ba6dd6c63715ceded239a62bf2274f", - "reference": "0b51a6c830ba6dd6c63715ceded239a62bf2274f", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/6c54d20ae795b83ecf3f826311d7f488cd1ef005", + "reference": "6c54d20ae795b83ecf3f826311d7f488cd1ef005", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", + "adaptcms/adaptcms": "<=1.3", "admidio/admidio": "<4.3.12", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<2.2", @@ -8525,7 +8865,8 @@ "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", "apache-solr-for-typo3/solr": "<2.8.3", "apereo/phpcas": "<1.6", - "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3|>=3.3.8,<3.3.15", + "api-platform/core": "<3.4.17|>=4.0.0.0-alpha1,<4.0.22", + "api-platform/graphql": "<3.4.17|>=4.0.0.0-alpha1,<4.0.22", "appwrite/server-ce": "<=1.2.1", "arc/web": "<3", "area17/twill": "<1.2.5|>=2,<2.5.3", @@ -8541,6 +8882,7 @@ "awesome-support/awesome-support": "<=6.0.7", "aws/aws-sdk-php": "<3.288.1", "azuracast/azuracast": "<0.18.3", + "b13/seo_basics": "<0.8.2", "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2", "backpack/crud": "<3.4.9", "backpack/filemanager": "<2.0.2|>=3,<3.0.9", @@ -8556,6 +8898,7 @@ "bbpress/bbpress": "<2.6.5", "bcosca/fatfree": "<3.7.2", "bedita/bedita": "<4", + "bednee/cooluri": "<1.0.30", "bigfork/silverstripe-form-capture": ">=3,<3.1.1", "billz/raspap-webgui": "<=3.1.4", "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", @@ -8572,6 +8915,7 @@ "brotkrueml/typo3-matomo-integration": "<1.3.2", "buddypress/buddypress": "<7.2.1", "bugsnag/bugsnag-laravel": ">=2,<2.0.2", + "bvbmedia/multishop": "<2.0.39", "bytefury/crater": "<6.0.2", "cachethq/cachet": "<2.5.1", "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", @@ -8590,6 +8934,7 @@ "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", "ckeditor/ckeditor": "<4.25", "clickstorm/cs-seo": ">=6,<6.7|>=7,<7.4|>=8,<8.3|>=9,<9.2", + "co-stack/fal_sftp": "<0.2.6", "cockpit-hq/cockpit": "<2.7|==2.7", "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<3.1.9", @@ -8597,9 +8942,10 @@ "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", + "commerceteam/commerce": ">=0.9.6,<0.9.9", "components/jquery": ">=1.0.3,<3.5", "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7", - "concrete5/concrete5": "<9.4.0.0-RC1-dev", + "concrete5/concrete5": "<9.4.0.0-RC2-dev", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", @@ -8630,6 +8976,9 @@ "devgroup/dotplant": "<2020.09.14-dev", "digimix/wp-svg-upload": "<=1", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "dl/yag": "<3.0.1", + "dmk/webkitpdf": "<1.1.4", + "dnadesign/silverstripe-elemental": "<5.3.12", "doctrine/annotations": "<1.2.7", "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", "doctrine/common": "<2.4.3|>=2.5,<2.5.1", @@ -8657,6 +9006,7 @@ "drupal/matomo": "<1.24", "drupal/oauth2_client": "<4.1.3", "drupal/oauth2_server": "<2.1", + "drupal/obfuscate": "<2.0.1", "drupal/rapidoc_elements_field_formatter": "<1.0.1", "drupal/spamspan": "<3.2.1", "drupal/tfa": "<1.10", @@ -8689,7 +9039,7 @@ "ezsystems/ezplatform-http-cache": "<2.3.16", "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35", "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", - "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev|>=3.3,<3.3.40", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.26|>=3.3,<3.3.40", "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", "ezsystems/ezplatform-user": ">=1,<1.0.1", "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", @@ -8743,6 +9093,8 @@ "funadmin/funadmin": "<=5.0.2", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", + "georgringer/news": "<1.3.3", + "geshi/geshi": "<1.0.8.11-dev", "getformwork/formwork": "<1.13.1|>=2.0.0.0-beta1,<2.0.0.0-beta4", "getgrav/grav": "<1.7.46", "getkirby/cms": "<=3.6.6.5|>=3.7,<=3.7.5.4|>=3.8,<=3.8.4.3|>=3.9,<=3.9.8.1|>=3.10,<=3.10.1|>=4,<=4.3", @@ -8775,7 +9127,7 @@ "hyn/multi-tenant": ">=5.6,<5.7.2", "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.14", "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", - "ibexa/fieldtype-richtext": ">=4.6,<4.6.10", + "ibexa/fieldtype-richtext": ">=4.6,<4.6.19", "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", "ibexa/http-cache": ">=4.6,<4.6.14", "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14", @@ -8791,7 +9143,7 @@ "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", "imdbphp/imdbphp": "<=5.1.1", "impresscms/impresscms": "<=1.4.5", - "impresspages/impresspages": "<=1.0.12", + "impresspages/impresspages": "<1.0.13", "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.3", "in2code/ipandlanguageredirect": "<5.1.2", "in2code/lux": "<17.6.1|>=18,<24.0.2", @@ -8804,25 +9156,30 @@ "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", "jackalope/jackalope-doctrine-dbal": "<1.7.4", + "jambagecom/div2007": "<0.10.2", "james-heinrich/getid3": "<1.9.21", "james-heinrich/phpthumb": "<1.7.12", "jasig/phpcas": "<1.3.3", + "jbartels/wec-map": "<3.0.3", "jcbrand/converse.js": "<3.3.3", "joelbutcher/socialstream": "<5.6|>=6,<6.2", "johnbillion/wp-crontrol": "<1.16.2", "joomla/application": "<1.0.13", "joomla/archive": "<1.1.12|>=2,<2.0.1", + "joomla/database": ">=1,<2.2|>=3,<3.4", "joomla/filesystem": "<1.6.2|>=2,<2.0.1", "joomla/filter": "<1.4.4|>=2,<2.0.1", "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12", "joomla/input": ">=2,<2.0.2", - "joomla/joomla-cms": ">=2.5,<3.9.12", + "joomla/joomla-cms": "<3.9.12|>=4,<4.4.13|>=5,<5.2.6", + "joomla/joomla-platform": "<1.5.4", "joomla/session": "<1.3.1", "joyqi/hyper-down": "<=2.4.27", "jsdecena/laracom": "<2.0.9", "jsmitty12/phpwhois": "<5.1", "juzaweb/cms": "<=3.4", "jweiland/events2": "<8.3.8|>=9,<9.0.6", + "jweiland/kk-downloader": "<1.2.2", "kazist/phpwhois": "<=4.2.6", "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", @@ -8879,6 +9236,7 @@ "mainwp/mainwp": "<=4.4.3.3", "mantisbt/mantisbt": "<=2.26.3", "marcwillmann/turn": "<0.3.3", + "matomo/matomo": "<1.11", "matyhtf/framework": "<3.0.6", "mautic/core": "<5.2.3", "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", @@ -8890,6 +9248,7 @@ "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", + "mehrwert/phpmyadmin": "<3.2", "melisplatform/melis-asset-manager": "<5.0.1", "melisplatform/melis-cms": "<5.0.1", "melisplatform/melis-front": "<5.0.1", @@ -8949,6 +9308,7 @@ "october/october": "<=3.6.4", "october/rain": "<1.0.472|>=1.1,<1.1.2", "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.5.15", + "oliverklee/phpunit": "<3.5.15", "omeka/omeka-s": "<4.0.3", "onelogin/php-saml": "<2.10.4", "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", @@ -8982,6 +9342,7 @@ "pear/archive_tar": "<1.4.14", "pear/auth": "<1.2.4", "pear/crypt_gpg": "<1.6.7", + "pear/http_request2": "<2.7", "pear/pear": "<=1.10.1", "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", "personnummer/personnummer": "<3.0.2", @@ -9007,7 +9368,7 @@ "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<1.7.4", + "pimcore/admin-ui-classic-bundle": "<1.7.6", "pimcore/customer-management-framework-bundle": "<4.2.1", "pimcore/data-hub": "<1.2.4", "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3", @@ -9015,6 +9376,7 @@ "pimcore/ecommerce-framework-bundle": "<1.0.10", "pimcore/perspective-editor": "<1.5.1", "pimcore/pimcore": "<11.5.4", + "piwik/piwik": "<1.11", "pixelfed/pixelfed": "<0.12.5", "plotly/plotly.js": "<2.25.2", "pocketmine/bedrock-protocol": "<8.0.2", @@ -9040,6 +9402,7 @@ "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", + "punktde/pt_extbase": "<1.5.1", "pusher/pusher-php-server": "<2.2.1", "pwweb/laravel-core": "<=0.3.6.0-beta", "pxlrbt/filament-excel": "<1.1.14|>=2.0.0.0-alpha,<2.3.3", @@ -9072,8 +9435,8 @@ "serluck/phpwhois": "<=4.2.6", "sfroemken/url_redirect": "<=1.2.1", "sheng/yiicms": "<1.2.1", - "shopware/core": "<=6.5.8.12|>=6.6,<=6.6.5", - "shopware/platform": "<=6.5.8.12|>=6.6,<=6.6.5", + "shopware/core": "<6.5.8.17-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev", + "shopware/platform": "<6.5.8.17-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev", "shopware/production": "<=6.3.5.2", "shopware/shopware": "<=5.7.17", "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", @@ -9086,7 +9449,7 @@ "silverstripe/cms": "<4.11.3", "silverstripe/comments": ">=1.3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<5.3.8", + "silverstripe/framework": "<5.3.23", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/recipe-cms": ">=4.5,<4.5.3", @@ -9109,7 +9472,9 @@ "simplesamlphp/xml-security": "==1.6.11", "simplito/elliptic-php": "<1.0.6", "sitegeist/fluid-components": "<3.5", + "sjbr/sr-feuser-register": "<2.6.2", "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", + "sjbr/static-info-tables": "<2.3.1", "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", @@ -9137,6 +9502,7 @@ "sulu/sulu": "<1.6.44|>=2,<2.5.21|>=2.6,<2.6.5", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", + "svewap/a21glossary": "<=0.4.10", "swag/paypal": "<5.4.4", "swiftmailer/swiftmailer": "<6.2.5", "swiftyedit/swiftyedit": "<1.2", @@ -9224,6 +9590,7 @@ "typo3/cms-dashboard": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-felogin": ">=4.2,<4.2.3", "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", @@ -9248,21 +9615,24 @@ "uvdesk/core-framework": "<=1.1.1", "vanilla/safecurl": "<0.9.2", "verbb/comments": "<1.5.5", - "verbb/formie": "<2.1.6", + "verbb/formie": "<=2.1.43", "verbb/image-resizer": "<2.0.9", "verbb/knock-knock": "<1.2.8", "verot/class.upload.php": "<=2.1.6", + "vertexvaar/falsftp": "<0.2.6", "villagedefrance/opencart-overclocked": "<=1.11.1", "vova07/yii2-fileapi-widget": "<0.1.9", "vrana/adminer": "<4.8.1", "vufind/vufind": ">=2,<9.1.1", "waldhacker/hcaptcha": "<2.1.2", "wallabag/tcpdf": "<6.2.22", - "wallabag/wallabag": "<2.6.7", + "wallabag/wallabag": "<2.6.11", "wanglelecc/laracms": "<=1.0.3", + "wapplersystems/a21glossary": "<=0.4.10", "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9", "web-auth/webauthn-lib": ">=4.5,<4.9", "web-feet/coastercms": "==5.5", + "web-tp3/wec_map": "<3.0.3", "webbuilders-group/silverstripe-kapost-bridge": "<0.4", "webcoast/deferred-image-processing": "<1.0.2", "webklex/laravel-imap": "<5.3", @@ -9292,8 +9662,8 @@ "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", - "yiisoft/yii": "<1.1.29", - "yiisoft/yii2": "<2.0.49.4-dev", + "yiisoft/yii": "<1.1.31", + "yiisoft/yii2": "<2.0.52", "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", "yiisoft/yii2-dev": "<=2.0.45", @@ -9379,7 +9749,7 @@ "type": "tidelift" } ], - "time": "2025-04-02T18:06:24+00:00" + "time": "2025-04-17T15:05:22+00:00" }, { "name": "sebastian/cli-parser", @@ -10246,6 +10616,141 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/browser-kit", + "version": "v7.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "8ce0ee23857d87d5be493abba2d52d1f9e49da61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/8ce0ee23857d87d5be493abba2d52d1f9e49da61", + "reference": "8ce0ee23857d87d5be493abba2d52d1f9e49da61", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/dom-crawler": "^6.4|^7.0" + }, + "require-dev": { + "symfony/css-selector": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v7.2.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-14T14:27:24+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v7.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7", + "reference": "19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v7.2.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-17T15:53:07+00:00" + }, { "name": "symfony/maker-bundle", "version": "v1.62.1", diff --git a/config/bundles.php b/config/bundles.php index 42d43d0..5b95dbf 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -1,6 +1,7 @@ ['all' => true], EasyAdminBundle::class => ['all' => true], DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], + SymfonyCastsVerifyEmailBundle::class => ['all' => true], ]; diff --git a/config/packages/mailer.yaml b/config/packages/mailer.yaml new file mode 100644 index 0000000..4759f62 --- /dev/null +++ b/config/packages/mailer.yaml @@ -0,0 +1,7 @@ +framework: + mailer: + dsn: '%env(MAILER_DSN)%' + envelope: + sender: '%env(MAILER_SENDER)%' + headers: + From: 'Tijd voor de test <%env(MAILER_SENDER)%>' diff --git a/config/packages/security.yaml b/config/packages/security.yaml index a1eeee1..1732fdb 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -18,11 +18,12 @@ security: lazy: true provider: app_user_provider form_login: - login_path: app_login - check_path: app_login + login_path: app_login_login + check_path: app_login_login enable_csrf: true + default_target_path: app_backoffice_index logout: - path: app_logout + path: app_login_logout # where to redirect after logout # target: app_any_route diff --git a/migrations/Version20250420111904.php b/migrations/Version20250420111904.php new file mode 100644 index 0000000..2b664eb --- /dev/null +++ b/migrations/Version20250420111904.php @@ -0,0 +1,30 @@ +addSql(<<<'SQL' + ALTER TABLE "user" ADD is_verified BOOLEAN NOT NULL + SQL); + } + + public function down(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE "user" DROP is_verified + SQL); + } +} diff --git a/migrations/Version20250420125040.php b/migrations/Version20250420125040.php new file mode 100644 index 0000000..2103f0e --- /dev/null +++ b/migrations/Version20250420125040.php @@ -0,0 +1,30 @@ +addSql(<<<'SQL' + ALTER TABLE season DROP preregister_candidates + SQL); + } + + public function down(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE season ADD preregister_candidates BOOLEAN NOT NULL DEFAULT true + SQL); + } +} diff --git a/rector.php b/rector.php index f800278..90975a8 100644 --- a/rector.php +++ b/rector.php @@ -26,6 +26,7 @@ return RectorConfig::configure() doctrineCodeQuality: true, symfonyCodeQuality: true, ) - ->withComposerBased(twig: true, doctrine: true, phpunit: true) + ->withAttributesSets(all: true) + ->withComposerBased(twig: true, doctrine: true, phpunit: true, symfony: true) ->withAttributesSets() ; diff --git a/src/Controller/BackofficeController.php b/src/Controller/BackofficeController.php index ae71e00..c02463d 100644 --- a/src/Controller/BackofficeController.php +++ b/src/Controller/BackofficeController.php @@ -6,24 +6,35 @@ namespace App\Controller; use App\Entity\Quiz; use App\Entity\Season; +use App\Entity\User; use App\Repository\CandidateRepository; use App\Repository\SeasonRepository; +use App\Security\Voter\SeasonVoter; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; #[AsController] +#[IsGranted('ROLE_USER')] final class BackofficeController extends AbstractController { public function __construct( private readonly SeasonRepository $seasonRepository, private readonly CandidateRepository $candidateRepository, + private readonly Security $security, ) {} #[Route('/backoffice/', name: 'app_backoffice_index')] public function index(): Response { - $seasons = $this->seasonRepository->findAll(); + $user = $this->getUser(); + \assert($user instanceof User); + + $seasons = $this->security->isGranted('ROLE_ADMIN') + ? $this->seasonRepository->findAll() + : $this->seasonRepository->getSeasonsForUser($user); return $this->render('backoffice/index.html.twig', [ 'seasons' => $seasons, @@ -31,6 +42,7 @@ final class BackofficeController extends AbstractController } #[Route('/backoffice/{seasonCode}', name: 'app_backoffice_season')] + #[IsGranted(SeasonVoter::EDIT, subject: 'season')] public function season(Season $season): Response { return $this->render('backoffice/season.html.twig', [ diff --git a/src/Controller/LoginController.php b/src/Controller/LoginController.php index c581f81..cf4e3df 100644 --- a/src/Controller/LoginController.php +++ b/src/Controller/LoginController.php @@ -11,7 +11,7 @@ use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; #[AsController] -class LoginController extends AbstractController +final class LoginController extends AbstractController { #[Route(path: '/login', name: 'app_login_login')] public function login(AuthenticationUtils $authenticationUtils): Response @@ -29,7 +29,7 @@ class LoginController extends AbstractController } #[Route(path: '/logout', name: 'app_login_logout')] - public function logout(): void + public function logout(): never { throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); } diff --git a/src/Controller/PrepareEliminationController.php b/src/Controller/PrepareEliminationController.php index adcc94f..ecaedf4 100644 --- a/src/Controller/PrepareEliminationController.php +++ b/src/Controller/PrepareEliminationController.php @@ -8,10 +8,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; -#[Route(path: '/backoffice/elimination')] final class PrepareEliminationController extends AbstractController { - #[Route('/prepare', name: 'app_prepare_elimination')] + #[Route('/backoffice/elimination/prepare', name: 'app_prepare_elimination')] public function index(): Response { return $this->render('prepare_elimination/index.html.twig', [ diff --git a/src/Controller/QuizController.php b/src/Controller/QuizController.php index b0d3016..a8c7982 100644 --- a/src/Controller/QuizController.php +++ b/src/Controller/QuizController.php @@ -84,14 +84,9 @@ final class QuizController extends AbstractController $candidate = $candidateRepository->getCandidateByHash($season, $nameHash); if (!$candidate instanceof Candidate) { - if ($season->isPreregisterCandidates()) { - $this->addFlash(FlashType::Danger, 'Candidate not found'); + $this->addFlash(FlashType::Danger, 'Candidate not found'); - return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season->getSeasonCode()]); - } - - $candidate = new Candidate(Base64::base64UrlDecode($nameHash)); - $candidateRepository->save($candidate); + return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season->getSeasonCode()]); } if ('POST' === $request->getMethod()) { diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php new file mode 100644 index 0000000..0575940 --- /dev/null +++ b/src/Controller/RegistrationController.php @@ -0,0 +1,93 @@ +createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var string $plainPassword */ + $plainPassword = $form->get('plainPassword')->getData(); + + $user->setPassword($userPasswordHasher->hashPassword($user, $plainPassword)); + + $entityManager->persist($user); + $entityManager->flush(); + + // generate a signed url and email it to the user + $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, + (new TemplatedEmail()) + ->to((string) $user->getEmail()) + ->subject($this->translator->trans('Please Confirm your Email')) + ->htmlTemplate('registration/confirmation_email.html.twig') + ); + + $response = $security->login($user, 'form_login', 'main'); + \assert($response instanceof Response); + + return $response; + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form, + ]); + } + + #[Route('/verify/email', name: 'app_verify_email')] + public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response + { + $id = $request->query->get('id'); + + if (null === $id) { + return $this->redirectToRoute('app_register'); + } + + $user = $userRepository->find($id); + + if (null === $user) { + return $this->redirectToRoute('app_register'); + } + + // validate email confirmation link, sets User::isVerified=true and persists + try { + $this->emailVerifier->handleEmailConfirmation($request, $user); + } catch (VerifyEmailExceptionInterface $verifyEmailException) { + $this->addFlash('verify_email_error', $translator->trans($verifyEmailException->getReason(), [], 'VerifyEmailBundle')); + + return $this->redirectToRoute('app_register'); + } + + $this->addFlash('success', 'Your email address has been verified.'); + + return $this->redirectToRoute('app_backoffice_index'); + } +} diff --git a/src/DataFixtures/KrtekFixtures.php b/src/DataFixtures/KrtekFixtures.php index cc4ba2d..4169294 100644 --- a/src/DataFixtures/KrtekFixtures.php +++ b/src/DataFixtures/KrtekFixtures.php @@ -21,7 +21,6 @@ class KrtekFixtures extends Fixture $season->setName('Krtek Weekend') ->setSeasonCode('krtek') - ->setPreregisterCandidates(true) ->addCandidate(new Candidate('Claudia')) ->addCandidate(new Candidate('Eelco')) ->addCandidate(new Candidate('Elise')) diff --git a/src/Entity/Season.php b/src/Entity/Season.php index a17bf17..287211e 100644 --- a/src/Entity/Season.php +++ b/src/Entity/Season.php @@ -27,9 +27,6 @@ class Season #[ORM\Column(length: 5)] private string $seasonCode; - #[ORM\Column] - private bool $preregisterCandidates; - /** @var Collection */ #[ORM\OneToMany(targetEntity: Quiz::class, mappedBy: 'season', cascade: ['persist'], orphanRemoval: true)] private Collection $quizzes; @@ -81,18 +78,6 @@ class Season return $this; } - public function isPreregisterCandidates(): bool - { - return $this->preregisterCandidates; - } - - public function setPreregisterCandidates(bool $preregisterCandidates): static - { - $this->preregisterCandidates = $preregisterCandidates; - - return $this; - } - /** @return Collection */ public function getQuizzes(): Collection { @@ -158,4 +143,9 @@ class Season return $this; } + + public function isOwner(User $user): bool + { + return $this->owners->contains($user); + } } diff --git a/src/Entity/User.php b/src/Entity/User.php index fb317a2..5f742bd 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -10,6 +10,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\Types\UuidType; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Uid\Uuid; @@ -17,6 +18,7 @@ use Symfony\Component\Uid\Uuid; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Table(name: '`user`')] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])] +#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] @@ -40,6 +42,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\ManyToMany(targetEntity: Season::class, mappedBy: 'owners')] private Collection $seasons; + #[ORM\Column] + private bool $isVerified = false; + public function __construct() { $this->seasons = new ArrayCollection(); @@ -140,4 +145,21 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + public function isVerified(): bool + { + return $this->isVerified; + } + + public function setIsVerified(bool $isVerified): static + { + $this->isVerified = $isVerified; + + return $this; + } + + public function isAdmin(): bool + { + return \in_array('ROLE_ADMIN', $this->getRoles(), true); + } } diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php new file mode 100644 index 0000000..1d82609 --- /dev/null +++ b/src/Form/RegistrationFormType.php @@ -0,0 +1,56 @@ + + */ +class RegistrationFormType extends AbstractType +{ + public function __construct(private readonly TranslatorInterface $translator) {} + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('email', EmailType::class, [ + 'label' => $this->translator->trans('Email'), + 'attr' => ['autocomplete' => 'email'], + ]) + ->add('plainPassword', PasswordType::class, [ + 'label' => $this->translator->trans('Password'), + 'mapped' => false, + 'attr' => ['autocomplete' => 'new-password'], + 'constraints' => [ + new NotBlank([ + 'message' => 'Please enter a password', + ]), + new Length([ + 'min' => 8, + 'minMessage' => 'Your password should be at least {{ limit }} characters', + // max length allowed by Symfony for security reasons + 'max' => 4096, + ]), + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Repository/SeasonRepository.php b/src/Repository/SeasonRepository.php index 62bd0b7..2d09619 100644 --- a/src/Repository/SeasonRepository.php +++ b/src/Repository/SeasonRepository.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Repository; use App\Entity\Season; +use App\Entity\User; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -17,4 +18,15 @@ class SeasonRepository extends ServiceEntityRepository { parent::__construct($registry, Season::class); } + + /** @return list Returns an array of Season objects */ + public function getSeasonsForUser(User $user): array + { + $qb = $this->createQueryBuilder('s') + ->where(':user MEMBER OF s.owners') + ->orderBy('s.name') + ->setParameter('user', $user); + + return $qb->getQuery()->getResult(); + } } diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 5633a4e..eb6d672 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -22,7 +22,9 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader parent::__construct($registry, User::class); } - /** Used to upgrade (rehash) the user's password automatically over time. */ + /** Used to upgrade (rehash) the user's password automatically over time. + * @param User $user + * */ public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void { $user->setPassword($newHashedPassword); diff --git a/src/Security/EmailVerifier.php b/src/Security/EmailVerifier.php new file mode 100644 index 0000000..730e585 --- /dev/null +++ b/src/Security/EmailVerifier.php @@ -0,0 +1,52 @@ +verifyEmailHelper->generateSignature( + $verifyEmailRouteName, + (string) $user->getId(), + (string) $user->getEmail(), + ['id' => $user->getId()] + ); + + $context = $email->getContext(); + $context['signedUrl'] = $signatureComponents->getSignedUrl(); + $context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey(); + $context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData(); + + $email->context($context); + + $this->mailer->send($email); + } + + /** @throws VerifyEmailExceptionInterface */ + public function handleEmailConfirmation(Request $request, User $user): void + { + $this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, (string) $user->getId(), (string) $user->getEmail()); + + $user->setIsVerified(true); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + } +} diff --git a/src/Security/Voter/SeasonVoter.php b/src/Security/Voter/SeasonVoter.php new file mode 100644 index 0000000..8dcc00f --- /dev/null +++ b/src/Security/Voter/SeasonVoter.php @@ -0,0 +1,48 @@ +getUser(); + if (!$user instanceof User) { + return false; + } + + if ($user->isAdmin()) { + return true; + } + + switch ($attribute) { + case self::EDIT: + case self::DELETE: + if ($subject->isOwner($user)) { + return true; + } + + break; + } + + return false; + } +} diff --git a/symfony.lock b/symfony.lock index 89b76ab..7ce65a2 100644 --- a/symfony.lock +++ b/symfony.lock @@ -141,6 +141,18 @@ "src/Kernel.php" ] }, + "symfony/mailer": { + "version": "7.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "4.3", + "ref": "09051cfde49476e3c12cd3a0e44289ace1c75a4f" + }, + "files": [ + "config/packages/mailer.yaml" + ] + }, "symfony/maker-bundle": { "version": "1.61", "recipe": { @@ -248,6 +260,9 @@ "config/routes/web_profiler.yaml" ] }, + "symfonycasts/verify-email-bundle": { + "version": "v1.17.3" + }, "twig/extra-bundle": { "version": "v3.18.0" }, diff --git a/templates/backoffice/base.html.twig b/templates/backoffice/base.html.twig index aac28e1..f29abf8 100644 --- a/templates/backoffice/base.html.twig +++ b/templates/backoffice/base.html.twig @@ -28,7 +28,14 @@ {% endblock nav %}
- {# {% include "messages.html" %} #} + {% for label, messages in app.flashes() %} + {% for message in messages %} + + {% endfor %} + {% endfor %} {% block body %} {% endblock body %}
diff --git a/templates/backoffice/index.html.twig b/templates/backoffice/index.html.twig index 05a0f67..090887f 100644 --- a/templates/backoffice/index.html.twig +++ b/templates/backoffice/index.html.twig @@ -3,26 +3,33 @@ {% block title %}Hello BackofficeController!{% endblock %} {% block body %} -

{% trans %}Your Seasons{% endtrans %}

+

+ {{ is_granted('ROLE_ADMIN') ? 'All Seasons'|trans : 'Your Seasons'|trans }} +

- - - - - + {% if is_granted('ROLE_ADMIN') %} + + {% endif %} + + + + {% for season in seasons %} + {% if is_granted('ROLE_ADMIN') %} + + {% endif %} - {% else %} diff --git a/templates/login/login.html.twig b/templates/login/login.html.twig index 7e17603..023da61 100644 --- a/templates/login/login.html.twig +++ b/templates/login/login.html.twig @@ -1,43 +1,39 @@ -{% extends 'base.html.twig' %} +{% extends 'backoffice/base.html.twig' %} -{% block title %}Log in!{% endblock %} +{% block title %}Log in{% endblock %} {% block body %} - - {% if error %} -
{{ error.messageKey|trans(error.messageData, 'security') }}
- {% endif %} - - {% if app.user %} -
- You are logged in as {{ app.user.userIdentifier }}, Logout -
- {% endif %} - -

Please sign in

- - - - - - - - {# - Uncomment this section and add a remember_me option below your firewall to activate remember me functionality. - See https://symfony.com/doc/current/security/remember_me.html - -
- - + {% if app.user %} +
+ You are logged in as {{ app.user.userIdentifier }}, Logout
- #} + {% else %} + +

{{ 'Please sign in'|trans }}

+
+ + +
+
+ + +
+ +
+ + +
- - + + {{ 'Create an account'|trans }} + + {% endif %} {% endblock %} diff --git a/templates/registration/confirmation_email.html.twig b/templates/registration/confirmation_email.html.twig new file mode 100644 index 0000000..7c79d8a --- /dev/null +++ b/templates/registration/confirmation_email.html.twig @@ -0,0 +1,11 @@ +

Hi! Please confirm your email!

+ +

+ Please confirm your email address by clicking the following link:

+ Confirm my Email. + This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}. +

+ +

+ Cheers! +

diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig new file mode 100644 index 0000000..dd88bb6 --- /dev/null +++ b/templates/registration/register.html.twig @@ -0,0 +1,17 @@ +{% extends 'backoffice/base.html.twig' %} +{% block title %}{{ 'Register'|trans }}{% endblock %} + +{% block body %} +

{{ 'Register'|trans }}

+ + {{ form_errors(registrationForm) }} + + {{ form_start(registrationForm) }} + {{ form_row(registrationForm.email) }} + {{ form_row(registrationForm.plainPassword) }} + + + {{ 'Already have an account? Log in'|trans }} + + {{ form_end(registrationForm) }} +{% endblock %} diff --git a/tests/LoginControllerTest.php b/tests/LoginControllerTest.php deleted file mode 100644 index ff6134a..0000000 --- a/tests/LoginControllerTest.php +++ /dev/null @@ -1,85 +0,0 @@ -client = static::createClient(); - $container = static::getContainer(); - $em = $container->get('doctrine.orm.entity_manager'); - $userRepository = $em->getRepository(User::class); - - // Remove any existing users from the test database - foreach ($userRepository->findAll() as $user) { - $em->remove($user); - } - - $em->flush(); - - // Create a User fixture - /** @var UserPasswordHasherInterface $passwordHasher */ - $passwordHasher = $container->get('security.user_password_hasher'); - - $user = (new User())->setEmail('email@example.com'); - $user->setPassword($passwordHasher->hashPassword($user, 'password')); - - $em->persist($user); - $em->flush(); - } - - public function testLogin(): void - { - // Denied - Can't login with invalid email address. - $this->client->request('GET', '/login'); - $this->assertResponseIsSuccessful(); - - $this->client->submitForm('Sign in', [ - '_username' => 'doesNotExist@example.com', - '_password' => 'password', - ]); - - $this->assertResponseRedirects('/login'); - $this->client->followRedirect(); - - // Ensure we do not reveal if the user exists or not. - $this->assertSelectorTextContains('.alert-danger', 'Invalid credentials.'); - - // Denied - Can't login with invalid password. - $this->client->request('GET', '/login'); - $this->assertResponseIsSuccessful(); - - $this->client->submitForm('Sign in', [ - '_username' => 'email@example.com', - '_password' => 'bad-password', - ]); - - $this->assertResponseRedirects('/login'); - $this->client->followRedirect(); - - // Ensure we do not reveal the user exists but the password is wrong. - $this->assertSelectorTextContains('.alert-danger', 'Invalid credentials.'); - - // Success - Login with valid credentials is allowed. - $this->client->submitForm('Sign in', [ - '_username' => 'email@example.com', - '_password' => 'password', - ]); - - $this->assertResponseRedirects('/'); - $this->client->followRedirect(); - - $this->assertSelectorNotExists('.alert-danger'); - $this->assertResponseIsSuccessful(); - } -} diff --git a/translations/messages+intl-icu.nl.yaml b/translations/messages+intl-icu.nl.yaml index 792cb4b..5379739 100644 --- a/translations/messages+intl-icu.nl.yaml +++ b/translations/messages+intl-icu.nl.yaml @@ -1,8 +1,12 @@ 'Active Quiz': 'Actieve test' +'All Seasons': 'Alle seizoenen' +'Already have an account? Log in': 'Heb je al een account? Log in' Candidate: Kandidaat Candidates: Kandidaten 'Correct Answers': 'Goede antwoorden' Corrections: Jokers +'Create an account': 'Maak een account aan' +Email: E-mail 'Enter your name': 'Voor je naam in' 'Load Prepared Elimination': 'Laad voorbereide eliminatie' Manage: Beheren @@ -10,15 +14,21 @@ Name: Naam 'No active quiz': 'Geen actieve test' 'No results': 'Geen resultaten' 'Number of dropouts:': 'Aantal afvallers:' +Owner(s): Eigenar(en) +Password: Wachtwoord +'Please Confirm your Email': messages +'Please sign in': 'Log in aub' 'Prepare Custom Elimination': 'Bereid aangepaste eliminatie voor' -'Preregister?': 'Voorregistreren?' Questions: Vragen Quiz: Test Quizzes: Tests +Register: Registreren +'Remember me': 'Onthoud mij' Score: Score Season: Seizoen 'Season Code': Seizoenscode Seasons: Seizoenen +'Sign in': 'Log in' 'Start Elimination': 'Start eliminatie' 'There are no answers for this question': 'Er zijn geen antwoorden voor deze vraag' Time: Tijd
{% trans %}Name{% endtrans %}{% trans %}Active Quiz{% endtrans %}{% trans %}Season Code{% endtrans %}{% trans %}Preregister?{% endtrans %}{% trans %}Manage{% endtrans %}{{ 'Owner(s)'|trans }}{{ 'Name'|trans }}{{ 'Active Quiz'|trans }}{{ 'Season Code'|trans }}{{ 'Manage'|trans }}
{{ season.owners|map(o => o.email)|join(', ') }}{{ season.name }} {% if season.activeQuiz %} {{ season.activeQuiz.name }} {% else %} - {% trans %} No active quiz {% endtrans %} + {{ ' No active quiz '|trans }} {% endif %} @@ -30,14 +37,7 @@ {% else %}class="disabled" {% endif %}>{{ season.seasonCode }} - - - {% trans %}Manage{% endtrans %} + {{ 'Manage'|trans }}