53 Commits

Author SHA1 Message Date
0319224979 Dependey updates 2025-09-28 21:19:01 +02:00
81e471a760 Change namespace to Tvdt 2025-09-28 18:14:58 +02:00
cfb69c8dab Add Deploy step 2025-09-08 21:51:00 +02:00
35ec71302b Update bake 2025-09-08 19:53:23 +02:00
b7a570928a Update dependencies 2025-09-08 19:50:23 +02:00
d80436534f Update Symfony packages 2025-09-08 19:26:45 +02:00
dependabot[bot]
14e2dd490e Bump phpstan/phpstan-symfony from 2.0.6 to 2.0.7
Bumps [phpstan/phpstan-symfony](https://github.com/phpstan/phpstan-symfony) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/phpstan/phpstan-symfony/releases)
- [Commits](https://github.com/phpstan/phpstan-symfony/compare/2.0.6...2.0.7)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-symfony
  dependency-version: 2.0.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-25 13:30:05 +02:00
dependabot[bot]
68e54b1110 Bump doctrine/doctrine-bundle from 2.14.0 to 2.15.0
Bumps [doctrine/doctrine-bundle](https://github.com/doctrine/DoctrineBundle) from 2.14.0 to 2.15.0.
- [Release notes](https://github.com/doctrine/DoctrineBundle/releases)
- [Commits](https://github.com/doctrine/DoctrineBundle/compare/2.14.0...2.15.0)

---
updated-dependencies:
- dependency-name: doctrine/doctrine-bundle
  dependency-version: 2.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-25 13:29:46 +02:00
dependabot[bot]
e615b2cdea Bump symfony/serializer from 7.3.0 to 7.3.1
Bumps [symfony/serializer](https://github.com/symfony/serializer) from 7.3.0 to 7.3.1.
- [Release notes](https://github.com/symfony/serializer/releases)
- [Changelog](https://github.com/symfony/serializer/blob/7.3/CHANGELOG.md)
- [Commits](https://github.com/symfony/serializer/compare/v7.3.0...v7.3.1)

---
updated-dependencies:
- dependency-name: symfony/serializer
  dependency-version: 7.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-25 13:29:34 +02:00
dependabot[bot]
4b45a2c557 Bump symfony/security-bundle from 7.3.0 to 7.3.1
Bumps [symfony/security-bundle](https://github.com/symfony/security-bundle) from 7.3.0 to 7.3.1.
- [Release notes](https://github.com/symfony/security-bundle/releases)
- [Changelog](https://github.com/symfony/security-bundle/blob/7.3/CHANGELOG.md)
- [Commits](https://github.com/symfony/security-bundle/compare/v7.3.0...v7.3.1)

---
updated-dependencies:
- dependency-name: symfony/security-bundle
  dependency-version: 7.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-25 13:29:24 +02:00
c6fe553341 Create dependabot.yml 2025-07-25 13:04:20 +02:00
69a2b9c811 Open db port 2025-06-15 02:18:27 +02:00
f31a7d527d Fix scores? 2025-06-14 21:34:52 +02:00
ed3cf7644f Correct sorting for scores 2025-06-14 12:32:18 +02:00
77d21b004f Update backoffice templates to dynamically include titles and improve candidate handling in SeasonController 2025-06-14 12:32:18 +02:00
379fafcd16 Fix cs 2025-06-12 15:03:01 +02:00
7586d2d8ac Merge branch 'main' of github.com:MarijnDoeve/TijdVoorDeTest 2025-06-11 18:27:04 +02:00
9e41376244 Translations! 2025-06-11 18:26:17 +02:00
2bfef94bbe Add settings management for seasons, update templates, migrations, and commands 2025-06-10 23:06:59 +02:00
a8c4cba968 Disable Turbo for now 2025-06-09 15:52:15 +02:00
d5566d4737 Refactor repositories to use DQL queries, simplify logic, and enhance query efficiency 2025-06-09 14:19:10 +02:00
366bc36520 Fix typo 2025-06-07 23:47:58 +02:00
79b24b0d44 Refine error handling 2025-06-07 23:45:40 +02:00
7f93680987 Allow mail send fail 2025-06-07 23:43:52 +02:00
bdbff32256 Add MAILER_DSN 2025-06-07 23:34:50 +02:00
ebadc24b59 PHP 8.4 2025-06-07 22:24:38 +02:00
e0075fdcdc Rector upgrades 2025-06-07 22:24:38 +02:00
06aafefffc Upgrade to Symfony 7.3 2025-06-07 22:24:38 +02:00
ff6534fa81 Improve links 2025-06-07 22:24:38 +02:00
6a77df402d Add quiz clearing and deletion functionality with UI enhancements
This commit introduces the ability to clear quiz results and delete quizzes directly from the backoffice. It includes new routes, controllers, modals for user confirmation, and updates to translations. The `QuizRepository` now supports dedicated methods for clearing results and deleting quizzes along with error handling. Related database migrations and front-end adjustments are also included.
2025-06-07 20:59:01 +02:00
79236d84e9 Add correction management to backoffice, refactor security voter logic, and enhance candidate scoring
This commit introduces functionality to manage candidate corrections in the backoffice, with updated templates and a new route handler. The SeasonVoter is refactored to support additional entities, and scoring logic is updated to incorporate corrections consistently. Includes test coverage for voter logic and UI improvements for score tables.
2025-06-07 16:09:13 +02:00
beb8d13dde Refactor Candidate and Quiz entities, rename Correction to QuizCandidate, and update related workflows
This commit removes nullable Uuid properties for consistency, transitions the Correction entity to QuizCandidate with associated migrations, refactors queries and repositories, adjusts related routes and controllers to use the new entity, updates front-end assets for elimination workflows, and standardizes route requirements and naming conventions.
2025-06-06 23:06:47 +02:00
3e724ff1fb Add Turbo 2025-06-06 20:12:54 +02:00
e131d3b8d9 Fix cs 2025-06-06 19:33:54 +02:00
f6715de330 Add trailing comma in multi line. 2025-06-05 15:25:03 +02:00
0be29190e6 Fix scss 2025-06-01 21:36:39 +02:00
c6f9b57c60 Refactor translations to XLIFF format, enhance elimination workflows, and update compose configuration
This commit switches translations from YAML to XLIFF format for better standardization, updates the elimination preparation process with UI and functionality improvements, tweaks form structures, adjusts compose.override.yaml for improved asset handling, and optimizes back office usability with refined translation handling.
2025-06-01 21:28:56 +02:00
cd5946bda8 Hotfix Sass build 2025-06-01 15:45:15 +02:00
25aa8b8622 Introduce importmap, enhance elimination workflows, and update assets
This commit adds initial importmap configuration to manage assets, updates the elimination preparation workflow with form enhancements and database changes, introduces new styles and JS assets, refines translations, and improves entity handling with an input bag update method.
2025-06-01 15:40:47 +02:00
d3e5cb0569 Refactor elimination feature and improve backoffice usability
This commit introduces a refactored EliminationFactory for better modularity, updates the elimination preparation process, and adds functionality to view eliminations. Backoffice templates and forms have been reorganized, minor translations were corrected, and additional assets like styles and flashes were included for enhanced user experience.
2025-06-01 15:40:47 +02:00
e0350c8c31 WIP 2025-06-01 15:40:47 +02:00
58bda32f09 Update compose.yaml
Add always to db
2025-05-08 17:08:16 +02:00
0cd23f906e Create devcontainer.json 2025-05-05 12:34:04 +02:00
Marijn Doeve
f1fe827337 Add Sentry Replay 2025-04-30 14:59:41 +02:00
Marijn Doeve
ca08df26b2 Add Sentry config 2025-04-29 08:34:55 +02:00
Marijn Doeve
60723657c2 Add missing env vars 2025-04-29 08:17:50 +02:00
09d74ed327 Add lost file 2025-04-29 07:55:17 +02:00
49b7c0f5d5 Add Sheet upload function 2025-04-28 23:01:37 +02:00
9bae324447 Enhance login and navigation templates with authentication checks 2025-04-23 23:35:08 +02:00
4712c01688 Add quiz management features and improve UI 2025-04-23 23:23:22 +02:00
4e22feb256 Update framework.yaml 2025-04-22 23:49:20 +02:00
0f07e7eabf Phpstan 2025-04-22 23:42:07 +02:00
7b05e52d95 A lot 2025-04-22 22:25:18 +02:00
166 changed files with 7387 additions and 2846 deletions

View File

@@ -35,6 +35,9 @@ indent_size = 4
trim_trailing_whitespace = false trim_trailing_whitespace = false
indent_size = 2 indent_size = 2
[config/**/*.{yaml,yml}]
indent_size = 4
[.github/workflows/*.yml] [.github/workflows/*.yml]
indent_size = 2 indent_size = 2

View File

@@ -2,3 +2,4 @@
###> symfony/framework-bundle ### ###> symfony/framework-bundle ###
APP_SECRET=e26b9552d9e7f969b160373effaa7690 APP_SECRET=e26b9552d9e7f969b160373effaa7690
###< symfony/framework-bundle ### ###< symfony/framework-bundle ###
MAILER_SENDER=info@tijdvoordetest.nl

4
.env.test Normal file
View File

@@ -0,0 +1,4 @@
# define your env variables for the test env here
KERNEL_CLASS='Tvdt\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "composer" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- main - main
tags:
- '*'
pull_request: ~ pull_request: ~
workflow_dispatch: ~ workflow_dispatch: ~
@@ -18,10 +20,12 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Lint Dockerfile
uses: hadolint/hadolint-action@v3.1.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build Docker images - name: Build Docker images
uses: docker/bake-action@v4 uses: docker/bake-action@v5
with: with:
pull: true pull: true
load: true load: true
@@ -33,31 +37,37 @@ jobs:
*.cache-from=type=gha,scope=refs/heads/main *.cache-from=type=gha,scope=refs/heads/main
*.cache-to=type=gha,scope=${{github.ref}},mode=max *.cache-to=type=gha,scope=${{github.ref}},mode=max
- name: Start services - name: Start services
run: docker compose up --wait --no-build run: docker compose up php database --wait --no-build
- name: Lint Twig templates
run: docker compose exec -T php bin/console lint:twig --format=github templates
- name: Coding Style - name: Coding Style
run: docker compose exec -T php vendor/bin/php-cs-fixer check --diff --show-progress=none run: docker compose exec -T php vendor/bin/php-cs-fixer check --diff --show-progress=none
- name: Twig Coding Style
run: docker compose exec -T php vendor/bin/twig-cs-fixer check
- name: Check HTTP reachability - name: Check HTTP reachability
run: curl -v --fail-with-body http://localhost run: curl -v --fail-with-body http://localhost
- name: Check HTTPS reachability
if: false # Remove this line when the homepage will be configured, or change the path to check
run: curl -vk --fail-with-body https://localhost
- name: Check Mercure reachability - name: Check Mercure reachability
if: false
run: curl -vkI --fail-with-body https://localhost/.well-known/mercure?topic=test run: curl -vkI --fail-with-body https://localhost/.well-known/mercure?topic=test
- name: Create test database - name: Create test database
run: docker compose exec -T php bin/console -e test doctrine:database:create run: docker compose exec -T php bin/console -e test doctrine:database:create
- name: Run migrations - name: Run migrations
run: docker compose exec -T php bin/console -e test doctrine:migrations:migrate --no-interaction run: docker compose exec -T php bin/console -e test doctrine:migrations:migrate --no-interaction
- name: Run PHPUnit - name: Run PHPUnit
if: false # Remove this line when the tests are ready
run: docker compose exec -T php vendor/bin/phpunit run: docker compose exec -T php vendor/bin/phpunit
- name: Doctrine Schema Validator - name: Doctrine Schema Validator
run: docker compose exec -T php bin/console -e test doctrine:schema:validate run: docker compose exec -T php bin/console -e test doctrine:schema:validate
lint: deploy:
name: Docker Lint name: Deploy
environment:
name: ${{ startsWith(github.ref, 'refs/tags/') && 'production' || (github.ref == 'refs/heads/main' && 'acceptance' || '') }}
url: ${{ vars.URL }}
needs: tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps: steps:
- name: Checkout - shell: bash
uses: actions/checkout@v4 env:
- name: Lint Dockerfile PORTAINER_WEBHOOK: ${{secrets.PORTAINER_WEBHOOK}}
uses: hadolint/hadolint-action@v3.1.0 run: |
curl -v -X POST "$PORTAINER_WEBHOOK"

12
.gitignore vendored
View File

@@ -103,9 +103,19 @@ phpstan.neon
###< friendsofphp/php-cs-fixer ### ###< friendsofphp/php-cs-fixer ###
###> phpunit/phpunit ### ###> phpunit/phpunit ###
/phpunit.xml /phpunit.xml
.phpunit.result.cache /.phpunit.cache/
###< phpunit/phpunit ### ###< phpunit/phpunit ###
###> vincentlanglet/twig-cs-fixer ### ###> vincentlanglet/twig-cs-fixer ###
/.twig-cs-fixer.cache /.twig-cs-fixer.cache
###< vincentlanglet/twig-cs-fixer ### ###< vincentlanglet/twig-cs-fixer ###
###> symfony/phpunit-bridge ###
.phpunit.result.cache
/phpunit.xml
###< symfony/phpunit-bridge ###
###> symfony/asset-mapper ###
/public/assets/
/assets/vendor/
###< symfony/asset-mapper ###

View File

@@ -2,8 +2,8 @@
<module type="WEB_MODULE" version="4"> <module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Tvdt\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" /> <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Tvdt\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/ndjson-react" /> <excludeFolder url="file://$MODULE_DIR$/vendor/clue/ndjson-react" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" /> <excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" /> <excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" />
@@ -81,7 +81,6 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/runtime" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/runtime" />
@@ -102,7 +101,6 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/form" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/form" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/password-hasher" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/password-hasher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-icu" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-icu" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-access" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-access" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-info" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-info" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-bundle" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-bundle" />
@@ -136,8 +134,6 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/browser-kit" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/browser-kit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dom-crawler" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dom-crawler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mailer" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mailer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php83" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php84" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfonycasts/verify-email-bundle" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfonycasts/verify-email-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" /> <excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
<excludeFolder url="file://$MODULE_DIR$/vendor/jean85/pretty-package-versions" /> <excludeFolder url="file://$MODULE_DIR$/vendor/jean85/pretty-package-versions" />
@@ -147,6 +143,30 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry" /> <excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry-symfony" /> <excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry-symfony" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/psr-http-message-bridge" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/psr-http-message-bridge" />
<excludeFolder url="file://$MODULE_DIR$/.phpunit.cache" />
<excludeFolder url="file://$MODULE_DIR$/var" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpstan/phpdoc-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/css-selector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/phpunit-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/serializer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/maennchen/zipstream-php" />
<excludeFolder url="file://$MODULE_DIR$/vendor/markbaker/complex" />
<excludeFolder url="file://$MODULE_DIR$/vendor/markbaker/matrix" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpoffice/phpspreadsheet" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-client" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/simple-cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/asset-mapper" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfonycasts/sass-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/intl-extra" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stimulus-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/ux-turbo" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/brevo-mailer" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />

View File

@@ -1,7 +1,133 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="ERROR" enabled_by_default="true">
<option name="configuration">
<list>
<option value="\Codeception\Util\Debug::debug" />
<option value="\Codeception\Util\Debug::pause" />
<option value="\Doctrine\Common\Util\Debug::dump" />
<option value="\Doctrine\Common\Util\Debug::export" />
<option value="\Illuminate\Support\Debug\Dumper::dump" />
<option value="\Symfony\Component\Debug\Debug::enable" />
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
<option value="\Zend\Debug\Debug::dump" />
<option value="\Zend\Di\Display\Console::export" />
<option value="dd" />
<option value="debug_print_backtrace" />
<option value="debug_zval_dump" />
<option value="dpm" />
<option value="dpq" />
<option value="dsm" />
<option value="dump" />
<option value="dvm" />
<option value="error_log" />
<option value="kpr" />
<option value="phpinfo" />
<option value="print_r" />
<option value="trap" />
<option value="var_dump" />
<option value="var_export" />
<option value="wp_die" />
<option value="xdebug_break" />
<option value="xdebug_call_class" />
<option value="xdebug_call_file" />
<option value="xdebug_call_function" />
<option value="xdebug_call_line" />
<option value="xdebug_code_coverage_started" />
<option value="xdebug_debug_zval" />
<option value="xdebug_debug_zval_stdout" />
<option value="xdebug_dump_superglobals" />
<option value="xdebug_enable" />
<option value="xdebug_get_code_coverage" />
<option value="xdebug_get_collected_errors" />
<option value="xdebug_get_declared_vars" />
<option value="xdebug_get_function_stack" />
<option value="xdebug_get_headers" />
<option value="xdebug_get_monitored_functions" />
<option value="xdebug_get_profiler_filename" />
<option value="xdebug_get_stack_depth" />
<option value="xdebug_get_tracefile_name" />
<option value="xdebug_is_enabled" />
<option value="xdebug_memory_usage" />
<option value="xdebug_peak_memory_usage" />
<option value="xdebug_print_function_stack" />
<option value="xdebug_start_code_coverage" />
<option value="xdebug_start_error_collection" />
<option value="xdebug_start_function_monitor" />
<option value="xdebug_start_trace" />
<option value="xdebug_stop_code_coverage" />
<option value="xdebug_stop_error_collection" />
<option value="xdebug_stop_function_monitor" />
<option value="xdebug_stop_trace" />
<option value="xdebug_time_index" />
<option value="xdebug_var_dump" />
</list>
</option>
<option name="migratedIntoUserSpace" value="true" />
</inspection_tool>
<inspection_tool class="PhpCSFixerValidationInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> <inspection_tool class="PhpCSFixerValidationInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpFullyQualifiedNameUsageInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="IGNORE_GLOBAL_NAMESPACE" value="true" />
</inspection_tool>
<inspection_tool class="PhpStanGlobal" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> <inspection_tool class="PhpStanGlobal" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="optionConfiguration">
<list>
<option value="barryvdh/laravel-debugbar" />
<option value="behat/behat" />
<option value="brianium/paratest" />
<option value="codeception/codeception" />
<option value="codedungeon/phpunit-result-printer" />
<option value="composer/composer" />
<option value="doctrine/coding-standard" />
<option value="filp/whoops" />
<option value="friendsofphp/php-cs-fixer" />
<option value="humbug/humbug" />
<option value="infection/infection" />
<option value="jakub-onderka/php-parallel-lint" />
<option value="johnkary/phpunit-speedtrap" />
<option value="kalessil/production-dependencies-guard" />
<option value="mikey179/vfsStream" />
<option value="mockery/mockery" />
<option value="mybuilder/phpunit-accelerator" />
<option value="orchestra/testbench" />
<option value="pdepend/pdepend" />
<option value="phan/phan" />
<option value="phing/phing" />
<option value="phpcompatibility/php-compatibility" />
<option value="phpmd/phpmd" />
<option value="phpro/grumphp" />
<option value="phpspec/phpspec" />
<option value="phpspec/prophecy" />
<option value="phpstan/phpstan" />
<option value="phpunit/phpunit" />
<option value="povils/phpmnd" />
<option value="roave/security-advisories" />
<option value="satooshi/php-coveralls" />
<option value="sebastian/phpcpd" />
<option value="slevomat/coding-standard" />
<option value="spatie/phpunit-watcher" />
<option value="squizlabs/php_codesniffer" />
<option value="sstalle/php7cc" />
<option value="symfony/debug" />
<option value="symfony/maker-bundle" />
<option value="symfony/phpunit-bridge" />
<option value="symfony/var-dumper" />
<option value="vimeo/psalm" />
<option value="wimg/php-compatibility" />
<option value="wp-coding-standards/wpcs" />
<option value="yiisoft/yii2-coding-standards" />
<option value="yiisoft/yii2-debug" />
<option value="yiisoft/yii2-gii" />
<option value="zendframework/zend-coding-standard" />
<option value="zendframework/zend-debug" />
<option value="zendframework/zend-test" />
</list>
</option>
</inspection_tool>
</profile> </profile>
</component> </component>

View File

@@ -5,7 +5,7 @@
<tool tool_name="PHPUnit"> <tool tool_name="PHPUnit">
<cache> <cache>
<versions> <versions>
<info id="interpreter-c1266788-d465-407a-ac5d-1f67a9cf3e8a" version="12.1.2" /> <info id="interpreter-96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" version="12.1.2" />
</versions> </versions>
</cache> </cache>
</tool> </tool>

324
.idea/php.xml generated
View File

@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="MessDetector"> <component name="LaravelPint">
<phpmd_settings> <laravel_pint_settings>
<phpmd_by_interpreter asDefaultInterpreter="true" interpreter_id="c1266788-d465-407a-ac5d-1f67a9cf3e8a" timeout="30000" /> <laravel_pint_by_interpreter asDefaultInterpreter="true" interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8">
</phpmd_settings> <option name="timeout" value="30000" />
</laravel_pint_by_interpreter>
</laravel_pint_settings>
</component> </component>
<component name="MessDetectorOptionsConfiguration"> <component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" /> <option name="transferred" value="true" />
@@ -19,14 +21,14 @@
</component> </component>
<component name="PhpCSFixer"> <component name="PhpCSFixer">
<phpcsfixer_settings> <phpcsfixer_settings>
<phpcs_fixer_by_interpreter interpreter_id="c1266788-d465-407a-ac5d-1f67a9cf3e8a" standards="DoctrineAnnotation;PER;PER-CS;PER-CS1.0;PER-CS2.0;PHP54Migration;PHP56Migration;PHP70Migration;PHP71Migration;PHP73Migration;PHP74Migration;PHP80Migration;PHP81Migration;PHP82Migration;PHP83Migration;PHP84Migration;PHPUnit100Migration;PHPUnit30Migration;PHPUnit32Migration;PHPUnit35Migration;PHPUnit43Migration;PHPUnit48Migration;PHPUnit50Migration;PHPUnit52Migration;PHPUnit54Migration;PHPUnit55Migration;PHPUnit56Migration;PHPUnit57Migration;PHPUnit60Migration;PHPUnit75Migration;PHPUnit84Migration;PHPUnit91Migration;PSR1;PSR12;PSR2;PhpCsFixer;Symfony" tool_path="/app/vendor/bin/php-cs-fixer" timeout="30000" /> <phpcs_fixer_by_interpreter interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" standards="DoctrineAnnotation;PER;PER-CS;PER-CS1.0;PER-CS2.0;PHP54Migration;PHP56Migration;PHP70Migration;PHP71Migration;PHP73Migration;PHP74Migration;PHP80Migration;PHP81Migration;PHP82Migration;PHP83Migration;PHP84Migration;PHPUnit100Migration;PHPUnit30Migration;PHPUnit32Migration;PHPUnit35Migration;PHPUnit43Migration;PHPUnit48Migration;PHPUnit50Migration;PHPUnit52Migration;PHPUnit54Migration;PHPUnit55Migration;PHPUnit56Migration;PHPUnit57Migration;PHPUnit60Migration;PHPUnit75Migration;PHPUnit84Migration;PHPUnit91Migration;PSR1;PSR12;PSR2;PhpCsFixer;Symfony" tool_path="vendor/bin/php-cs-fixer" timeout="30000" />
<phpcs_fixer_by_interpreter asDefaultInterpreter="true" deletedFromTheList="true" interpreter_id="c1266788-d465-407a-ac5d-1f67a9cf3e8a" standards="DoctrineAnnotation;PER;PER-CS;PER-CS1.0;PER-CS2.0;PHP54Migration;PHP56Migration;PHP70Migration;PHP71Migration;PHP73Migration;PHP74Migration;PHP80Migration;PHP81Migration;PHP82Migration;PHP83Migration;PHP84Migration;PHPUnit100Migration;PHPUnit30Migration;PHPUnit32Migration;PHPUnit35Migration;PHPUnit43Migration;PHPUnit48Migration;PHPUnit50Migration;PHPUnit52Migration;PHPUnit54Migration;PHPUnit55Migration;PHPUnit56Migration;PHPUnit57Migration;PHPUnit60Migration;PHPUnit75Migration;PHPUnit84Migration;PHPUnit91Migration;PSR1;PSR12;PSR2;PhpCsFixer;Symfony" tool_path="/app/vendor/bin/php-cs-fixer" timeout="30000" /> <PhpCSFixerConfiguration deletedFromTheList="true" standards="PSR1;PSR2;Symfony;DoctrineAnnotation;PHP70Migration;PHP71Migration" tool_path="$PROJECT_DIR$/vendor/bin/php-cs-fixer" />
<PhpCSFixerConfiguration deletedFromTheList="true" tool_path="$PROJECT_DIR$/vendor/bin/php-cs-fixer" /> <phpcs_fixer_by_interpreter asDefaultInterpreter="true" deletedFromTheList="true" interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" standards="DoctrineAnnotation;PER;PER-CS;PER-CS1.0;PER-CS2.0;PHP54Migration;PHP56Migration;PHP70Migration;PHP71Migration;PHP73Migration;PHP74Migration;PHP80Migration;PHP81Migration;PHP82Migration;PHP83Migration;PHP84Migration;PHPUnit100Migration;PHPUnit30Migration;PHPUnit32Migration;PHPUnit35Migration;PHPUnit43Migration;PHPUnit48Migration;PHPUnit50Migration;PHPUnit52Migration;PHPUnit54Migration;PHPUnit55Migration;PHPUnit56Migration;PHPUnit57Migration;PHPUnit60Migration;PHPUnit75Migration;PHPUnit84Migration;PHPUnit91Migration;PSR1;PSR12;PSR2;PhpCsFixer;Symfony" tool_path="vendor/bin/php-cs-fixer" timeout="30000" />
</phpcsfixer_settings> </phpcsfixer_settings>
</component> </component>
<component name="PhpCodeSniffer"> <component name="PhpCodeSniffer">
<phpcs_settings> <phpcs_settings>
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="c1266788-d465-407a-ac5d-1f67a9cf3e8a" timeout="30000" /> <phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" timeout="30000" />
</phpcs_settings> </phpcs_settings>
</component> </component>
<component name="PhpExternalFormatter"> <component name="PhpExternalFormatter">
@@ -34,154 +36,173 @@
</component> </component>
<component name="PhpIncludePathManager"> <component name="PhpIncludePathManager">
<include_path> <include_path>
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" /> <path value="$PROJECT_DIR$/vendor/react/stream" />
<path value="$PROJECT_DIR$/vendor/symfony/string" /> <path value="$PROJECT_DIR$/vendor/react/promise" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/runtime/frankenphp-symfony" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/flex" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/migrations" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/vendor/doctrine/sql-formatter" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/rector/rector" />
<path value="$PROJECT_DIR$/vendor/react/event-loop" /> <path value="$PROJECT_DIR$/vendor/react/event-loop" />
<path value="$PROJECT_DIR$/vendor/react/cache" /> <path value="$PROJECT_DIR$/vendor/react/cache" />
<path value="$PROJECT_DIR$/vendor/react/stream" />
<path value="$PROJECT_DIR$/vendor/clue/ndjson-react" />
<path value="$PROJECT_DIR$/vendor/react/promise" />
<path value="$PROJECT_DIR$/vendor/react/dns" />
<path value="$PROJECT_DIR$/vendor/friendsofphp/php-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/react/socket" />
<path value="$PROJECT_DIR$/vendor/react/child-process" /> <path value="$PROJECT_DIR$/vendor/react/child-process" />
<path value="$PROJECT_DIR$/vendor/evenement/evenement" /> <path value="$PROJECT_DIR$/vendor/jean85/pretty-package-versions" />
<path value="$PROJECT_DIR$/vendor/react/dns" />
<path value="$PROJECT_DIR$/vendor/react/socket" />
<path value="$PROJECT_DIR$/vendor/sentry/sentry" />
<path value="$PROJECT_DIR$/vendor/staabm/side-effects-detector" />
<path value="$PROJECT_DIR$/vendor/rector/rector" />
<path value="$PROJECT_DIR$/vendor/sentry/sentry-symfony" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/egulias/email-validator" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan-symfony" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan-phpunit" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan-doctrine" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/phpstan/extension-installer" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpdoc-parser" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-icu" />
<path value="$PROJECT_DIR$/vendor/symfony/form" />
<path value="$PROJECT_DIR$/vendor/runtime/frankenphp-symfony" />
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/validator" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/serializer" />
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
<path value="$PROJECT_DIR$/vendor/symfony/security-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/intl" />
<path value="$PROJECT_DIR$/vendor/symfony/asset" />
<path value="$PROJECT_DIR$/vendor/symfony/browser-kit" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
<path value="$PROJECT_DIR$/vendor/symfony/uid" />
<path value="$PROJECT_DIR$/vendor/symfony/asset-mapper" />
<path value="$PROJECT_DIR$/vendor/symfony/mailer" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/symfony/phpunit-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" /> <path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
<path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" /> <path value="$PROJECT_DIR$/vendor/symfony/security-csrf" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/css-selector" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/web-profiler-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/property-access" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/dom-crawler" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/symfony/mime" />
<path value="$PROJECT_DIR$/vendor/symfony/psr-http-message-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/security-core" />
<path value="$PROJECT_DIR$/vendor/symfony/property-info" />
<path value="$PROJECT_DIR$/vendor/symfony/type-info" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/symfony/flex" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/ux-twig-component" />
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/doctrine/migrations" />
<path value="$PROJECT_DIR$/vendor/doctrine/data-fixtures" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/vendor/doctrine/sql-formatter" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/easycorp/easyadmin-bundle" />
<path value="$PROJECT_DIR$/vendor/evenement/evenement" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-bundle" />
<path value="$PROJECT_DIR$/vendor/markbaker/matrix" />
<path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
<path value="$PROJECT_DIR$/vendor/maennchen/zipstream-php" />
<path value="$PROJECT_DIR$/vendor/markbaker/complex" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" /> <path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" /> <path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" /> <path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" /> <path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" /> <path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" /> <path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/staabm/side-effects-detector" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/symfony/uid" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/security-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/type-info" />
<path value="$PROJECT_DIR$/vendor/symfony/property-access" />
<path value="$PROJECT_DIR$/vendor/symfony/property-info" />
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/security-csrf" />
<path value="$PROJECT_DIR$/vendor/symfony/security-core" />
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
<path value="$PROJECT_DIR$/vendor/symfony/web-profiler-bundle" />
<path value="$PROJECT_DIR$/vendor/thecodingmachine/safe" />
<path value="$PROJECT_DIR$/vendor/phpstan/extension-installer" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan-doctrine" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan-phpunit" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan-symfony" />
<path value="$PROJECT_DIR$/vendor/symfony/asset" />
<path value="$PROJECT_DIR$/vendor/thecodingmachine/phpstan-safe-rule" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-icu" />
<path value="$PROJECT_DIR$/vendor/symfony/form" />
<path value="$PROJECT_DIR$/vendor/twig/html-extra" />
<path value="$PROJECT_DIR$/vendor/twig/extra-bundle" />
<path value="$PROJECT_DIR$/vendor/easycorp/easyadmin-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/mime" />
<path value="$PROJECT_DIR$/vendor/symfony/validator" />
<path value="$PROJECT_DIR$/vendor/symfony/ux-twig-component" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
<path value="$PROJECT_DIR$/vendor/symfony/intl" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/doctrine/data-fixtures" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
<path value="$PROJECT_DIR$/vendor/webmozart/assert" /> <path value="$PROJECT_DIR$/vendor/webmozart/assert" />
<path value="$PROJECT_DIR$/vendor/vincentlanglet/twig-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php84" />
<path value="$PROJECT_DIR$/vendor/symfony/browser-kit" />
<path value="$PROJECT_DIR$/vendor/symfony/dom-crawler" />
<path value="$PROJECT_DIR$/vendor/masterminds/html5" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php83" />
<path value="$PROJECT_DIR$/vendor/egulias/email-validator" />
<path value="$PROJECT_DIR$/vendor/symfony/mailer" />
<path value="$PROJECT_DIR$/vendor/symfonycasts/verify-email-bundle" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" /> <path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" /> <path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/jean85/pretty-package-versions" /> <path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/psr/http-factory" /> <path value="$PROJECT_DIR$/vendor/symfonycasts/verify-email-bundle" />
<path value="$PROJECT_DIR$/vendor/symfonycasts/sass-bundle" />
<path value="$PROJECT_DIR$/vendor/masterminds/html5" />
<path value="$PROJECT_DIR$/vendor/friendsofphp/php-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-docblock" />
<path value="$PROJECT_DIR$/vendor/vincentlanglet/twig-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
<path value="$PROJECT_DIR$/vendor/thecodingmachine/safe" />
<path value="$PROJECT_DIR$/vendor/thecodingmachine/phpstan-safe-rule" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/psr/http-message" /> <path value="$PROJECT_DIR$/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/vendor/sentry/sentry-symfony" /> <path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/sentry/sentry" /> <path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/psr-http-message-bridge" /> <path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/twig/extra-bundle" />
<path value="$PROJECT_DIR$/vendor/twig/intl-extra" />
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
<path value="$PROJECT_DIR$/vendor/clue/ndjson-react" />
<path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/twig/html-extra" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/symfony/ux-turbo" />
<path value="$PROJECT_DIR$/vendor/symfony/stimulus-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php84" />
<path value="$PROJECT_DIR$/vendor/symfony/brevo-mailer" />
</include_path> </include_path>
</component> </component>
<component name="PhpInterpreters"> <component name="PhpInterpreters">
<interpreters> <interpreters>
<interpreter id="c1266788-d465-407a-ac5d-1f67a9cf3e8a" name="php" home="docker-compose://DATA" auto="false" debugger_id="php.debugger.XDebug"> <interpreter id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" name="Compose PHP 8.4" home="docker-compose://DATA" auto="false" debugger_id="php.debugger.XDebug">
<remote_data INTERPRETER_PATH="php" HELPERS_PATH="/opt/.phpstorm_helpers" VALID="true" RUN_AS_ROOT_VIA_SUDO="false" DOCKER_ACCOUNT_NAME="Colima" DOCKER_COMPOSE_SERVICE_NAME="php" DOCKER_REMOTE_PROJECT_PATH="/opt/project"> <remote_data INTERPRETER_PATH="php" HELPERS_PATH="/opt/.phpstorm_helpers" VALID="true" RUN_AS_ROOT_VIA_SUDO="false" DOCKER_ACCOUNT_NAME="Colima" DOCKER_COMPOSE_SERVICE_NAME="php" DOCKER_REMOTE_PROJECT_PATH="/opt/project">
<type_data command="EXEC" /> <type_data command="EXEC" />
<dockerComposeConfigurationPaths> <dockerComposeConfigurationPaths>
@@ -195,15 +216,15 @@
</component> </component>
<component name="PhpInterpretersPhpInfoCache"> <component name="PhpInterpretersPhpInfoCache">
<phpInfoCache> <phpInfoCache>
<interpreter name="php"> <interpreter name="Compose PHP 8.4">
<phpinfo binary_type="PHP" php_cgi="/usr/local/bin/php-cgi" php_cli="/usr/local/bin/php" path_separator=":" version="8.3.15"> <phpinfo binary_type="PHP" php_cgi="/usr/local/bin/php-cgi" php_cli="/usr/local/bin/php" path_separator=":" version="8.4.8">
<additional_php_ini>/usr/local/etc/php/conf.d/docker-php-ext-apcu.ini, /usr/local/etc/php/conf.d/docker-php-ext-intl.ini, /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini, /usr/local/etc/php/conf.d/docker-php-ext-pdo_pgsql.ini, /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini, /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini, /usr/local/etc/php/conf.d/docker-php-ext-zip.ini, /usr/local/etc/php/app.conf.d/10-app.ini, /usr/local/etc/php/app.conf.d/20-app.dev.ini</additional_php_ini> <additional_php_ini>/usr/local/etc/php/conf.d/docker-php-ext-apcu.ini, /usr/local/etc/php/conf.d/docker-php-ext-excimer.ini, /usr/local/etc/php/conf.d/docker-php-ext-gd.ini, /usr/local/etc/php/conf.d/docker-php-ext-intl.ini, /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini, /usr/local/etc/php/conf.d/docker-php-ext-pdo_pgsql.ini, /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini, /usr/local/etc/php/conf.d/docker-php-ext-uuid.ini, /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini, /usr/local/etc/php/conf.d/docker-php-ext-zip.ini, /usr/local/etc/php/app.conf.d/10-app.ini, /usr/local/etc/php/app.conf.d/20-app.dev.ini</additional_php_ini>
<configuration_file>/usr/local/etc/php/php.ini</configuration_file> <configuration_file>/usr/local/etc/php/php.ini</configuration_file>
<configuration_options> <configuration_options>
<configuration_option name="include_path" value=".:/usr/local/lib/php" /> <configuration_option name="include_path" value=".:/usr/local/lib/php" />
</configuration_options> </configuration_options>
<debuggers> <debuggers>
<debugger_info debugger="xdebug" debugger_version="3.4.0"> <debugger_info debugger="xdebug" debugger_version="3.4.3">
<debug_extensions /> <debug_extensions />
</debugger_info> </debugger_info>
</debuggers> </debuggers>
@@ -220,8 +241,10 @@
<extension name="curl" /> <extension name="curl" />
<extension name="date" /> <extension name="date" />
<extension name="dom" /> <extension name="dom" />
<extension name="excimer" />
<extension name="fileinfo" /> <extension name="fileinfo" />
<extension name="filter" /> <extension name="filter" />
<extension name="gd" />
<extension name="hash" /> <extension name="hash" />
<extension name="iconv" /> <extension name="iconv" />
<extension name="intl" /> <extension name="intl" />
@@ -241,6 +264,7 @@
<extension name="sqlite3" /> <extension name="sqlite3" />
<extension name="standard" /> <extension name="standard" />
<extension name="tokenizer" /> <extension name="tokenizer" />
<extension name="uuid" />
<extension name="xdebug" /> <extension name="xdebug" />
<extension name="xml" /> <extension name="xml" />
<extension name="xmlreader" /> <extension name="xmlreader" />
@@ -252,27 +276,27 @@
</interpreter> </interpreter>
</phpInfoCache> </phpInfoCache>
</component> </component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.3" /> <component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
<component name="PhpStan"> <component name="PhpStan">
<PhpStan_settings> <PhpStan_settings>
<phpstan_by_interpreter interpreter_id="c1266788-d465-407a-ac5d-1f67a9cf3e8a" tool_path="/app/vendor/bin/phpstan" timeout="60000" /> <phpstan_by_interpreter interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" tool_path="vendor/bin/phpstan" timeout="60000" />
<phpstan_by_interpreter asDefaultInterpreter="true" deletedFromTheList="true" interpreter_id="afbe50d9-a569-498c-8dcc-cf68a5d73813" timeout="60000" />
<PhpStanConfiguration deletedFromTheList="true" tool_path="$PROJECT_DIR$/vendor/bin/phpstan" /> <PhpStanConfiguration deletedFromTheList="true" tool_path="$PROJECT_DIR$/vendor/bin/phpstan" />
<phpstan_by_interpreter asDefaultInterpreter="true" deletedFromTheList="true" interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" tool_path="vendor/bin/phpstan" timeout="60000" />
</PhpStan_settings> </PhpStan_settings>
</component> </component>
<component name="PhpStanOptionsConfiguration"> <component name="PhpStanOptionsConfiguration">
<option name="config" value="$PROJECT_DIR$/phpstan.dist.neon" /> <option name="config" value="phpstan.dist.neon" />
<option name="level" value="8" /> <option name="fullProject" value="true" />
<option name="transferred" value="true" /> <option name="transferred" value="true" />
</component> </component>
<component name="PhpUnit"> <component name="PhpUnit">
<phpunit_settings> <phpunit_settings>
<phpunit_by_interpreter interpreter_id="c1266788-d465-407a-ac5d-1f67a9cf3e8a" configuration_file_path="phpunit.xml" custom_loader_path="/app/vendor/autoload.php" phpunit_phar_path="" use_configuration_file="true" /> <phpunit_by_interpreter interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" configuration_file_path="phpunit.xml.dist" custom_loader_path="vendor/autoload.php" phpunit_phar_path="" use_configuration_file="true" />
</phpunit_settings> </phpunit_settings>
</component> </component>
<component name="Psalm"> <component name="Psalm">
<Psalm_settings> <Psalm_settings>
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="afbe50d9-a569-498c-8dcc-cf68a5d73813" timeout="60000" /> <psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" timeout="60000" />
</Psalm_settings> </Psalm_settings>
</component> </component>
<component name="PsalmOptionsConfiguration"> <component name="PsalmOptionsConfiguration">

10
.idea/phpunit.xml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPUnit">
<option name="directories">
<list>
<option value="$PROJECT_DIR$/tests" />
</list>
</option>
</component>
</project>

16
.idea/remote-mappings.xml generated Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteMappingsManager">
<list>
<list>
<remote-mappings server-id="php@96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8">
<settings>
<list>
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
</list>
</settings>
</remote-mappings>
</list>
</list>
</component>
</project>

1
.idea/symfony2.xml generated
View File

@@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="Symfony2PluginSettings"> <component name="Symfony2PluginSettings">
<option name="pluginEnabled" value="true" /> <option name="pluginEnabled" value="true" />
<option name="profilerCsvPath" value="" />
</component> </component>
</project> </project>

View File

@@ -8,6 +8,7 @@ use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
$finder = (new Finder()) $finder = (new Finder())
->in(__DIR__) ->in(__DIR__)
->exclude('var') ->exclude('var')
->exclude('bin')
; ;
return (new Config()) return (new Config())
@@ -29,6 +30,8 @@ return (new Config())
'single_line_empty_body' => true, 'single_line_empty_body' => true,
'strict_comparison' => true, 'strict_comparison' => true,
'strict_param' => true, 'strict_param' => true,
'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'array_destructuring', 'arrays', 'match', 'parameters']],
]) ])
->setRiskyAllowed(true) ->setRiskyAllowed(true)
->setFinder($finder) ->setFinder($finder)

View File

@@ -1,7 +1,7 @@
#syntax=docker/dockerfile:1 #syntax=docker/dockerfile:1
# Versions # Versions
FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
# The different stages of this Dockerfile are meant to be built into separate images # The different stages of this Dockerfile are meant to be built into separate images
# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage # https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage
@@ -31,6 +31,8 @@ RUN set -eux; \
intl \ intl \
opcache \ opcache \
zip \ zip \
gd \
excimer-1.2.3 \
; ;
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser # https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
@@ -96,4 +98,7 @@ RUN set -eux; \
composer dump-autoload --classmap-authoritative --no-dev; \ composer dump-autoload --classmap-authoritative --no-dev; \
composer dump-env prod; \ composer dump-env prod; \
composer run-script --no-dev post-install-cmd; \ composer run-script --no-dev post-install-cmd; \
chmod +x bin/console; sync; chmod +x bin/console; \
bin/console sass:build; \
bin/console asset-map:compile --no-debug --quiet --no-ansi; \
sync;

View File

@@ -14,6 +14,8 @@ exec *args:
shell: shell:
@docker compose exec php bash @docker compose exec php bash
bash: shell
migrate: up migrate: up
docker compose run --rm php bin/console doctrine:migrations:migrate --no-interaction docker compose run --rm php bin/console doctrine:migrations:migrate --no-interaction
@@ -21,7 +23,7 @@ fixtures:
docker compose exec php bin/console doctrine:fixtures:load --purge-with-truncate --no-interaction docker compose exec php bin/console doctrine:fixtures:load --purge-with-truncate --no-interaction
translations: translations:
docker compose exec php bin/console translation:extract --domain=messages --force --format=yaml --sort=asc --clean nl docker compose exec php bin/console translation:extract --force --format=xliff --sort=asc --clean nl
fix-cs: fix-cs:
docker compose exec php vendor/bin/php-cs-fixer fix docker compose exec php vendor/bin/php-cs-fixer fix
@@ -32,3 +34,8 @@ rector *args:
phpstan *args: phpstan *args:
docker compose exec php vendor/bin/phpstan analyse {{ args }} docker compose exec php vendor/bin/phpstan analyse {{ args }}
[confirm]
clean:
docker compose down -v --remove-orphans
rm -rf vendor var assets/vendor public/assets public/bundles .php-cs-fixer.cache .twig-cs-fixer.cache

5
assets/backoffice.js Normal file
View File

@@ -0,0 +1,5 @@
import * as bootstrap from 'bootstrap'
import './bootstrap.js';
import 'bootstrap/dist/css/bootstrap.min.css'
import './styles/backoffice.scss';

5
assets/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,5 @@
import { startStimulusApp } from '@symfony/stimulus-bundle';
const app = startStimulusApp();
// register any custom, 3rd party controllers here
// app.register('some_controller_name', SomeImportedController);

15
assets/controllers.json Normal file
View File

@@ -0,0 +1,15 @@
{
"controllers": {
"@symfony/ux-turbo": {
"turbo-core": {
"enabled": false,
"fetch": "eager"
},
"mercure-turbo-stream": {
"enabled": false,
"fetch": "eager"
}
}
},
"entrypoints": []
}

View File

@@ -0,0 +1,17 @@
import {Controller} from '@hotwired/stimulus';
import * as bootstrap from 'bootstrap'
export default class extends Controller {
connect() {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
}
clearQuiz() {
new bootstrap.Modal('#clearQuizModal').show();
}
deleteQuiz() {
new bootstrap.Modal('#deleteQuizModal').show();
}
}

View File

@@ -0,0 +1,79 @@
const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
const tokenCheck = /^[-_\/+a-zA-Z0-9]{24,}$/;
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
document.addEventListener('submit', function (event) {
generateCsrfToken(event.target);
}, true);
// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
document.addEventListener('turbo:submit-start', function (event) {
const h = generateCsrfHeaders(event.detail.formSubmission.formElement);
Object.keys(h).map(function (k) {
event.detail.formSubmission.fetchRequest.headers[k] = h[k];
});
});
// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
document.addEventListener('turbo:submit-end', function (event) {
removeCsrfToken(event.detail.formSubmission.formElement);
});
export function generateCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return;
}
let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
let csrfToken = csrfField.value;
if (!csrfCookie && nameCheck.test(csrfToken)) {
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
}
if (csrfCookie && tokenCheck.test(csrfToken)) {
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}
export function generateCsrfHeaders (formElement) {
const headers = {};
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return headers;
}
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
headers[csrfCookie] = csrfField.value;
}
return headers;
}
export function removeCsrfToken (formElement) {
const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]');
if (!csrfField) {
return;
}
const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
}
/* stimulusFetch: 'lazy' */
export default 'csrf-protection-controller';

View File

@@ -0,0 +1,10 @@
import {Controller} from '@hotwired/stimulus';
export default class extends Controller {
next() {
const currentUrl = window.location.href;
const urlParts = currentUrl.split('/');
urlParts.pop();
window.location.href = urlParts.join('/');
}
}

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

BIN
assets/img/green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

BIN
assets/img/red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

6
assets/quiz.js Normal file
View File

@@ -0,0 +1,6 @@
import './bootstrap.js';
import 'bootstrap/dist/css/bootstrap.min.css'
import * as bootstrap from 'bootstrap'
import './styles/app.scss'

26
assets/styles/app.scss Normal file
View File

@@ -0,0 +1,26 @@
html, body {
height: 100%;
background-image: url("../img/background.png");
background-position: center center;
background-repeat: no-repeat;
background-color: black;
color: white;
display: grid;
align-items: center;
justify-self: center;
}
.elimination-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
object-fit: contain;
background-color: white;
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}

View File

View File

@@ -3,7 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
use App\Kernel; use Tvdt\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_dir(dirname(__DIR__).'/vendor')) { if (!is_dir(dirname(__DIR__).'/vendor')) {

View File

@@ -9,11 +9,13 @@ services:
- ./frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro - ./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 - ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro
- ./frankenphp/data:/data - ./frankenphp/data:/data
- sass:/app/var/sass
environment: environment:
MERCURE_EXTRA_DIRECTIVES: demo MERCURE_EXTRA_DIRECTIVES: demo
# See https://xdebug.org/docs/all_settings#mode # See https://xdebug.org/docs/all_settings#mode
XDEBUG_MODE: "${XDEBUG_MODE:-off}" XDEBUG_MODE: "${XDEBUG_MODE:-off}"
MAILER_DSN: "smtp://mailer:1025" MAILER_DSN: "smtp://mailer:1025"
PHP_CS_FIXER_IGNORE_ENV: 1
extra_hosts: extra_hosts:
# Ensure that host.docker.internal is correctly defined on Linux # Ensure that host.docker.internal is correctly defined on Linux
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
@@ -31,6 +33,19 @@ services:
- target: 443 - target: 443
published: ${HTTP3_PORT:-443} published: ${HTTP3_PORT:-443}
protocol: udp protocol: udp
sass:
image: ${IMAGES_PREFIX:-}app-php
volumes:
- ./:/app:ro
- sass:/app/var/sass
entrypoint: ''
depends_on:
- php
command:
- bin/console
- sass:build
- --watch
- -v
###> symfony/mercure-bundle ### ###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ### ###< symfony/mercure-bundle ###
@@ -51,3 +66,6 @@ services:
MP_SMTP_AUTH_ACCEPT_ANY: 1 MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1 MP_SMTP_AUTH_ALLOW_INSECURE: 1
###< symfony/mailer ### ###< symfony/mailer ###
volumes:
sass:

View File

@@ -8,6 +8,9 @@ services:
APP_SECRET: ${APP_SECRET} APP_SECRET: ${APP_SECRET}
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET} MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}
MAILER_DSN: ${MAILER_DSN}
MAILER_SENDER: ${MAILER_SENDER}
SENTRY_DSN: ${SENTRY_DSN}
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.tvdt.rule=Host(`tijdvoordetest.nl`)" - "traefik.http.routers.tvdt.rule=Host(`tijdvoordetest.nl`)"
@@ -21,6 +24,8 @@ services:
database: database:
networks: networks:
- internal - internal
ports:
- "5430:5432"
networks: networks:
web: web:
external: true external: true

View File

@@ -12,9 +12,6 @@ services:
MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure} MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
MERCURE_PUBLIC_URL: ${CADDY_MERCURE_PUBLIC_URL:-https://${SERVER_NAME:-localhost}/.well-known/mercure} MERCURE_PUBLIC_URL: ${CADDY_MERCURE_PUBLIC_URL:-https://${SERVER_NAME:-localhost}/.well-known/mercure}
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!} MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
# The two next lines can be removed after initial installation
SYMFONY_VERSION: ${SYMFONY_VERSION:-}
STABILITY: ${STABILITY:-stable}
volumes: volumes:
- caddy_data:/data - caddy_data:/data
- caddy_config:/config - caddy_config:/config
@@ -38,6 +35,7 @@ services:
start_period: 60s start_period: 60s
volumes: volumes:
- database_data:/var/lib/postgresql/data:rw - database_data:/var/lib/postgresql/data:rw
restart: always
###< doctrine/doctrine-bundle ### ###< doctrine/doctrine-bundle ###
volumes: volumes:

View File

@@ -6,49 +6,64 @@
"minimum-stability": "stable", "minimum-stability": "stable",
"prefer-stable": true, "prefer-stable": true,
"require": { "require": {
"php": ">=8.3.15", "php": ">=8.4",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"doctrine/dbal": "^4.2.3", "doctrine/dbal": "^4.3.3",
"doctrine/doctrine-bundle": "^2.14.0", "doctrine/doctrine-bundle": "^2.16.2",
"doctrine/doctrine-fixtures-bundle": "^4.1", "doctrine/doctrine-migrations-bundle": "^3.4.2",
"doctrine/doctrine-migrations-bundle": "^3.4.1", "doctrine/orm": "^3.5.2",
"doctrine/orm": "^3.3.2", "easycorp/easyadmin-bundle": "^4.25.1",
"easycorp/easyadmin-bundle": "^4.24.6", "phpdocumentor/reflection-docblock": "^5.6.3",
"phpoffice/phpspreadsheet": "^5.1",
"phpstan/phpdoc-parser": "^2.3",
"runtime/frankenphp-symfony": "^0.2.0", "runtime/frankenphp-symfony": "^0.2.0",
"sentry/sentry-symfony": "^5.2", "sentry/sentry-symfony": "^5.6",
"symfony/asset": "7.2.*", "symfony/asset": "7.3.*",
"symfony/console": "7.2.*", "symfony/asset-mapper": "7.3.*",
"symfony/dotenv": "7.2.*", "symfony/brevo-mailer": "7.3.*",
"symfony/flex": "^2.5.0", "symfony/console": "7.3.*",
"symfony/form": "7.2.*", "symfony/dotenv": "7.3.*",
"symfony/framework-bundle": "7.2.*", "symfony/flex": "^2.8.2",
"symfony/mailer": "7.2.*", "symfony/form": "7.3.*",
"symfony/runtime": "7.2.*", "symfony/framework-bundle": "7.3.*",
"symfony/security-bundle": "7.2.*", "symfony/mailer": "7.3.*",
"symfony/security-csrf": "7.2.*", "symfony/property-access": "7.3.*",
"symfony/twig-bundle": "7.2.*", "symfony/property-info": "7.3.*",
"symfony/uid": "7.2.*", "symfony/runtime": "7.3.*",
"symfony/yaml": "7.2.*", "symfony/security-bundle": "7.3.*",
"symfonycasts/verify-email-bundle": "^1.17.3", "symfony/security-csrf": "7.3.*",
"thecodingmachine/safe": "^3.1.0" "symfony/serializer": "7.3.*",
"symfony/twig-bundle": "7.3.*",
"symfony/uid": "7.3.*",
"symfony/ux-turbo": "^2.30.0",
"symfony/yaml": "7.3.*",
"symfonycasts/sass-bundle": "^0.8.3",
"symfonycasts/verify-email-bundle": "^1.17.4",
"thecodingmachine/safe": "^3.3.0",
"twig/extra-bundle": "^3.21",
"twig/intl-extra": "^3.21",
"twig/twig": "^3.21.1"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^3.75.0", "doctrine/doctrine-fixtures-bundle": "^4.1",
"friendsofphp/php-cs-fixer": "^3.88.2",
"phpstan/extension-installer": "^1.4.3", "phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.1.12", "phpstan/phpstan": "^2.1.29",
"phpstan/phpstan-doctrine": "^2.0.2", "phpstan/phpstan-doctrine": "^2.0.6",
"phpstan/phpstan-phpunit": "^2.0.6", "phpstan/phpstan-phpunit": "^2.0.7",
"phpstan/phpstan-symfony": "^2.0.4", "phpstan/phpstan-symfony": "^2.0.8",
"phpunit/phpunit": "^12.1.2", "phpunit/phpunit": "^12.3.15",
"rector/rector": "^2.0.11", "rector/rector": "^2.1.7",
"roave/security-advisories": "dev-latest", "roave/security-advisories": "dev-latest",
"symfony/browser-kit": "7.2.*", "symfony/browser-kit": "7.3.*",
"symfony/maker-bundle": "^1.62.1", "symfony/css-selector": "7.3.*",
"symfony/stopwatch": "7.2.*", "symfony/maker-bundle": "^1.64.0",
"symfony/web-profiler-bundle": "7.2.*", "symfony/phpunit-bridge": "7.3.*",
"thecodingmachine/phpstan-safe-rule": "^1.4", "symfony/stopwatch": "7.3.*",
"vincentlanglet/twig-cs-fixer": "^3.5.1" "symfony/web-profiler-bundle": "7.3.*",
"thecodingmachine/phpstan-safe-rule": "^1.4.1",
"vincentlanglet/twig-cs-fixer": "^3.10.0"
}, },
"config": { "config": {
"allow-plugins": { "allow-plugins": {
@@ -62,28 +77,32 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"App\\": "src/" "Tvdt\\": "src/"
} }
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"App\\Tests\\": "tests/" "Tvdt\\Tests\\": "tests/"
} }
}, },
"replace": { "replace": {
"symfony/polyfill-ctype": "*", "symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*", "symfony/polyfill-iconv": "*",
"symfony/polyfill-mbstring": "*",
"symfony/polyfill-php72": "*", "symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*", "symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*", "symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*", "symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*", "symfony/polyfill-php81": "*",
"symfony/polyfill-php82": "*" "symfony/polyfill-php82": "*",
"symfony/polyfill-php83": "*",
"symfony/polyfill-php84": "*"
}, },
"scripts": { "scripts": {
"auto-scripts": { "auto-scripts": {
"cache:clear": "symfony-cmd", "cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd" "assets:install %PUBLIC_DIR%": "symfony-cmd",
"importmap:install": "symfony-cmd"
}, },
"post-install-cmd": [ "post-install-cmd": [
"@auto-scripts" "@auto-scripts"
@@ -98,7 +117,7 @@
"extra": { "extra": {
"symfony": { "symfony": {
"allow-contrib": false, "allow-contrib": false,
"require": "7.2.*", "require": "7.3.*",
"docker": true "docker": true
} }
} }

4441
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,8 +12,11 @@ use Symfony\Bundle\MakerBundle\MakerBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle; use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle; use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
use Symfony\UX\StimulusBundle\StimulusBundle;
use Symfony\UX\Turbo\TurboBundle;
use Symfony\UX\TwigComponent\TwigComponentBundle; use Symfony\UX\TwigComponent\TwigComponentBundle;
use SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle; use SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle;
use Symfonycasts\SassBundle\SymfonycastsSassBundle;
use Twig\Extra\TwigExtraBundle\TwigExtraBundle; use Twig\Extra\TwigExtraBundle\TwigExtraBundle;
return [ return [
@@ -30,4 +33,7 @@ return [
DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
SymfonyCastsVerifyEmailBundle::class => ['all' => true], SymfonyCastsVerifyEmailBundle::class => ['all' => true],
SentryBundle::class => ['prod' => true], SentryBundle::class => ['prod' => true],
SymfonycastsSassBundle::class => ['all' => true],
StimulusBundle::class => ['all' => true],
TurboBundle::class => ['all' => true],
]; ];

View File

@@ -0,0 +1,15 @@
framework:
asset_mapper:
# The paths to make available to the asset mapper.
paths:
- assets/
excluded_patterns:
- '*/assets/styles/_*.scss'
- '*/assets/styles/**/_*.scss'
missing_import_mode: strict
when@prod:
framework:
asset_mapper:
missing_import_mode: warn

View File

@@ -18,12 +18,14 @@ doctrine:
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
auto_mapping: true auto_mapping: true
mappings: mappings:
App: Tvdt:
type: attribute type: attribute
is_bundle: false is_bundle: false
dir: '%kernel.project_dir%/src/Entity' dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity' prefix: 'Tvdt\Entity'
alias: App alias: Tvdt
controller_resolver:
auto_mapping: false
when@test: when@test:
doctrine: doctrine:

View File

@@ -1,6 +1,6 @@
doctrine_migrations: doctrine_migrations:
migrations_paths: migrations_paths:
# namespace is arbitrary but should be different from App\Migrations # namespace is arbitrary but should be different from Tvdt\Migrations
# as migrations classes should NOT be autoloaded # as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations' 'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false enable_profiler: false

View File

@@ -9,6 +9,12 @@ framework:
enabled: true enabled: true
#esi: true #esi: true
#fragments: true #fragments: true
when@prod:
framework:
# shortcut for private IP address ranges of your proxy
trusted_proxies: 'private_ranges'
# or, if your proxy instead uses the "Forwarded" header
trusted_headers: [ 'forwarded' ]
when@test: when@test:
framework: framework:

View File

@@ -0,0 +1,3 @@
framework:
property_info:
with_constructor_extractor: true

View File

@@ -5,9 +5,9 @@ security:
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers: providers:
# used to reload user from session & other features (e.g. switch_user) # used to reload user from session & other features (e.g. switch_user)
app_user_provider: tvdt_user_provider:
entity: entity:
class: App\Entity\User class: Tvdt\Entity\User
property: email property: email
# used to reload user from session & other features (e.g. switch_user) # used to reload user from session & other features (e.g. switch_user)
firewalls: firewalls:
@@ -16,16 +16,16 @@ security:
security: false security: false
main: main:
lazy: true lazy: true
provider: app_user_provider provider: tvdt_user_provider
form_login: form_login:
login_path: app_login_login login_path: tvdt_login_login
check_path: app_login_login check_path: tvdt_login_login
enable_csrf: true enable_csrf: true
default_target_path: app_backoffice_index default_target_path: tvdt_backoffice_index
logout: logout:
path: app_login_logout path: tvdt_login_logout
# where to redirect after logout # where to redirect after logout
# target: app_any_route # target: tvdt_any_route
# activate different ways to authenticate # activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall # https://symfony.com/doc/current/security.html#the-firewall
@@ -37,7 +37,7 @@ security:
# Note: Only the *first* access control that matches will be used # Note: Only the *first* access control that matches will be used
access_control: access_control:
- { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER } - { path: ^/backoffice, roles: ROLE_USER }
when@test: when@test:
security: security:

View File

@@ -4,7 +4,10 @@ when@prod:
# Add request headers, cookies, IP address and the authenticated user # Add request headers, cookies, IP address and the authenticated user
# see https://docs.sentry.io/platforms/php/data-management/data-collected/ for more info # see https://docs.sentry.io/platforms/php/data-management/data-collected/ for more info
# send_default_pii: true # send_default_pii: true
options: options:
traces_sample_rate: 1.0
profiles_sample_rate: 1.0
ignore_exceptions: ignore_exceptions:
- 'Symfony\Component\ErrorHandler\Error\FatalError' - 'Symfony\Component\ErrorHandler\Error\FatalError'
- 'Symfony\Component\Debug\Exception\FatalErrorException' - 'Symfony\Component\Debug\Exception\FatalErrorException'

View File

@@ -2,4 +2,4 @@ twig_component:
anonymous_template_directory: 'components/' anonymous_template_directory: 'components/'
defaults: defaults:
# Namespace & directory for components # Namespace & directory for components
App\Twig\Components\: 'components/' Tvdt\Twig\Components\: 'components/'

View File

@@ -3,7 +3,7 @@ framework:
# Enables validator auto-mapping support. # Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata. # For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping: #auto_mapping:
# App\Entity\: [] # Tvdt\Entity\: []
when@test: when@test:
framework: framework:

View File

@@ -1,5 +1,5 @@
controllers: controllers:
resource: resource:
path: ../src/Controller/ path: ../src/Controller/
namespace: App\Controller namespace: Tvdt\Controller
type: attribute type: attribute

View File

@@ -1,9 +1,9 @@
controllers: controllers:
resource: resource:
path: ../../src/Controller/ path: ../../src/Controller/
namespace: App\Controller namespace: Tvdt\Controller
type: attribute type: attribute
kernel: kernel:
resource: App\Kernel resource: Tvdt\Kernel
type: attribute type: attribute

View File

@@ -1,4 +1,4 @@
when@dev: when@dev:
_errors: _errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml' resource: '@FrameworkBundle/Resources/config/routing/errors.php'
prefix: /_error prefix: /_error

View File

@@ -1,8 +1,8 @@
when@dev: when@dev:
web_profiler_wdt: web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
prefix: /_wdt prefix: /_wdt
web_profiler_profiler: web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
prefix: /_profiler prefix: /_profiler

View File

@@ -13,7 +13,7 @@ services:
# makes classes in src/ available to be used as services # makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name # this creates a service per class whose id is the fully-qualified class name
App\: Tvdt\:
resource: '../src/' resource: '../src/'
exclude: exclude:
- '../src/DependencyInjection/' - '../src/DependencyInjection/'

View File

@@ -3,12 +3,6 @@
frankenphp { frankenphp {
{$FRANKENPHP_CONFIG} {$FRANKENPHP_CONFIG}
worker {
file ./public/index.php
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
{$FRANKENPHP_WORKER_CONFIG}
}
} }
} }
@@ -29,6 +23,8 @@
encode zstd br gzip encode zstd br gzip
mercure { mercure {
# Transport to use (default to Bolt)
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
# Publisher JWT key # Publisher JWT key
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
# Subscriber JWT key # Subscriber JWT key
@@ -57,7 +53,5 @@
@frontController path index.php @frontController path index.php
php @frontController php @frontController
file_server { file_server
hide *.php
}
} }

44
importmap.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* Returns the importmap for this application.
*
* - "path" is a path inside the asset mapper system. Use the
* "debug:asset-map" command to see the full list of paths.
*
* - "entrypoint" (JavaScript only) set to true for any module that will
* be used as an "entrypoint" (and passed to the importmap() Twig function).
*
* The "importmap:require" command can be used to add new entries to this file.
*/
return [
'quiz' => [
'path' => './assets/quiz.js',
'entrypoint' => true,
],
'backoffice' => [
'path' => './assets/backoffice.js',
'entrypoint' => true,
],
'bootstrap' => [
'version' => '5.3.6',
],
'@popperjs/core' => [
'version' => '2.11.8',
],
'bootstrap/dist/css/bootstrap.min.css' => [
'version' => '5.3.6',
'type' => 'css',
],
'@hotwired/stimulus' => [
'version' => '3.2.2',
],
'@symfony/stimulus-bundle' => [
'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
],
'@hotwired/turbo' => [
'version' => '7.3.0',
],
];

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250427174822 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add ordering to question and answer';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE answer ADD ordering SMALLINT DEFAULT 0 NOT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE question ADD ordering SMALLINT DEFAULT 0 NOT NULL
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE answer DROP ordering
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE question DROP ordering
SQL);
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250504101440 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_C8B28E445E237E064EC001D1 ON candidate (name, season_id)
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_A412FA925E237E064EC001D1 ON quiz (name, season_id)
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_A412FA925E237E064EC001D1
SQL);
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_C8B28E445E237E064EC001D1
SQL);
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250521192752 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD quiz_id UUID NOT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD CONSTRAINT FK_5947284F853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_5947284F853CD175 ON elimination (quiz_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz ALTER dropouts SET DEFAULT 1
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz ALTER dropouts SET NOT NULL
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE quiz ALTER dropouts DROP DEFAULT
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz ALTER dropouts DROP NOT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP CONSTRAINT FK_5947284F853CD175
SQL);
$this->addSql(<<<'SQL'
DROP INDEX IDX_5947284F853CD175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP quiz_id
SQL);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250521194035 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD created TIMESTAMP(0) WITHOUT TIME ZONE
SQL);
$this->addSql(<<<'SQL'
UPDATE elimination SET created = NOW()
SQL
);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ALTER created SET NOT NULL
SQL
);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP created
SQL);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250606192337 extends AbstractMigration
{
public function getDescription(): string
{
return 'Ze Big migration';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE quiz_candidate (id UUID NOT NULL, corrections DOUBLE PRECISION NOT NULL, created TIMESTAMP(0) WITH TIME ZONE NOT NULL, quiz_id UUID NOT NULL, candidate_id UUID NOT NULL, PRIMARY KEY(id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_CED2FFA2853CD175 ON quiz_candidate (quiz_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_CED2FFA291BD8781 ON quiz_candidate (candidate_id)
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_CED2FFA291BD8781853CD175 ON quiz_candidate (candidate_id, quiz_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz_candidate ADD CONSTRAINT FK_CED2FFA2853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz_candidate ADD CONSTRAINT FK_CED2FFA291BD8781 FOREIGN KEY (candidate_id) REFERENCES candidate (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE correction DROP CONSTRAINT fk_a29da1b891bd8781
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE correction DROP CONSTRAINT fk_a29da1b8853cd175
SQL);
$this->addSql(<<<'SQL'
DROP TABLE correction
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ALTER created TYPE TIMESTAMP(0) WITH TIME ZONE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER created TYPE TIMESTAMP(0) WITH TIME ZONE
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE correction (id UUID NOT NULL, candidate_id UUID NOT NULL, quiz_id UUID NOT NULL, amount DOUBLE PRECISION NOT NULL, PRIMARY KEY(id))
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX uniq_a29da1b891bd8781853cd175 ON correction (candidate_id, quiz_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX idx_a29da1b8853cd175 ON correction (quiz_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX idx_a29da1b891bd8781 ON correction (candidate_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE correction ADD CONSTRAINT fk_a29da1b891bd8781 FOREIGN KEY (candidate_id) REFERENCES candidate (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE correction ADD CONSTRAINT fk_a29da1b8853cd175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz_candidate DROP CONSTRAINT FK_CED2FFA2853CD175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz_candidate DROP CONSTRAINT FK_CED2FFA291BD8781
SQL);
$this->addSql(<<<'SQL'
DROP TABLE quiz_candidate
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER created TYPE TIMESTAMP(0) WITHOUT TIME ZONE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ALTER created TYPE TIMESTAMP(0) WITHOUT TIME ZONE
SQL);
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250606195952 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
delete from given_answer where answer_id is null
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER answer_id TYPE UUID
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER answer_id SET NOT NULL
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER answer_id TYPE UUID
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER answer_id DROP NOT NULL
SQL);
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250607154730 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE season DROP CONSTRAINT FK_F0E45BA96706D6B
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD CONSTRAINT FK_F0E45BA96706D6B FOREIGN KEY (active_quiz_id) REFERENCES quiz (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE season DROP CONSTRAINT fk_f0e45ba96706d6b
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD CONSTRAINT fk_f0e45ba96706d6b FOREIGN KEY (active_quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250607184525 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP CONSTRAINT FK_5947284F853CD175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD CONSTRAINT FK_5947284F853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer DROP CONSTRAINT FK_9AC61A30853CD175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ADD CONSTRAINT FK_9AC61A30853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE given_answer DROP CONSTRAINT fk_9ac61a30853cd175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ADD CONSTRAINT fk_9ac61a30853cd175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP CONSTRAINT fk_5947284f853cd175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD CONSTRAINT fk_5947284f853cd175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250610210417 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE season_settings (id UUID NOT NULL, show_numbers BOOLEAN DEFAULT false NOT NULL, confirm_answers BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(id))
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD settings_id UUID DEFAULT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD CONSTRAINT FK_F0E45BA959949888 FOREIGN KEY (settings_id) REFERENCES season_settings (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_F0E45BA959949888 ON season (settings_id)
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
DROP TABLE season_settings
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season DROP CONSTRAINT FK_F0E45BA959949888
SQL);
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_F0E45BA959949888
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season DROP settings_id
SQL);
}
}

View File

@@ -1,4 +1,5 @@
parameters: parameters:
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'
level: 8 level: 8
paths: paths:
- bin/ - bin/
@@ -6,3 +7,4 @@ parameters:
- public/ - public/
- src/ - src/
- tests/ - tests/
treatPhpDocTypesAsCertain: false

34
phpunit.dist.xml Normal file
View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
failOnNotice="true"
failOnWarning="true"
bootstrap="tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="display_errors" value="1" />
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<server name="SHELL_VERBOSITY" value="-1" />
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<source ignoreSuppressionOfDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
<extensions>
</extensions>
</phpunit>

View File

@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
use App\Kernel; use Tvdt\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

View File

@@ -11,6 +11,8 @@ return RectorConfig::configure()
__DIR__.'/src', __DIR__.'/src',
__DIR__.'/tests', __DIR__.'/tests',
]) ])
->withSymfonyContainerXml('var/cache/dev/App_KernelDevDebugContainer.xml')
->withParallel()
->withPhpSets() ->withPhpSets()
->withPreparedSets( ->withPreparedSets(
deadCode: true, deadCode: true,
@@ -28,5 +30,4 @@ return RectorConfig::configure()
) )
->withAttributesSets(all: true) ->withAttributesSets(all: true)
->withComposerBased(twig: true, doctrine: true, phpunit: true, symfony: true) ->withComposerBased(twig: true, doctrine: true, phpunit: true, symfony: true)
->withAttributesSets()
; ;

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Tvdt\Command;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Tvdt\Entity\SeasonSettings;
use Tvdt\Repository\SeasonRepository;
#[AsCommand(
name: 'tvdt:add-settings',
description: 'Add a short description for your command',
)]
readonly class AddSettingsCommand
{
public function __construct(private SeasonRepository $seasonRepository, private EntityManagerInterface $entityManager) {}
public function __invoke(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
foreach ($this->seasonRepository->findAll() as $season) {
if (null !== $season->getSettings()) {
continue;
}
$io->text('Adding settings to season : '.$season->getSeasonCode());
$season->setSettings(new SeasonSettings());
}
$this->entityManager->flush();
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Tvdt\Command;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Tvdt\Repository\SeasonRepository;
use Tvdt\Repository\UserRepository;
#[AsCommand(
name: 'tvdt:claim-season',
description: 'Give a user owner rights on a season',
)]
readonly class ClaimSeasonCommand
{
public function __construct(private UserRepository $userRepository, private SeasonRepository $seasonRepository, private EntityManagerInterface $entityManager) {}
public function __invoke(
#[Argument]
string $seasonCode,
#[Argument]
string $email,
InputInterface $input,
OutputInterface $output,
): int {
$io = new SymfonyStyle($input, $output);
try {
$season = $this->seasonRepository->findOneBy(['seasonCode' => $seasonCode]);
if (null === $season) {
throw new \InvalidArgumentException('Season not found');
}
$user = $this->userRepository->findOneBy(['email' => $email]);
if (null === $user) {
throw new \InvalidArgumentException('User not found');
}
$season->addOwner($user);
$this->entityManager->flush();
} catch (\InvalidArgumentException $invalidArgumentException) {
$io->error($invalidArgumentException->getMessage());
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Tvdt\Command;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Tvdt\Repository\UserRepository;
#[AsCommand(
name: 'tvdt:make-admin',
description: 'Give a user the role admin',
)]
readonly class MakeAdminCommand
{
public function __construct(private UserRepository $userRepository) {}
public function __invoke(
#[Argument]
string $email,
InputInterface $input,
OutputInterface $output,
): int {
$io = new SymfonyStyle($input, $output);
try {
$this->userRepository->makeAdmin($email);
} catch (\InvalidArgumentException) {
$io->error('User not found');
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

View File

@@ -2,13 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller; namespace Tvdt\Controller;
use App\Enum\FlashType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as AbstractBaseController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as AbstractBaseController;
use Tvdt\Enum\FlashType;
abstract class AbstractController extends AbstractBaseController abstract class AbstractController extends AbstractBaseController
{ {
protected const string SEASON_CODE_REGEX = '[A-Za-z\d]{5}';
protected const string CANDIDATE_HASH_REGEX = '[\w\-=]+';
#[\Override] #[\Override]
protected function addFlash(FlashType|string $type, mixed $message): void protected function addFlash(FlashType|string $type, mixed $message): void
{ {

View File

@@ -2,29 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Admin; namespace Tvdt\Controller\Admin;
use App\Entity\Answer;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use Tvdt\Entity\Answer;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
/** @extends AbstractCrudController<Answer> */
class AnswerCrudController extends AbstractCrudController class AnswerCrudController extends AbstractCrudController
{ {
public static function getEntityFqcn(): string public static function getEntityFqcn(): string
{ {
return Answer::class; return Answer::class;
} }
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
} }

View File

@@ -2,29 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Admin; namespace Tvdt\Controller\Admin;
use App\Entity\Candidate;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use Tvdt\Entity\Candidate;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
/** @extends AbstractCrudController<Candidate> */
class CandidateCrudController extends AbstractCrudController class CandidateCrudController extends AbstractCrudController
{ {
public static function getEntityFqcn(): string public static function getEntityFqcn(): string
{ {
return Candidate::class; return Candidate::class;
} }
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
} }

View File

@@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Correction;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class CorrectionCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Correction::class;
}
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
}

View File

@@ -2,31 +2,29 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Admin; namespace Tvdt\Controller\Admin;
use App\Entity\Answer; use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard;
use App\Entity\Candidate;
use App\Entity\Correction;
use App\Entity\GivenAnswer;
use App\Entity\Question;
use App\Entity\Quiz;
use App\Entity\Season;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard; use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem; use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Tvdt\Entity\Answer;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\GivenAnswer;
use Tvdt\Entity\Question;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\QuizCandidate;
use Tvdt\Entity\Season;
use Tvdt\Entity\User;
#[AdminDashboard(routePath: '/admin', routeName: 'admin')]
class DashboardController extends AbstractDashboardController class DashboardController extends AbstractDashboardController
{ {
#[Route('/admin', name: 'admin')]
#[\Override] #[\Override]
public function index(): Response public function index(): Response
{ {
// return parent::index();
// Option 1. You can make your dashboard redirect to some common page of your backend // Option 1. You can make your dashboard redirect to some common page of your backend
// //
$adminUrlGenerator = $this->container->get(AdminUrlGenerator::class); $adminUrlGenerator = $this->container->get(AdminUrlGenerator::class);
@@ -60,10 +58,10 @@ class DashboardController extends AbstractDashboardController
yield MenuItem::linkToCrud('Quiz', 'fas fa-list', Quiz::class); yield MenuItem::linkToCrud('Quiz', 'fas fa-list', Quiz::class);
yield MenuItem::linkToCrud('Question', 'fas fa-list', Question::class); yield MenuItem::linkToCrud('Question', 'fas fa-list', Question::class);
yield MenuItem::linkToCrud('Candidate', 'fas fa-list', Candidate::class); yield MenuItem::linkToCrud('Candidate', 'fas fa-list', Candidate::class);
yield MenuItem::linkToCrud('Correction', 'fas fa-list', Correction::class); yield MenuItem::linkToCrud('Correction', 'fas fa-list', QuizCandidate::class);
yield MenuItem::linkToCrud('User', 'fas fa-list', User::class); yield MenuItem::linkToCrud('User', 'fas fa-list', User::class);
yield MenuItem::linkToCrud('Given Answer', 'fas fa-list', GivenAnswer::class); yield MenuItem::linkToCrud('Given Answer', 'fas fa-list', GivenAnswer::class);
yield MenuItem::linkToCrud('Answer', 'fas fa-list', Answer::class); yield MenuItem::linkToCrud('Answer', 'fas fa-list', Answer::class);
yield MenuItem::linkToLogout('Logout', 'fa fa-exit'); yield MenuItem::linkToLogout('Logout', 'fas fa-sign-out');
} }
} }

View File

@@ -2,29 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Admin; namespace Tvdt\Controller\Admin;
use App\Entity\GivenAnswer;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use Tvdt\Entity\GivenAnswer;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
/** @extends AbstractCrudController<GivenAnswer> */
class GivenAnswerCrudController extends AbstractCrudController class GivenAnswerCrudController extends AbstractCrudController
{ {
public static function getEntityFqcn(): string public static function getEntityFqcn(): string
{ {
return GivenAnswer::class; return GivenAnswer::class;
} }
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
} }

View File

@@ -2,29 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Admin; namespace Tvdt\Controller\Admin;
use App\Entity\Question;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use Tvdt\Entity\Question;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
/** @extends AbstractCrudController<Question> */
class QuestionCrudController extends AbstractCrudController class QuestionCrudController extends AbstractCrudController
{ {
public static function getEntityFqcn(): string public static function getEntityFqcn(): string
{ {
return Question::class; return Question::class;
} }
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
} }

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\QuizCandidate;
/** @extends AbstractCrudController<QuizCandidate> */
class QuizCorrectionCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return QuizCandidate::class;
}
}

View File

@@ -2,29 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Admin; namespace Tvdt\Controller\Admin;
use App\Entity\Quiz;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use Tvdt\Entity\Quiz;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
/** @extends AbstractCrudController<Quiz> */
class QuizCrudController extends AbstractCrudController class QuizCrudController extends AbstractCrudController
{ {
public static function getEntityFqcn(): string public static function getEntityFqcn(): string
{ {
return Quiz::class; return Quiz::class;
} }
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
} }

View File

@@ -2,29 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Admin; namespace Tvdt\Controller\Admin;
use App\Entity\Season;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use Tvdt\Entity\Season;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
/** @extends AbstractCrudController<Season> */
class SeasonCrudController extends AbstractCrudController class SeasonCrudController extends AbstractCrudController
{ {
public static function getEntityFqcn(): string public static function getEntityFqcn(): string
{ {
return Season::class; return Season::class;
} }
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
} }

View File

@@ -2,29 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Admin; namespace Tvdt\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use Tvdt\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
/** @extends AbstractCrudController<User> */
class UserCrudController extends AbstractCrudController class UserCrudController extends AbstractCrudController
{ {
public static function getEntityFqcn(): string public static function getEntityFqcn(): string
{ {
return User::class; return User::class;
} }
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
} }

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Tvdt\Controller\AbstractController;
use Tvdt\Entity\Season;
use Tvdt\Entity\User;
use Tvdt\Form\CreateSeasonFormType;
use Tvdt\Repository\SeasonRepository;
use Tvdt\Service\QuizSpreadsheetService;
#[AsController]
#[IsGranted('ROLE_USER')]
final class BackofficeController extends AbstractController
{
public function __construct(
private readonly SeasonRepository $seasonRepository,
private readonly Security $security,
) {}
#[Route('/backoffice/', name: 'tvdt_backoffice_index')]
public function index(): Response
{
$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,
]);
}
#[Route('/backoffice/season/add', name: 'tvdt_backoffice_season_add', priority: 10)]
public function addSeason(Request $request, EntityManagerInterface $em): Response
{
$season = new Season();
$form = $this->createForm(CreateSeasonFormType::class, $season);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $this->getUser();
\assert($user instanceof User);
$season->addOwner($user);
$season->generateSeasonCode();
$em->persist($season);
$em->flush();
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
}
return $this->render('backoffice/season_add.html.twig', ['form' => $form]);
}
#[Route('/backoffice/template', name: 'tvdt_backoffice_template', priority: 10)]
public function getTemplate(QuizSpreadsheetService $excel): Response
{
$response = new StreamedResponse($excel->generateTemplate());
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$response->headers->set('Content-Disposition', 'attachment; filename="template.xlsx"');
return $response;
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;
use Tvdt\Controller\AbstractController;
use Tvdt\Entity\Elimination;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
use Tvdt\Factory\EliminationFactory;
final class PrepareEliminationController extends AbstractController
{
#[Route(
'/backoffice/season/{seasonCode:season}/quiz/{quiz}/elimination/prepare',
name: 'tvdt_prepare_elimination',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID],
)]
public function index(Season $season, Quiz $quiz, EliminationFactory $eliminationFactory): Response
{
$elimination = $eliminationFactory->createEliminationFromQuiz($quiz);
return $this->redirectToRoute('tvdt_prepare_elimination_view', ['elimination' => $elimination->getId()]);
}
#[Route(
'/backoffice/elimination/{elimination}',
name: 'tvdt_prepare_elimination_view',
requirements: ['elimination' => Requirement::UUID],
)]
public function viewElimination(Elimination $elimination, Request $request, EntityManagerInterface $em): Response
{
if ('POST' === $request->getMethod()) {
$elimination->updateFromInputBag($request->request);
$em->flush();
if ($request->request->getBoolean('start')) {
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->getId()]);
}
$this->addFlash('success', 'Elimination updated');
}
return $this->render('backoffice/prepare_elimination/index.html.twig', [
'controller_name' => 'PrepareEliminationController',
'elimination' => $elimination,
]);
}
}

View File

@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;
use Tvdt\Controller\AbstractController;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
use Tvdt\Exception\ErrorClearingQuizException;
use Tvdt\Repository\CandidateRepository;
use Tvdt\Repository\QuizCandidateRepository;
use Tvdt\Repository\QuizRepository;
use Tvdt\Security\Voter\SeasonVoter;
#[AsController]
#[IsGranted('ROLE_USER')]
class QuizController extends AbstractController
{
public function __construct(
private readonly CandidateRepository $candidateRepository,
private readonly TranslatorInterface $translator,
) {}
#[Route(
'/backoffice/season/{seasonCode:season}/quiz/{quiz}',
name: 'tvdt_backoffice_quiz',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function index(Season $season, Quiz $quiz): Response
{
return $this->render('backoffice/quiz.html.twig', [
'season' => $season,
'quiz' => $quiz,
'result' => $this->candidateRepository->getScores($quiz),
]);
}
#[Route(
'/backoffice/season/{seasonCode:season}/quiz/{quiz}/enable',
name: 'tvdt_backoffice_enable',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID.'|null'],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function enableQuiz(Season $season, ?Quiz $quiz, EntityManagerInterface $em): RedirectResponse
{
$season->setActiveQuiz($quiz);
$em->flush();
if ($quiz instanceof Quiz) {
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $season->getSeasonCode(), 'quiz' => $quiz->getId()]);
}
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
}
#[Route(
'/backoffice/quiz/{quiz}/clear',
name: 'tvdt_backoffice_quiz_clear',
requirements: ['quiz' => Requirement::UUID],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
public function clearQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse
{
try {
$quizRepository->clearQuiz($quiz);
$this->addFlash('success', $this->translator->trans('Quiz cleared'));
} catch (ErrorClearingQuizException) {
$this->addFlash('error', $this->translator->trans('Error clearing quiz'));
}
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->getSeason()->getSeasonCode(), 'quiz' => $quiz->getId()]);
}
#[Route(
'/backoffice/quiz/{quiz}/delete',
name: 'tvdt_backoffice_quiz_delete',
requirements: ['quiz' => Requirement::UUID],
)]
#[IsGranted(SeasonVoter::DELETE, subject: 'quiz')]
public function deleteQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse
{
$quizRepository->deleteQuiz($quiz);
$this->addFlash('success', $this->translator->trans('Quiz deleted'));
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $quiz->getSeason()->getSeasonCode()]);
}
#[Route(
'/backoffice/quiz/{quiz}/candidate/{candidate}/modify_correction',
name: 'tvdt_backoffice_modify_correction',
requirements: ['quiz' => Requirement::UUID, 'candidate' => Requirement::UUID],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
public function modifyCorrection(Quiz $quiz, Candidate $candidate, QuizCandidateRepository $quizCandidateRepository, Request $request): RedirectResponse
{
if (!$request->isMethod('POST')) {
throw new MethodNotAllowedHttpException(['POST']);
}
$corrections = (float) $request->request->get('corrections');
$quizCandidateRepository->setCorrectionsForCandidate($quiz, $candidate, $corrections);
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->getSeason()->getSeasonCode(), 'quiz' => $quiz->getId()]);
}
}

View File

@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Backoffice;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;
use Tvdt\Controller\AbstractController;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
use Tvdt\Enum\FlashType;
use Tvdt\Form\AddCandidatesFormType;
use Tvdt\Form\SettingsForm;
use Tvdt\Form\UploadQuizFormType;
use Tvdt\Security\Voter\SeasonVoter;
use Tvdt\Service\QuizSpreadsheetService;
#[AsController]
#[IsGranted('ROLE_USER')]
class SeasonController extends AbstractController
{
public function __construct(
private readonly TranslatorInterface $translator,
private readonly EntityManagerInterface $em,
) {}
#[Route(
'/backoffice/season/{seasonCode:season}',
name: 'tvdt_backoffice_season',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function index(Season $season, Request $request): Response
{
$form = $this->createForm(SettingsForm::class, $season->getSettings());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->em->flush();
}
return $this->render('backoffice/season.html.twig', [
'season' => $season,
'form' => $form,
]);
}
#[Route(
'/backoffice/season/{seasonCode:season}/add-candidate',
name: 'tvdt_backoffice_add_candidates',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
priority: 10,
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function addCandidates(Season $season, Request $request): Response
{
$form = $this->createForm(AddCandidatesFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$candidates = $form->get('candidates')->getData();
foreach (explode("\n", (string) $candidates) as $candidate) {
$season->addCandidate(new Candidate(mb_rtrim($candidate)));
}
$this->em->flush();
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
}
return $this->render('backoffice/season_add_candidates.html.twig', ['form' => $form]);
}
#[Route(
'/backoffice/season/{seasonCode:season}/add-quiz',
name: 'tvdt_backoffice_quiz_add',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
priority: 10,
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function addQuiz(Request $request, Season $season, QuizSpreadsheetService $quizSpreadsheet): Response
{
$quiz = new Quiz();
$form = $this->createForm(UploadQuizFormType::class, $quiz);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/* @var UploadedFile $sheet */
$sheet = $form->get('sheet')->getData();
$quizSpreadsheet->xlsxToQuiz($quiz, $sheet);
$quiz->setSeason($season);
$this->em->persist($quiz);
$this->em->flush();
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz Added!'));
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
}
return $this->render('/backoffice/quiz_add.html.twig', ['form' => $form, 'season' => $season]);
}
}

View File

@@ -1,62 +0,0 @@
<?php
declare(strict_types=1);
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
{
$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,
]);
}
#[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', [
'season' => $season,
]);
}
#[Route('/backoffice/{seasonCode}/{quiz}', name: 'app_backoffice_quiz')]
public function quiz(Season $season, Quiz $quiz): Response
{
return $this->render('backoffice/quiz.html.twig', [
'season' => $season,
'quiz' => $quiz,
'result' => $this->candidateRepository->getScores($quiz),
]);
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\Elimination;
use Tvdt\Enum\FlashType;
use Tvdt\Form\EliminationEnterNameType;
use Tvdt\Helpers\Base64;
use Tvdt\Repository\CandidateRepository;
use Tvdt\Security\Voter\SeasonVoter;
use function Symfony\Component\Translation\t;
#[AsController]
#[IsGranted('ROLE_USER')]
final class EliminationController extends AbstractController
{
public function __construct(private readonly TranslatorInterface $translator) {}
#[Route('/elimination/{elimination}', name: 'tvdt_elimination', requirements: ['elimination' => Requirement::UUID])]
#[IsGranted(SeasonVoter::ELIMINATION, 'elimination')]
public function index(#[MapEntity] Elimination $elimination, Request $request): Response
{
$form = $this->createForm(EliminationEnterNameType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$name = $form->get('name')->getData();
return $this->redirectToRoute('tvdt_elimination_candidate', ['elimination' => $elimination->getId(), 'candidateHash' => Base64::base64UrlEncode($name)]);
}
return $this->render('quiz/elimination/index.html.twig', [
'form' => $form,
'controller_name' => 'EliminationController',
]);
}
#[Route('/elimination/{elimination}/{candidateHash}', name: 'tvdt_elimination_candidate', requirements: ['elimination' => Requirement::UUID, 'candidateHash' => self::CANDIDATE_HASH_REGEX])]
#[IsGranted(SeasonVoter::ELIMINATION, 'elimination')]
public function candidateScreen(Elimination $elimination, string $candidateHash, CandidateRepository $candidateRepository): Response
{
$candidate = $candidateRepository->getCandidateByHash($elimination->getQuiz()->getSeason(), $candidateHash);
if (!$candidate instanceof Candidate) {
$this->addFlash(FlashType::Warning,
t('Cound not find candidate with name %name%', ['%name%' => Base64::base64UrlDecode($candidateHash)])->trans($this->translator),
);
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->getId()]);
}
$screenColour = $elimination->getScreenColour($candidate->getName());
if (null === $screenColour) {
$this->addFlash(FlashType::Warning, $this->translator->trans('Cound not find candidate with name %name% in elimination.', ['%name%' => $candidate->getName()]));
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->getId()]);
}
return $this->render('quiz/elimination/candidate.html.twig', [
'candidate' => $candidate,
'colour' => $screenColour,
]);
}
}

View File

@@ -2,19 +2,21 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller; namespace Tvdt\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Contracts\Translation\TranslatorInterface;
use Tvdt\Enum\FlashType;
#[AsController] #[AsController]
final class LoginController extends AbstractController final class LoginController extends AbstractController
{ {
#[Route(path: '/login', name: 'app_login_login')] #[Route(path: '/login', name: 'tvdt_login_login')]
public function login(AuthenticationUtils $authenticationUtils): Response public function login(AuthenticationUtils $authenticationUtils, TranslatorInterface $translator): Response
{ {
// get the login error if there is one // get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError(); $error = $authenticationUtils->getLastAuthenticationError();
@@ -22,13 +24,17 @@ final class LoginController extends AbstractController
// last username entered by the user // last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername(); $lastUsername = $authenticationUtils->getLastUsername();
return $this->render('login/login.html.twig', [ if ($error instanceof AuthenticationException) {
$this->addFlash(FlashType::Danger, $translator->trans($error->getMessageKey(), $error->getMessageData(), 'security'));
}
return $this->render('backoffice/login/login.html.twig', [
'last_username' => $lastUsername, 'last_username' => $lastUsername,
'error' => $error, 'error' => $error,
]); ]);
} }
#[Route(path: '/logout', name: 'app_login_logout')] #[Route(path: '/logout', name: 'tvdt_login_logout')]
public function logout(): never public function logout(): never
{ {
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');

View File

@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class PrepareEliminationController extends AbstractController
{
#[Route('/backoffice/elimination/prepare', name: 'app_prepare_elimination')]
public function index(): Response
{
return $this->render('prepare_elimination/index.html.twig', [
'controller_name' => 'PrepareEliminationController',
]);
}
}

View File

@@ -2,65 +2,60 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller; namespace Tvdt\Controller;
use App\Entity\Answer;
use App\Entity\Candidate;
use App\Entity\GivenAnswer;
use App\Entity\Question;
use App\Entity\Season;
use App\Enum\FlashType;
use App\Form\EnterNameType;
use App\Form\SelectSeasonType;
use App\Helpers\Base64;
use App\Repository\AnswerRepository;
use App\Repository\CandidateRepository;
use App\Repository\GivenAnswerRepository;
use App\Repository\QuestionRepository;
use App\Repository\SeasonRepository;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Tvdt\Entity\Answer;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\GivenAnswer;
use Tvdt\Entity\Question;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
use Tvdt\Enum\FlashType;
use Tvdt\Form\EnterNameType;
use Tvdt\Form\SelectSeasonType;
use Tvdt\Helpers\Base64;
use Tvdt\Repository\AnswerRepository;
use Tvdt\Repository\CandidateRepository;
use Tvdt\Repository\GivenAnswerRepository;
use Tvdt\Repository\QuestionRepository;
use Tvdt\Repository\QuizCandidateRepository;
use Tvdt\Repository\SeasonRepository;
#[AsController] #[AsController]
final class QuizController extends AbstractController final class QuizController extends AbstractController
{ {
public const string SEASON_CODE_REGEX = '[A-Za-z\d]{5}';
private const string CANDIDATE_HASH_REGEX = '[\w\-=]+';
public function __construct(private readonly TranslatorInterface $translator) {} public function __construct(private readonly TranslatorInterface $translator) {}
#[Route(path: '/', name: 'app_quiz_selectseason', methods: ['GET', 'POST'])] #[Route(path: '/', name: 'tvdt_quiz_select_season', methods: ['GET', 'POST'])]
public function selectSeason(Request $request, SeasonRepository $seasonRepository): Response public function selectSeason(Request $request, SeasonRepository $seasonRepository): Response
{ {
$form = $this->createForm(SelectSeasonType::class); $form = $this->createForm(SelectSeasonType::class);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData(); $seasonCode = $form->get('season_code')->getData();
$season_code = $data['season_code'];
if ([] === $seasonRepository->findBy(['seasonCode' => $season_code])) { if ([] === $seasonRepository->findBy(['seasonCode' => $seasonCode])) {
$this->addFlash(FlashType::Warning, $this->translator->trans('Invalid season code')); $this->addFlash(FlashType::Warning, $this->translator->trans('Invalid season code'));
return $this->redirectToRoute('app_quiz_selectseason'); return $this->redirectToRoute('tvdt_quiz_select_season');
} }
return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season_code]); return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $seasonCode]);
} }
return $this->render('quiz/select_season.html.twig', ['form' => $form]); return $this->render('quiz/select_season.html.twig', ['form' => $form]);
} }
#[Route(path: '/{seasonCode}', name: 'app_quiz_entername', requirements: ['seasonCode' => self::SEASON_CODE_REGEX])] #[Route(path: '/{seasonCode:season}', name: 'tvdt_quiz_enter_name', requirements: ['seasonCode' => self::SEASON_CODE_REGEX])]
public function enterName( public function enterName(
Request $request, Request $request,
#[MapEntity(mapping: ['seasonCode' => 'seasonCode'])]
Season $season, Season $season,
): Response { ): Response {
$form = $this->createForm(EnterNameType::class); $form = $this->createForm(EnterNameType::class);
@@ -68,50 +63,56 @@ final class QuizController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData(); $name = $form->get('name')->getData();
$name = $data['name'];
return $this->redirectToRoute('app_quiz_quizpage', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => Base64::base64UrlEncode($name)]); return $this->redirectToRoute('tvdt_quiz_quiz_page', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => Base64::base64UrlEncode($name)]);
} }
return $this->render('quiz/enter_name.twig', ['season' => $season, 'form' => $form]); return $this->render('quiz/enter_name.twig', ['season' => $season, 'form' => $form]);
} }
#[Route( #[Route(
path: '/{seasonCode}/{nameHash}', path: '/{seasonCode:season}/{nameHash}',
name: 'app_quiz_quizpage', name: 'tvdt_quiz_quiz_page',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX], requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX],
)] )]
public function quizPage( public function quizPage(
#[MapEntity(mapping: ['seasonCode' => 'seasonCode'])]
Season $season, Season $season,
string $nameHash, string $nameHash,
Request $request,
CandidateRepository $candidateRepository, CandidateRepository $candidateRepository,
QuestionRepository $questionRepository, QuestionRepository $questionRepository,
AnswerRepository $answerRepository, AnswerRepository $answerRepository,
GivenAnswerRepository $givenAnswerRepository, GivenAnswerRepository $givenAnswerRepository,
Request $request, QuizCandidateRepository $quizCandidateRepository,
): Response { ): Response {
$candidate = $candidateRepository->getCandidateByHash($season, $nameHash); $candidate = $candidateRepository->getCandidateByHash($season, $nameHash);
if (!$candidate instanceof Candidate) { if (!$candidate instanceof Candidate) {
$this->addFlash(FlashType::Danger, $this->translator->trans('Candidate not found')); $this->addFlash(FlashType::Danger, $this->translator->trans('Candidate not found'));
return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season->getSeasonCode()]); return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
}
$quiz = $season->getActiveQuiz();
if (!$quiz instanceof Quiz) {
$this->addFlash(FlashType::Warning, $this->translator->trans('There is no active quiz'));
return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
} }
if ('POST' === $request->getMethod()) { if ('POST' === $request->getMethod()) {
$answer = $answerRepository->findOneBy(['id' => $request->request->get('answer')]); $answer = $answerRepository->findOneBy(['id' => $request->request->get('answer')]);
if (!$answer instanceof Answer) { if (!$answer instanceof Answer) {
throw new BadRequestException('Invalid Answer ID'); throw new BadRequestHttpException('Invalid Answer ID');
} }
$givenAnswer = (new GivenAnswer()) $givenAnswer = new GivenAnswer($candidate, $answer->getQuestion()->getQuiz(), $answer);
->setCandidate($candidate)
->setAnswer($answer)
->setQuiz($answer->getQuestion()->getQuiz());
$givenAnswerRepository->save($givenAnswer); $givenAnswerRepository->save($givenAnswer);
return $this->redirectToRoute('tvdt_quiz_quiz_page', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => $nameHash]);
} }
$question = $questionRepository->findNextQuestionForCandidate($candidate); $question = $questionRepository->findNextQuestionForCandidate($candidate);
@@ -119,10 +120,11 @@ final class QuizController extends AbstractController
if (!$question instanceof Question) { if (!$question instanceof Question) {
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz completed')); $this->addFlash(FlashType::Success, $this->translator->trans('Quiz completed'));
return $this->redirectToRoute('app_quiz_entername', ['seasonCode' => $season->getSeasonCode()]); return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
} }
// TODO One first question record time $quizCandidateRepository->createIfNotExist($quiz, $candidate);
return $this->render('quiz/question.twig', ['candidate' => $candidate, 'question' => $question]);
return $this->render('quiz/question.twig', ['candidate' => $candidate, 'question' => $question, 'season' => $season]);
} }
} }

View File

@@ -2,33 +2,36 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller; namespace Tvdt\Controller;
use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Repository\UserRepository;
use App\Security\EmailVerifier;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface; use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
use Tvdt\Entity\User;
use Tvdt\Form\RegistrationFormType;
use Tvdt\Repository\UserRepository;
use Tvdt\Security\EmailVerifier;
final class RegistrationController extends AbstractController final class RegistrationController extends AbstractController
{ {
public function __construct(private readonly EmailVerifier $emailVerifier, private readonly TranslatorInterface $translator) {} public function __construct(private readonly EmailVerifier $emailVerifier, private readonly TranslatorInterface $translator) {}
#[Route('/register', name: 'app_register')] #[Route('/register', name: 'tvdt_register')]
public function register( public function register(
Request $request, Request $request,
UserPasswordHasherInterface $userPasswordHasher, UserPasswordHasherInterface $userPasswordHasher,
Security $security, Security $security,
EntityManagerInterface $entityManager, EntityManagerInterface $entityManager,
LoggerInterface $logger,
): Response { ): Response {
$user = new User(); $user = new User();
$form = $this->createForm(RegistrationFormType::class, $user); $form = $this->createForm(RegistrationFormType::class, $user);
@@ -43,13 +46,17 @@ final class RegistrationController extends AbstractController
$entityManager->persist($user); $entityManager->persist($user);
$entityManager->flush(); $entityManager->flush();
try {
// generate a signed url and email it to the user // generate a signed url and email it to the user
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, $this->emailVerifier->sendEmailConfirmation('tvdt_verify_email', $user,
(new TemplatedEmail()) new TemplatedEmail()
->to((string) $user->getEmail()) ->to((string) $user->getEmail())
->subject($this->translator->trans('Please Confirm your Email')) ->subject($this->translator->trans('Please Confirm your Email'))
->htmlTemplate('registration/confirmation_email.html.twig') ->htmlTemplate('backoffice/registration/confirmation_email.html.twig'),
); );
} catch (TransportExceptionInterface $e) {
$logger->error($e->getMessage());
}
$response = $security->login($user, 'form_login', 'main'); $response = $security->login($user, 'form_login', 'main');
\assert($response instanceof Response); \assert($response instanceof Response);
@@ -57,24 +64,24 @@ final class RegistrationController extends AbstractController
return $response; return $response;
} }
return $this->render('registration/register.html.twig', [ return $this->render('backoffice/registration/register.html.twig', [
'registrationForm' => $form, 'registrationForm' => $form,
]); ]);
} }
#[Route('/verify/email', name: 'app_verify_email')] #[Route('/verify/email', name: 'tvdt_verify_email')]
public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response
{ {
$id = $request->query->get('id'); $id = $request->query->get('id');
if (null === $id) { if (null === $id) {
return $this->redirectToRoute('app_register'); return $this->redirectToRoute('tvdt_register');
} }
$user = $userRepository->find($id); $user = $userRepository->find($id);
if (null === $user) { if (null === $user) {
return $this->redirectToRoute('app_register'); return $this->redirectToRoute('tvdt_register');
} }
// validate email confirmation link, sets User::isVerified=true and persists // validate email confirmation link, sets User::isVerified=true and persists
@@ -83,11 +90,11 @@ final class RegistrationController extends AbstractController
} catch (VerifyEmailExceptionInterface $verifyEmailException) { } catch (VerifyEmailExceptionInterface $verifyEmailException) {
$this->addFlash('verify_email_error', $translator->trans($verifyEmailException->getReason(), [], 'VerifyEmailBundle')); $this->addFlash('verify_email_error', $translator->trans($verifyEmailException->getReason(), [], 'VerifyEmailBundle'));
return $this->redirectToRoute('app_register'); return $this->redirectToRoute('tvdt_register');
} }
$this->addFlash('success', $this->translator->trans('Your email address has been verified.')); $this->addFlash('success', $this->translator->trans('Your email address has been verified.'));
return $this->redirectToRoute('app_backoffice_index'); return $this->redirectToRoute('tvdt_backoffice_index');
} }
} }

View File

@@ -2,15 +2,15 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\DataFixtures; namespace Tvdt\DataFixtures;
use App\Entity\Answer;
use App\Entity\Candidate;
use App\Entity\Question;
use App\Entity\Quiz;
use App\Entity\Season;
use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Tvdt\Entity\Answer;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\Question;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
class KrtekFixtures extends Fixture class KrtekFixtures extends Fixture
{ {
@@ -44,38 +44,42 @@ class KrtekFixtures extends Fixture
private function createQuiz1(Season $season): Quiz private function createQuiz1(Season $season): Quiz
{ {
return (new Quiz()) return new Quiz()
->setName('Quiz 1') ->setName('Quiz 1')
->setSeason($season) ->setSeason($season)
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Is de Krtek een man of een vrouw?') ->setQuestion('Is de Krtek een man of een vrouw?')
->addAnswer(new Answer('Vrouw', true)) ->addAnswer(new Answer('Vrouw', true))
->addAnswer(new Answer('Man')) ->addAnswer(new Answer('Man'))
->setOrdering(1),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Hoeveel broers heeft de Krtek?') ->setQuestion('Hoeveel broers heeft de Krtek?')
->addAnswer(new Answer('Geen', true)) ->addAnswer(new Answer('Geen', true))
->addAnswer(new Answer('1')) ->addAnswer(new Answer('1'))
->addAnswer(new Answer('2')) ->addAnswer(new Answer('2'))
->setOrdering(2),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Wat is de lievelingsfeestdag van de Krtek?') ->setQuestion('Wat is de lievelingsfeestdag van de Krtek?')
->addAnswer(new Answer('Geen')) ->addAnswer(new Answer('Geen'))
->addAnswer(new Answer('Diens eigen verjaardag')) ->addAnswer(new Answer('Diens eigen verjaardag'))
->addAnswer(new Answer('Koningsdag')) ->addAnswer(new Answer('Koningsdag'))
->addAnswer(new Answer('Kerst', true)) ->addAnswer(new Answer('Kerst', true))
->addAnswer(new Answer('Oud en Nieuw')) ->addAnswer(new Answer('Oud en Nieuw'))
->setOrdering(3),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Hoe kwam de Krtek naar Kersteren vandaag?') ->setQuestion('Hoe kwam de Krtek naar Kersteren vandaag?')
->addAnswer(new Answer('Met het OV', true)) ->addAnswer(new Answer('Met het OV', true))
->addAnswer(new Answer('Met de auto')) ->addAnswer(new Answer('Met de auto'))
->setOrdering(4),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Met wie keek de Krtek video bij binnenkomst?') ->setQuestion('Met wie keek de Krtek video bij binnenkomst?')
->addAnswer(new Answer('Claudia')) ->addAnswer(new Answer('Claudia'))
->addAnswer(new Answer('Eelco')) ->addAnswer(new Answer('Eelco'))
@@ -90,9 +94,10 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Remy')) ->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert')) ->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom', true)) ->addAnswer(new Answer('Tom', true))
->setOrdering(5),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Welk advies zou de Krtek zichzelf als kind geven?') ->setQuestion('Welk advies zou de Krtek zichzelf als kind geven?')
->addAnswer(new Answer('Geef je vader een knuffel.')) ->addAnswer(new Answer('Geef je vader een knuffel.'))
->addAnswer(new Answer('Trek je wat minder aan van anderen.')) ->addAnswer(new Answer('Trek je wat minder aan van anderen.'))
@@ -102,9 +107,10 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Probeer ook eens buiten de lijntjes te kleuren', true)) ->addAnswer(new Answer('Probeer ook eens buiten de lijntjes te kleuren', true))
->addAnswer(new Answer('Ga als je groot bent op groepsreis! ')) ->addAnswer(new Answer('Ga als je groot bent op groepsreis! '))
->addAnswer(new Answer('Trek minder aan van de mening van anderen, het is oké om anders te zijn.')) ->addAnswer(new Answer('Trek minder aan van de mening van anderen, het is oké om anders te zijn.'))
->setOrdering(6),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Wat voor soort schoenen droeg de Krtek bij het diner?') ->setQuestion('Wat voor soort schoenen droeg de Krtek bij het diner?')
->addAnswer(new Answer('Sneakers')) ->addAnswer(new Answer('Sneakers'))
->addAnswer(new Answer('Wandel-/bergschoenen', true)) ->addAnswer(new Answer('Wandel-/bergschoenen', true))
@@ -112,22 +118,25 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Pantoffels')) ->addAnswer(new Answer('Pantoffels'))
->addAnswer(new Answer('Hakken')) ->addAnswer(new Answer('Hakken'))
->addAnswer(new Answer('Geen schoenen, alleen sokken')) ->addAnswer(new Answer('Geen schoenen, alleen sokken'))
->setOrdering(7),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Met welk vervoersmiddel reist de Krtek het liefste?') ->setQuestion('Met welk vervoersmiddel reist de Krtek het liefste?')
->addAnswer(new Answer('Fiets', true)) ->addAnswer(new Answer('Fiets', true))
->addAnswer(new Answer('Auto')) ->addAnswer(new Answer('Auto'))
->addAnswer(new Answer('Trein')) ->addAnswer(new Answer('Trein'))
->setOrdering(8),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Heeft de Krtek een eigen auto?') ->setQuestion('Heeft de Krtek een eigen auto?')
->addAnswer(new Answer('Ja')) ->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true)) ->addAnswer(new Answer('Nee', true))
->setOrdering(9),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Van wie is de quote die de Krtek gepakt heeft') ->setQuestion('Van wie is de quote die de Krtek gepakt heeft')
->addAnswer(new Answer('Karen')) ->addAnswer(new Answer('Karen'))
->addAnswer(new Answer('Gilles de Coster')) ->addAnswer(new Answer('Gilles de Coster'))
@@ -144,36 +153,41 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Pieter')) ->addAnswer(new Answer('Pieter'))
->addAnswer(new Answer('Renée Fokker')) ->addAnswer(new Answer('Renée Fokker'))
->addAnswer(new Answer('Sam, Davy', true)) ->addAnswer(new Answer('Sam, Davy', true))
->setOrdering(10),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Zou de Krtek molboekjes, jokers, vrijstellingen of topitos uit iemands rugzak stelen om te kunnen winnen?') ->setQuestion('Zou de Krtek molboekjes, jokers, vrijstellingen of topitos uit iemands rugzak stelen om te kunnen winnen?')
->addAnswer(new Answer('Ja')) ->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true)) ->addAnswer(new Answer('Nee', true))
->setOrdering(11),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('In wat voor bed slaapt de Krtek dit weekend?') ->setQuestion('In wat voor bed slaapt de Krtek dit weekend?')
->addAnswer(new Answer('Éénpersoons, losstaand bed')) ->addAnswer(new Answer('Éénpersoons, losstaand bed'))
->addAnswer(new Answer('Éénpersoonsbed, tegen een ander bed aan', true)) ->addAnswer(new Answer('Éénpersoonsbed, tegen een ander bed aan', true))
->addAnswer(new Answer('Tweepersoons bed')) ->addAnswer(new Answer('Tweepersoons bed'))
->setOrdering(12),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Hoeveel jaar heeft de Krtek gedaan over de middelbare school?') ->setQuestion('Hoeveel jaar heeft de Krtek gedaan over de middelbare school?')
->addAnswer(new Answer('5')) ->addAnswer(new Answer('5'))
->addAnswer(new Answer('6', true)) ->addAnswer(new Answer('6', true))
->addAnswer(new Answer('7')) ->addAnswer(new Answer('7'))
->addAnswer(new Answer('8')) ->addAnswer(new Answer('8'))
->setOrdering(13),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Waar zat de Krtek aan tafel bij het diner?') ->setQuestion('Waar zat de Krtek aan tafel bij het diner?')
->addAnswer(new Answer('Met de rug naar de accommodatie')) ->addAnswer(new Answer('Met de rug naar de accommodatie'))
->addAnswer(new Answer('Met de rug naar de buitenmuur', true)) ->addAnswer(new Answer('Met de rug naar de buitenmuur', true))
->setOrdering(14),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Wie is de Krtek?') ->setQuestion('Wie is de Krtek?')
->addAnswer(new Answer('Claudia', true)) ->addAnswer(new Answer('Claudia', true))
->addAnswer(new Answer('Eelco')) ->addAnswer(new Answer('Eelco'))
@@ -188,23 +202,25 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Remy')) ->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert')) ->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom')) ->addAnswer(new Answer('Tom'))
->setOrdering(15),
) )
; ;
} }
private function createQuiz2(Season $season): Quiz private function createQuiz2(Season $season): Quiz
{ {
return (new Quiz()) return new Quiz()
->setName('Quiz 2') ->setName('Quiz 2')
->setSeason($season) ->setSeason($season)
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Is de Krtek een man of een vrouw?') ->setQuestion('Is de Krtek een man of een vrouw?')
->addAnswer(new Answer('Man')) ->addAnswer(new Answer('Man'))
->addAnswer(new Answer('Vrouw', true)) ->addAnswer(new Answer('Vrouw', true))
->setOrdering(1),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Heeft de Krtek dieetwensen of allergieën?') ->setQuestion('Heeft de Krtek dieetwensen of allergieën?')
->addAnswer(new Answer('nee')) ->addAnswer(new Answer('nee'))
->addAnswer(new Answer('De Krtek is vegetariër', true)) ->addAnswer(new Answer('De Krtek is vegetariër', true))
@@ -213,9 +229,10 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('De Krtek heeft een intolerantie')) ->addAnswer(new Answer('De Krtek heeft een intolerantie'))
->addAnswer(new Answer('De Krtek eet geen rundvlees')) ->addAnswer(new Answer('De Krtek eet geen rundvlees'))
->addAnswer(new Answer('De Krtek eet geen waterdieren')) ->addAnswer(new Answer('De Krtek eet geen waterdieren'))
->setOrdering(2),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Hoe heet het huisdier/de huisdieren van de Krtek?') ->setQuestion('Hoe heet het huisdier/de huisdieren van de Krtek?')
->addAnswer(new Answer('Amy, Karel en Floyd')) ->addAnswer(new Answer('Amy, Karel en Floyd'))
->addAnswer(new Answer('Flip en Majoor')) ->addAnswer(new Answer('Flip en Majoor'))
@@ -224,9 +241,10 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Tom')) ->addAnswer(new Answer('Tom'))
->addAnswer(new Answer('De huisdieren van de Krtek hebben geen naam')) ->addAnswer(new Answer('De huisdieren van de Krtek hebben geen naam'))
->addAnswer(new Answer('De Krtek heeft geen huisdieren', true)) ->addAnswer(new Answer('De Krtek heeft geen huisdieren', true))
->setOrdering(3),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Wat dronk de Krtek deze ochtend bij het ontbijt?') ->setQuestion('Wat dronk de Krtek deze ochtend bij het ontbijt?')
->addAnswer(new Answer('Koffie')) ->addAnswer(new Answer('Koffie'))
->addAnswer(new Answer('Thee')) ->addAnswer(new Answer('Thee'))
@@ -234,9 +252,10 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Melk')) ->addAnswer(new Answer('Melk'))
->addAnswer(new Answer('Sap')) ->addAnswer(new Answer('Sap'))
->addAnswer(new Answer('Niks')) ->addAnswer(new Answer('Niks'))
->setOrdering(4),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Waar ging de eerste vakantie die de Krtek zich nog herinnert heen?') ->setQuestion('Waar ging de eerste vakantie die de Krtek zich nog herinnert heen?')
->addAnswer(new Answer('Denemarken')) ->addAnswer(new Answer('Denemarken'))
->addAnswer(new Answer('Drenthe')) ->addAnswer(new Answer('Drenthe'))
@@ -245,48 +264,54 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Oostenrijk')) ->addAnswer(new Answer('Oostenrijk'))
->addAnswer(new Answer('Turkije')) ->addAnswer(new Answer('Turkije'))
->addAnswer(new Answer('Zweden', true)) ->addAnswer(new Answer('Zweden', true))
->setOrdering(5),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Met welk groepje ging de Krtek als eerste het Douanespel in?') ->setQuestion('Met welk groepje ging de Krtek als eerste het Douanespel in?')
->addAnswer(new Answer('Het eerste groepje', true)) ->addAnswer(new Answer('Het eerste groepje', true))
->addAnswer(new Answer('Het tweede groepje')) ->addAnswer(new Answer('Het tweede groepje'))
->addAnswer(new Answer('Het derde groepje')) ->addAnswer(new Answer('Het derde groepje'))
->addAnswer(new Answer('Het vierde groepje')) ->addAnswer(new Answer('Het vierde groepje'))
->addAnswer(new Answer('Het vijfde groepje')) ->addAnswer(new Answer('Het vijfde groepje'))
->setOrdering(6),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Gelooft de Krtek ergens in?') ->setQuestion('Gelooft de Krtek ergens in?')
->addAnswer(new Answer('Nee')) ->addAnswer(new Answer('Nee'))
->addAnswer(new Answer('Het universum', true)) ->addAnswer(new Answer('Het universum', true))
->addAnswer(new Answer('Toeval')) ->addAnswer(new Answer('Toeval'))
->addAnswer(new Answer('De Krtek is hindoeïstisch')) ->addAnswer(new Answer('De Krtek is hindoeïstisch'))
->setOrdering(7),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('At de Krtek op vrijdagavond heksenkaas tijdens het diner?') ->setQuestion('At de Krtek op vrijdagavond heksenkaas tijdens het diner?')
->addAnswer(new Answer('Ja', true)) ->addAnswer(new Answer('Ja', true))
->addAnswer(new Answer('Nee')) ->addAnswer(new Answer('Nee'))
->setOrdering(8),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Hoe laat ging de Krtek gisteravond naar bed?') ->setQuestion('Hoe laat ging de Krtek gisteravond naar bed?')
->addAnswer(new Answer('Tussen 0:00 en 0:59 uur')) ->addAnswer(new Answer('Tussen 0:00 en 0:59 uur'))
->addAnswer(new Answer('Tussen 1:00 en 1:59 uur', true)) ->addAnswer(new Answer('Tussen 1:00 en 1:59 uur', true))
->addAnswer(new Answer('Tussen 2:00 en 2:59 uur')) ->addAnswer(new Answer('Tussen 2:00 en 2:59 uur'))
->addAnswer(new Answer('Na 3:00')) ->addAnswer(new Answer('Na 3:00'))
->setOrdering(9),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Hoeveel batterijen heeft de Krtek naar het bord gebracht bij het douanespel?') ->setQuestion('Hoeveel batterijen heeft de Krtek naar het bord gebracht bij het douanespel?')
->addAnswer(new Answer('1')) ->addAnswer(new Answer('1'))
->addAnswer(new Answer('2')) ->addAnswer(new Answer('2'))
->addAnswer(new Answer('3')) ->addAnswer(new Answer('3'))
->addAnswer(new Answer('geen', true)) ->addAnswer(new Answer('geen', true))
->setOrdering(10),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Wat keek de Krtek als kind graag op TV?') ->setQuestion('Wat keek de Krtek als kind graag op TV?')
->addAnswer(new Answer('Digimon', true)) ->addAnswer(new Answer('Digimon', true))
->addAnswer(new Answer('Floris')) ->addAnswer(new Answer('Floris'))
@@ -294,16 +319,18 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Sesamstraat')) ->addAnswer(new Answer('Sesamstraat'))
->addAnswer(new Answer('Spongebob Squarepants')) ->addAnswer(new Answer('Spongebob Squarepants'))
->addAnswer(new Answer('Teletubbies')) ->addAnswer(new Answer('Teletubbies'))
->setOrdering(11),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Waarin zat op de heenreis de bagage van de Krtek (voornamelijk)?') ->setQuestion('Waarin zat op de heenreis de bagage van de Krtek (voornamelijk)?')
->addAnswer(new Answer('In koffer(s)', true)) ->addAnswer(new Answer('In koffer(s)', true))
->addAnswer(new Answer('In losse tas(sen)')) ->addAnswer(new Answer('In losse tas(sen)'))
->addAnswer(new Answer('In een rugzak')) ->addAnswer(new Answer('In een rugzak'))
->setOrdering(12),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Van welk geluid gaan de haren van de Krtek overeind staan?') ->setQuestion('Van welk geluid gaan de haren van de Krtek overeind staan?')
->addAnswer(new Answer('Een vork die door een metalen pan krast ')) ->addAnswer(new Answer('Een vork die door een metalen pan krast '))
->addAnswer(new Answer('Smakkende mensen')) ->addAnswer(new Answer('Smakkende mensen'))
@@ -313,15 +340,17 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Servies dat tegen elkaar klettert')) ->addAnswer(new Answer('Servies dat tegen elkaar klettert'))
->addAnswer(new Answer('Het geroekoe van een duif', true)) ->addAnswer(new Answer('Het geroekoe van een duif', true))
->addAnswer(new Answer('Piepschuim')) ->addAnswer(new Answer('Piepschuim'))
->setOrdering(13),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Wilde de Krtek penningmeester worden?') ->setQuestion('Wilde de Krtek penningmeester worden?')
->addAnswer(new Answer('Ja')) ->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true)) ->addAnswer(new Answer('Nee', true))
->setOrdering(14),
) )
->addQuestion((new Question()) ->addQuestion(new Question()
->setQuestion('Wie is de Krtek?') ->setQuestion('Wie is de Krtek?')
->addAnswer(new Answer('Claudia', true)) ->addAnswer(new Answer('Claudia', true))
->addAnswer(new Answer('Eelco')) ->addAnswer(new Answer('Eelco'))
@@ -336,6 +365,7 @@ class KrtekFixtures extends Fixture
->addAnswer(new Answer('Remy')) ->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert')) ->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom')) ->addAnswer(new Answer('Tom'))
->setOrdering(15),
) )
; ;
} }

View File

@@ -2,15 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace Tvdt\Entity;
use App\Repository\AnswerRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\AnswerRepository;
#[ORM\Entity(repositoryClass: AnswerRepository::class)] #[ORM\Entity(repositoryClass: AnswerRepository::class)]
class Answer class Answer
@@ -21,6 +22,9 @@ class Answer
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id; private Uuid $id;
#[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])]
private int $ordering = 0;
#[ORM\ManyToOne(inversedBy: 'answers')] #[ORM\ManyToOne(inversedBy: 'answers')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
private Question $question; private Question $question;
@@ -72,7 +76,7 @@ class Answer
return $this; return $this;
} }
public function isRightAnswer(): ?bool public function isRightAnswer(): bool
{ {
return $this->isRightAnswer; return $this->isRightAnswer;
} }
@@ -112,13 +116,15 @@ class Answer
return $this->givenAnswers; return $this->givenAnswers;
} }
public function addGivenAnswer(GivenAnswer $givenAnswer): static public function getOrdering(): int
{ {
if (!$this->givenAnswers->contains($givenAnswer)) { return $this->ordering;
$this->givenAnswers->add($givenAnswer);
$givenAnswer->setAnswer($this);
} }
public function setOrdering(int $ordering): self
{
$this->ordering = $ordering;
return $this; return $this;
} }
} }

View File

@@ -2,25 +2,26 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace Tvdt\Entity;
use App\Helpers\Base64;
use App\Repository\CandidateRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Helpers\Base64;
use Tvdt\Repository\CandidateRepository;
#[ORM\Entity(repositoryClass: CandidateRepository::class)] #[ORM\Entity(repositoryClass: CandidateRepository::class)]
#[ORM\UniqueConstraint(fields: ['name', 'season'])]
class Candidate class Candidate
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null; private Uuid $id;
#[ORM\ManyToOne(inversedBy: 'candidates')] #[ORM\ManyToOne(inversedBy: 'candidates')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
@@ -34,9 +35,9 @@ class Candidate
#[ORM\OneToMany(targetEntity: GivenAnswer::class, mappedBy: 'candidate', orphanRemoval: true)] #[ORM\OneToMany(targetEntity: GivenAnswer::class, mappedBy: 'candidate', orphanRemoval: true)]
private Collection $givenAnswers; private Collection $givenAnswers;
/** @var Collection<int, Correction> */ /** @var Collection<int, QuizCandidate> */
#[ORM\OneToMany(targetEntity: Correction::class, mappedBy: 'candidate', orphanRemoval: true)] #[ORM\OneToMany(targetEntity: QuizCandidate::class, mappedBy: 'candidate', orphanRemoval: true)]
private Collection $corrections; private Collection $quizData;
public function __construct( public function __construct(
#[ORM\Column(length: 16)] #[ORM\Column(length: 16)]
@@ -44,10 +45,10 @@ class Candidate
) { ) {
$this->answersOnCandidate = new ArrayCollection(); $this->answersOnCandidate = new ArrayCollection();
$this->givenAnswers = new ArrayCollection(); $this->givenAnswers = new ArrayCollection();
$this->corrections = new ArrayCollection(); $this->quizData = new ArrayCollection();
} }
public function getId(): ?Uuid public function getId(): Uuid
{ {
return $this->id; return $this->id;
} }
@@ -107,30 +108,10 @@ class Candidate
return $this->givenAnswers; return $this->givenAnswers;
} }
public function addGivenAnswer(GivenAnswer $givenAnswer): static /** @return Collection<int, QuizCandidate> */
public function getQuizData(): Collection
{ {
if (!$this->givenAnswers->contains($givenAnswer)) { return $this->quizData;
$this->givenAnswers->add($givenAnswer);
$givenAnswer->setCandidate($this);
}
return $this;
}
/** @return Collection<int, Correction> */
public function getCorrections(): Collection
{
return $this->corrections;
}
public function addCorrection(Correction $correction): static
{
if (!$this->corrections->contains($correction)) {
$this->corrections->add($correction);
$correction->setCandidate($this);
}
return $this;
} }
public function getNameHash(): string public function getNameHash(): string

View File

@@ -1,74 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use App\Repository\CorrectionRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: CorrectionRepository::class)]
#[ORM\UniqueConstraint(columns: ['candidate_id', 'quiz_id'])]
class Correction
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null;
#[ORM\ManyToOne(inversedBy: 'corrections')]
#[ORM\JoinColumn(nullable: false)]
private Candidate $candidate;
#[ORM\ManyToOne(inversedBy: 'corrections')]
#[ORM\JoinColumn(nullable: false)]
private Quiz $quiz;
#[ORM\Column]
private float $amount = 0;
public function getId(): ?Uuid
{
return $this->id;
}
public function getCandidate(): Candidate
{
return $this->candidate;
}
public function setCandidate(Candidate $candidate): static
{
$this->candidate = $candidate;
return $this;
}
public function getQuiz(): Quiz
{
return $this->quiz;
}
public function setQuiz(Quiz $quiz): static
{
$this->quiz = $quiz;
return $this;
}
public function getAmount(): ?float
{
return $this->amount;
}
public function setAmount(float $amount): static
{
$this->amount = $amount;
return $this;
}
}

View File

@@ -2,41 +2,94 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace Tvdt\Entity;
use App\Repository\EliminationRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Safe\DateTimeImmutable;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\HttpFoundation\InputBag;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\EliminationRepository;
#[ORM\Entity(repositoryClass: EliminationRepository::class)] #[ORM\Entity(repositoryClass: EliminationRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Elimination class Elimination
{ {
public const string SCREEN_GREEN = 'green';
public const string SCREEN_RED = 'red';
#[ORM\Id] #[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null; private Uuid $id;
/** @var array<string, mixed> */
#[ORM\Column(type: Types::JSON)] #[ORM\Column(type: Types::JSON)]
private array $data = []; private array $data = [];
public function getId(): ?int #[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: false)]
private \DateTimeImmutable $created;
public function __construct(
#[ORM\ManyToOne(inversedBy: 'eliminations')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Quiz $quiz,
) {}
public function getId(): Uuid
{ {
return $this->id; return $this->id;
} }
/** @return array<string, mixed> */
public function getData(): array public function getData(): array
{ {
return $this->data; return $this->data;
} }
public function setData(array $data): static /** @param array<string, mixed> $data */
public function setData(array $data): self
{ {
$this->data = $data; $this->data = $data;
return $this; return $this;
} }
public function getQuiz(): Quiz
{
return $this->quiz;
}
/** @param InputBag<bool|float|int|string> $inputBag */
public function updateFromInputBag(InputBag $inputBag): self
{
foreach (array_keys($this->data) as $name) {
$newColour = $inputBag->get('colour-'.mb_strtolower($name));
if (\is_string($newColour)) {
$this->data[$name] = $inputBag->get('colour-'.mb_strtolower($name));
}
}
return $this;
}
public function getScreenColour(?string $name): ?string
{
return $this->data[$name] ?? null;
}
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{
$this->created = new DateTimeImmutable();
}
public function getCreated(): \DateTimeInterface
{
return $this->created;
}
} }

View File

@@ -2,15 +2,15 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace Tvdt\Entity;
use App\Repository\GivenAnswerRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\GivenAnswerRepository;
#[ORM\Entity(repositoryClass: GivenAnswerRepository::class)] #[ORM\Entity(repositoryClass: GivenAnswerRepository::class)]
#[ORM\HasLifecycleCallbacks] #[ORM\HasLifecycleCallbacks]
@@ -20,24 +20,26 @@ class GivenAnswer
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null; private Uuid $id;
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: false)]
private \DateTimeImmutable $created;
public function __construct(
#[ORM\ManyToOne(inversedBy: 'givenAnswers')] #[ORM\ManyToOne(inversedBy: 'givenAnswers')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
private Candidate $candidate; private Candidate $candidate,
#[ORM\ManyToOne] #[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Quiz $quiz; private Quiz $quiz,
#[ORM\ManyToOne(inversedBy: 'givenAnswers')] #[ORM\ManyToOne(inversedBy: 'givenAnswers')]
#[ORM\JoinColumn(nullable: true)] #[ORM\JoinColumn(nullable: false)]
private ?Answer $answer = null; private Answer $answer,
) {}
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: false)] public function getId(): Uuid
private \DateTimeInterface $created;
public function getId(): ?Uuid
{ {
return $this->id; return $this->id;
} }
@@ -47,38 +49,17 @@ class GivenAnswer
return $this->candidate; return $this->candidate;
} }
public function setCandidate(Candidate $candidate): static public function getQuiz(): Quiz
{
$this->candidate = $candidate;
return $this;
}
public function getQuiz(): ?Quiz
{ {
return $this->quiz; return $this->quiz;
} }
public function setQuiz(Quiz $quiz): static public function getAnswer(): Answer
{
$this->quiz = $quiz;
return $this;
}
public function getAnswer(): ?Answer
{ {
return $this->answer; return $this->answer;
} }
public function setAnswer(?Answer $answer): static public function getCreated(): \DateTimeImmutable
{
$this->answer = $answer;
return $this;
}
public function getCreated(): \DateTimeInterface
{ {
return $this->created; return $this->created;
} }

View File

@@ -2,15 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace Tvdt\Entity;
use App\Repository\QuestionRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\QuestionRepository;
#[ORM\Entity(repositoryClass: QuestionRepository::class)] #[ORM\Entity(repositoryClass: QuestionRepository::class)]
class Question class Question
@@ -19,9 +20,12 @@ class Question
#[ORM\Column(type: UuidType::NAME)] #[ORM\Column(type: UuidType::NAME)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null; private Uuid $id;
#[ORM\Column(length: 255, nullable: false)] #[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])]
private int $ordering;
#[ORM\Column(type: Types::STRING, length: 255)]
private string $question; private string $question;
#[ORM\ManyToOne(inversedBy: 'questions')] #[ORM\ManyToOne(inversedBy: 'questions')]
@@ -33,6 +37,7 @@ class Question
/** @var Collection<int, Answer> */ /** @var Collection<int, Answer> */
#[ORM\OneToMany(targetEntity: Answer::class, mappedBy: 'question', cascade: ['persist'], orphanRemoval: true)] #[ORM\OneToMany(targetEntity: Answer::class, mappedBy: 'question', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['ordering' => 'ASC'])]
private Collection $answers; private Collection $answers;
public function __construct() public function __construct()
@@ -40,7 +45,7 @@ class Question
$this->answers = new ArrayCollection(); $this->answers = new ArrayCollection();
} }
public function getId(): ?Uuid public function getId(): Uuid
{ {
return $this->id; return $this->id;
} }
@@ -103,7 +108,7 @@ class Question
return 'This question has no answers'; return 'This question has no answers';
} }
$correctAnswers = $this->answers->filter(static fn (Answer $answer): ?bool => $answer->isRightAnswer())->count(); $correctAnswers = $this->answers->filter(static fn (Answer $answer): bool => $answer->isRightAnswer())->count();
if (0 === $correctAnswers) { if (0 === $correctAnswers) {
return 'This question has no correct answers'; return 'This question has no correct answers';
@@ -115,4 +120,16 @@ class Question
return null; return null;
} }
public function getOrdering(): int
{
return $this->ordering;
}
public function setOrdering(int $ordering): static
{
$this->ordering = $ordering;
return $this;
}
} }

View File

@@ -2,24 +2,25 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace Tvdt\Entity;
use App\Repository\QuizRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\QuizRepository;
#[ORM\Entity(repositoryClass: QuizRepository::class)] #[ORM\Entity(repositoryClass: QuizRepository::class)]
#[ORM\UniqueConstraint(fields: ['name', 'season'])]
class Quiz class Quiz
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\Column(type: UuidType::NAME)] #[ORM\Column(type: UuidType::NAME)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null; private Uuid $id;
#[ORM\Column(length: 64)] #[ORM\Column(length: 64)]
private string $name; private string $name;
@@ -30,22 +31,29 @@ class Quiz
/** @var Collection<int, Question> */ /** @var Collection<int, Question> */
#[ORM\OneToMany(targetEntity: Question::class, mappedBy: 'quiz', cascade: ['persist'], orphanRemoval: true)] #[ORM\OneToMany(targetEntity: Question::class, mappedBy: 'quiz', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['ordering' => 'ASC'])]
private Collection $questions; private Collection $questions;
/** @var Collection<int, Correction> */ /** @var Collection<int, QuizCandidate> */
#[ORM\OneToMany(targetEntity: Correction::class, mappedBy: 'quiz', orphanRemoval: true)] #[ORM\OneToMany(targetEntity: QuizCandidate::class, mappedBy: 'quiz', orphanRemoval: true)]
private Collection $corrections; private Collection $candidateData;
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: false, options: ['default' => 1])]
private ?int $dropouts = null; private int $dropouts = 1;
/** @var Collection<int, Elimination> */
#[ORM\OneToMany(targetEntity: Elimination::class, mappedBy: 'quiz', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['created' => 'DESC'])]
private Collection $eliminations;
public function __construct() public function __construct()
{ {
$this->questions = new ArrayCollection(); $this->questions = new ArrayCollection();
$this->corrections = new ArrayCollection(); $this->candidateData = new ArrayCollection();
$this->eliminations = new ArrayCollection();
} }
public function getId(): ?Uuid public function getId(): Uuid
{ {
return $this->id; return $this->id;
} }
@@ -90,31 +98,34 @@ class Quiz
return $this; return $this;
} }
/** @return Collection<int, Correction> */ /** @return Collection<int, QuizCandidate> */
public function getCorrections(): Collection public function getCandidateData(): Collection
{ {
return $this->corrections; return $this->candidateData;
} }
public function addCorrection(Correction $correction): static public function getDropouts(): int
{
if (!$this->corrections->contains($correction)) {
$this->corrections->add($correction);
$correction->setQuiz($this);
}
return $this;
}
public function getDropouts(): ?int
{ {
return $this->dropouts; return $this->dropouts;
} }
public function setDropouts(?int $dropouts): static public function setDropouts(int $dropouts): static
{ {
$this->dropouts = $dropouts; $this->dropouts = $dropouts;
return $this; return $this;
} }
/** @return Collection<int, Elimination> */
public function getEliminations(): Collection
{
return $this->eliminations;
}
public function addElimination(Elimination $elimination): self
{
$this->eliminations->add($elimination);
return $this;
}
} }

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Safe\DateTimeImmutable;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\QuizCandidateRepository;
#[ORM\Entity(repositoryClass: QuizCandidateRepository::class)]
#[ORM\UniqueConstraint(columns: ['candidate_id', 'quiz_id'])]
#[ORM\HasLifecycleCallbacks]
class QuizCandidate
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Column]
private float $corrections = 0;
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)]
private \DateTimeImmutable $created;
public function __construct(
#[ORM\ManyToOne(inversedBy: 'candidateData')]
#[ORM\JoinColumn(nullable: false)]
private Quiz $quiz,
#[ORM\ManyToOne(inversedBy: 'quizData')]
#[ORM\JoinColumn(nullable: false)]
private Candidate $candidate,
) {}
public function getId(): Uuid
{
return $this->id;
}
public function getCandidate(): Candidate
{
return $this->candidate;
}
public function getQuiz(): Quiz
{
return $this->quiz;
}
public function getCorrections(): ?float
{
return $this->corrections;
}
public function setCorrections(float $corrections): static
{
$this->corrections = $corrections;
return $this;
}
public function getCreated(): \DateTimeImmutable
{
return $this->created;
}
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{
$this->created = new DateTimeImmutable();
}
}

View File

@@ -2,24 +2,26 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace Tvdt\Entity;
use App\Repository\SeasonRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\SeasonRepository;
#[ORM\Entity(repositoryClass: SeasonRepository::class)] #[ORM\Entity(repositoryClass: SeasonRepository::class)]
class Season class Season
{ {
private const string SEASON_CODE_CHARACTERS = 'bcdfghjklmnpqrstvwxz';
#[ORM\Id] #[ORM\Id]
#[ORM\Column(type: UuidType::NAME)] #[ORM\Column(type: UuidType::NAME)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null; private Uuid $id;
#[ORM\Column(length: 64)] #[ORM\Column(length: 64)]
private string $name; private string $name;
@@ -33,6 +35,7 @@ class Season
/** @var Collection<int, Candidate> */ /** @var Collection<int, Candidate> */
#[ORM\OneToMany(targetEntity: Candidate::class, mappedBy: 'season', cascade: ['persist'], orphanRemoval: true)] #[ORM\OneToMany(targetEntity: Candidate::class, mappedBy: 'season', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['name' => 'ASC'])]
private Collection $candidates; private Collection $candidates;
/** @var Collection<int, User> */ /** @var Collection<int, User> */
@@ -40,16 +43,22 @@ class Season
private Collection $owners; private Collection $owners;
#[ORM\ManyToOne] #[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
private ?Quiz $ActiveQuiz = null; private ?Quiz $ActiveQuiz = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: true)]
private ?SeasonSettings $settings = null;
public function __construct() public function __construct()
{ {
$this->settings = new SeasonSettings();
$this->quizzes = new ArrayCollection(); $this->quizzes = new ArrayCollection();
$this->candidates = new ArrayCollection(); $this->candidates = new ArrayCollection();
$this->owners = new ArrayCollection(); $this->owners = new ArrayCollection();
} }
public function getId(): ?Uuid public function getId(): Uuid
{ {
return $this->id; return $this->id;
} }
@@ -148,4 +157,30 @@ class Season
{ {
return $this->owners->contains($user); return $this->owners->contains($user);
} }
public function generateSeasonCode(): self
{
$code = '';
$len = mb_strlen(self::SEASON_CODE_CHARACTERS) - 1;
for ($i = 0; $i < 5; ++$i) {
$code .= self::SEASON_CODE_CHARACTERS[random_int(0, $len)];
}
$this->seasonCode = $code;
return $this;
}
public function getSettings(): ?SeasonSettings
{
return $this->settings;
}
public function setSettings(SeasonSettings $settings): static
{
$this->settings = $settings;
return $this;
}
} }

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\SeasonSettingsRepository;
#[ORM\Entity(repositoryClass: SeasonSettingsRepository::class)]
class SeasonSettings
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UuidType::NAME)]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
private bool $showNumbers = false;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
private bool $confirmAnswers = false;
public function getId(): Uuid
{
return $this->id;
}
public function isShowNumbers(): bool
{
return $this->showNumbers;
}
public function setShowNumbers(bool $showNumbers): self
{
$this->showNumbers = $showNumbers;
return $this;
}
public function isConfirmAnswers(): bool
{
return $this->confirmAnswers;
}
public function setConfirmAnswers(bool $confirmAnswers): self
{
$this->confirmAnswers = $confirmAnswers;
return $this;
}
}

View File

@@ -2,11 +2,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace Tvdt\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
@@ -14,6 +14,7 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\UserRepository;
#[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')] #[ORM\Table(name: '`user`')]
@@ -25,13 +26,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null; private Uuid $id;
#[ORM\Column(length: 180)] #[ORM\Column(length: 180)]
private string $email; private string $email;
/** @var list<string> The user roles */ /** @var list<string> The user roles */
#[ORM\Column] #[ORM\Column(type: Types::JSON)]
private array $roles = []; private array $roles = [];
/** @var string The hashed password */ /** @var string The hashed password */
@@ -50,7 +51,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
$this->seasons = new ArrayCollection(); $this->seasons = new ArrayCollection();
} }
public function getId(): ?Uuid public function getId(): Uuid
{ {
return $this->id; return $this->id;
} }
@@ -76,13 +77,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
*/ */
public function getUserIdentifier(): string public function getUserIdentifier(): string
{ {
return $this->email; /** @var non-empty-string $identifier */
$identifier = $this->email;
return $identifier;
} }
/** /**
* @see UserInterface * @see UserInterface
* *
* @return non-empty-list<string> * @return non-empty-array<int<0, max>, string>
*/ */
public function getRoles(): array public function getRoles(): array
{ {

Some files were not shown because too many files have changed in this diff Show More