mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-03-07 13:14:20 +01:00
Compare commits
19 Commits
366bc36520
...
v0.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
cfb69c8dab
|
|||
|
35ec71302b
|
|||
|
b7a570928a
|
|||
|
d80436534f
|
|||
|
|
14e2dd490e | ||
|
|
68e54b1110 | ||
|
|
e615b2cdea | ||
|
|
4b45a2c557 | ||
| c6fe553341 | |||
|
69a2b9c811
|
|||
|
f31a7d527d
|
|||
| ed3cf7644f | |||
| 77d21b004f | |||
|
379fafcd16
|
|||
|
7586d2d8ac
|
|||
|
9e41376244
|
|||
| 2bfef94bbe | |||
|
a8c4cba968
|
|||
|
d5566d4737
|
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal 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"
|
||||||
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
@@ -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
|
||||||
@@ -53,12 +57,17 @@ jobs:
|
|||||||
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"
|
||||||
|
|||||||
5
.idea/php.xml
generated
5
.idea/php.xml
generated
@@ -26,6 +26,11 @@
|
|||||||
<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" />
|
<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">
|
||||||
|
<phpcs_settings>
|
||||||
|
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="96512cb2-7b9e-4e1d-bfa2-bf7f3be424c8" timeout="30000" />
|
||||||
|
</phpcs_settings>
|
||||||
|
</component>
|
||||||
<component name="PhpExternalFormatter">
|
<component name="PhpExternalFormatter">
|
||||||
<option name="externalFormatter" value="PHP_CS_FIXER" />
|
<option name="externalFormatter" value="PHP_CS_FIXER" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"controllers": {
|
"controllers": {
|
||||||
"@symfony/ux-turbo": {
|
"@symfony/ux-turbo": {
|
||||||
"turbo-core": {
|
"turbo-core": {
|
||||||
"enabled": true,
|
"enabled": false,
|
||||||
"fetch": "eager"
|
"fetch": "eager"
|
||||||
},
|
},
|
||||||
"mercure-turbo-stream": {
|
"mercure-turbo-stream": {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ services:
|
|||||||
database:
|
database:
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
|
ports:
|
||||||
|
- "5430:5432"
|
||||||
networks:
|
networks:
|
||||||
web:
|
web:
|
||||||
external: true
|
external: true
|
||||||
|
|||||||
@@ -9,21 +9,21 @@
|
|||||||
"php": ">=8.4",
|
"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.1",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.4.2",
|
"doctrine/doctrine-migrations-bundle": "^3.4.2",
|
||||||
"doctrine/orm": "^3.3.3",
|
"doctrine/orm": "^3.5.2",
|
||||||
"easycorp/easyadmin-bundle": "^4.24.7",
|
"easycorp/easyadmin-bundle": "^4.25.0",
|
||||||
"phpdocumentor/reflection-docblock": "^5.6.2",
|
"phpdocumentor/reflection-docblock": "^5.6.3",
|
||||||
"phpoffice/phpspreadsheet": "^4.3.1",
|
"phpoffice/phpspreadsheet": "^5.1",
|
||||||
"phpstan/phpdoc-parser": "^2.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.4",
|
||||||
"symfony/asset": "7.3.*",
|
"symfony/asset": "7.3.*",
|
||||||
"symfony/asset-mapper": "7.3.*",
|
"symfony/asset-mapper": "7.3.*",
|
||||||
"symfony/console": "7.3.*",
|
"symfony/console": "7.3.*",
|
||||||
"symfony/dotenv": "7.3.*",
|
"symfony/dotenv": "7.3.*",
|
||||||
"symfony/flex": "^2.7.1",
|
"symfony/flex": "^2.8.2",
|
||||||
"symfony/form": "7.3.*",
|
"symfony/form": "7.3.*",
|
||||||
"symfony/framework-bundle": "7.3.*",
|
"symfony/framework-bundle": "7.3.*",
|
||||||
"symfony/mailer": "7.3.*",
|
"symfony/mailer": "7.3.*",
|
||||||
@@ -35,10 +35,10 @@
|
|||||||
"symfony/serializer": "7.3.*",
|
"symfony/serializer": "7.3.*",
|
||||||
"symfony/twig-bundle": "7.3.*",
|
"symfony/twig-bundle": "7.3.*",
|
||||||
"symfony/uid": "7.3.*",
|
"symfony/uid": "7.3.*",
|
||||||
"symfony/ux-turbo": "^2.26.1",
|
"symfony/ux-turbo": "^2.30.0",
|
||||||
"symfony/yaml": "7.3.*",
|
"symfony/yaml": "7.3.*",
|
||||||
"symfonycasts/sass-bundle": "^0.8.2",
|
"symfonycasts/sass-bundle": "^0.8.3",
|
||||||
"symfonycasts/verify-email-bundle": "^1.17.3",
|
"symfonycasts/verify-email-bundle": "^1.17.4",
|
||||||
"thecodingmachine/safe": "^3.3.0",
|
"thecodingmachine/safe": "^3.3.0",
|
||||||
"twig/extra-bundle": "^3.21",
|
"twig/extra-bundle": "^3.21",
|
||||||
"twig/intl-extra": "^3.21",
|
"twig/intl-extra": "^3.21",
|
||||||
@@ -46,23 +46,23 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
||||||
"friendsofphp/php-cs-fixer": "^3.75.0",
|
"friendsofphp/php-cs-fixer": "^3.87.1",
|
||||||
"phpstan/extension-installer": "^1.4.3",
|
"phpstan/extension-installer": "^1.4.3",
|
||||||
"phpstan/phpstan": "^2.1.17",
|
"phpstan/phpstan": "^2.1.22",
|
||||||
"phpstan/phpstan-doctrine": "^2.0.3",
|
"phpstan/phpstan-doctrine": "^2.0.5",
|
||||||
"phpstan/phpstan-phpunit": "^2.0.6",
|
"phpstan/phpstan-phpunit": "^2.0.7",
|
||||||
"phpstan/phpstan-symfony": "^2.0.6",
|
"phpstan/phpstan-symfony": "^2.0.8",
|
||||||
"phpunit/phpunit": "^12.2.1",
|
"phpunit/phpunit": "^12.3.8",
|
||||||
"rector/rector": "^2.0.17",
|
"rector/rector": "^2.1.6",
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
"symfony/browser-kit": "7.3.*",
|
"symfony/browser-kit": "7.3.*",
|
||||||
"symfony/css-selector": "7.3.*",
|
"symfony/css-selector": "7.3.*",
|
||||||
"symfony/maker-bundle": "^1.63.0",
|
"symfony/maker-bundle": "^1.64.0",
|
||||||
"symfony/phpunit-bridge": "7.3.*",
|
"symfony/phpunit-bridge": "7.3.*",
|
||||||
"symfony/stopwatch": "7.3.*",
|
"symfony/stopwatch": "7.3.*",
|
||||||
"symfony/web-profiler-bundle": "7.3.*",
|
"symfony/web-profiler-bundle": "7.3.*",
|
||||||
"thecodingmachine/phpstan-safe-rule": "^1.4.1",
|
"thecodingmachine/phpstan-safe-rule": "^1.4.1",
|
||||||
"vincentlanglet/twig-cs-fixer": "^3.7.1"
|
"vincentlanglet/twig-cs-fixer": "^3.9.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
|
|||||||
1678
composer.lock
generated
1678
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||||
|
|||||||
48
migrations/Version20250610210417.php
Normal file
48
migrations/Version20250610210417.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/Command/AddSettingsCommand.php
Normal file
41
src/Command/AddSettingsCommand.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Entity\SeasonSettings;
|
||||||
|
use App\Repository\SeasonRepository;
|
||||||
|
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;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app: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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\Answer;
|
use App\Entity\Answer;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
|
|
||||||
|
/** @extends AbstractCrudController<Answer> */
|
||||||
class AnswerCrudController extends AbstractCrudController
|
class AnswerCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\Candidate;
|
use App\Entity\Candidate;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
|
|
||||||
|
/** @extends AbstractCrudController<Candidate> */
|
||||||
class CandidateCrudController extends AbstractCrudController
|
class CandidateCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\GivenAnswer;
|
use App\Entity\GivenAnswer;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
|
|
||||||
|
/** @extends AbstractCrudController<GivenAnswer> */
|
||||||
class GivenAnswerCrudController extends AbstractCrudController
|
class GivenAnswerCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\Question;
|
use App\Entity\Question;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
|
|
||||||
|
/** @extends AbstractCrudController<Question> */
|
||||||
class QuestionCrudController extends AbstractCrudController
|
class QuestionCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\QuizCandidate;
|
use App\Entity\QuizCandidate;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
|
|
||||||
class CorrectionCrudController extends AbstractCrudController
|
/** @extends AbstractCrudController<QuizCandidate> */
|
||||||
|
class QuizCorrectionCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
{
|
{
|
||||||
@@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\Quiz;
|
use App\Entity\Quiz;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
|
|
||||||
|
/** @extends AbstractCrudController<Quiz> */
|
||||||
class QuizCrudController extends AbstractCrudController
|
class QuizCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\Season;
|
use App\Entity\Season;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
|
|
||||||
|
/** @extends AbstractCrudController<Season> */
|
||||||
class SeasonCrudController extends AbstractCrudController
|
class SeasonCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
|
|
||||||
|
/** @extends AbstractCrudController<User> */
|
||||||
class UserCrudController extends AbstractCrudController
|
class UserCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class QuizController extends AbstractController
|
|||||||
#[Route(
|
#[Route(
|
||||||
'/backoffice/season/{seasonCode:season}/quiz/{quiz}/enable',
|
'/backoffice/season/{seasonCode:season}/quiz/{quiz}/enable',
|
||||||
name: 'app_backoffice_enable',
|
name: 'app_backoffice_enable',
|
||||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID],
|
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID.'|null'],
|
||||||
)]
|
)]
|
||||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||||
public function enableQuiz(Season $season, ?Quiz $quiz, EntityManagerInterface $em): RedirectResponse
|
public function enableQuiz(Season $season, ?Quiz $quiz, EntityManagerInterface $em): RedirectResponse
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Entity\Quiz;
|
|||||||
use App\Entity\Season;
|
use App\Entity\Season;
|
||||||
use App\Enum\FlashType;
|
use App\Enum\FlashType;
|
||||||
use App\Form\AddCandidatesFormType;
|
use App\Form\AddCandidatesFormType;
|
||||||
|
use App\Form\SettingsForm;
|
||||||
use App\Form\UploadQuizFormType;
|
use App\Form\UploadQuizFormType;
|
||||||
use App\Security\Voter\SeasonVoter;
|
use App\Security\Voter\SeasonVoter;
|
||||||
use App\Service\QuizSpreadsheetService;
|
use App\Service\QuizSpreadsheetService;
|
||||||
@@ -26,7 +27,9 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||||||
#[IsGranted('ROLE_USER')]
|
#[IsGranted('ROLE_USER')]
|
||||||
class SeasonController extends AbstractController
|
class SeasonController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly TranslatorInterface $translator, private readonly EntityManagerInterface $em,
|
public function __construct(
|
||||||
|
private readonly TranslatorInterface $translator,
|
||||||
|
private readonly EntityManagerInterface $em,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route(
|
#[Route(
|
||||||
@@ -35,10 +38,19 @@ class SeasonController extends AbstractController
|
|||||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
|
||||||
)]
|
)]
|
||||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||||
public function index(Season $season): Response
|
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', [
|
return $this->render('backoffice/season.html.twig', [
|
||||||
'season' => $season,
|
'season' => $season,
|
||||||
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +69,7 @@ class SeasonController extends AbstractController
|
|||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$candidates = $form->get('candidates')->getData();
|
$candidates = $form->get('candidates')->getData();
|
||||||
foreach (explode("\n", (string) $candidates) as $candidate) {
|
foreach (explode("\n", (string) $candidates) as $candidate) {
|
||||||
$season->addCandidate(new Candidate($candidate));
|
$season->addCandidate(new Candidate(mb_rtrim($candidate)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->em->flush();
|
$this->em->flush();
|
||||||
|
|||||||
@@ -125,6 +125,6 @@ final class QuizController extends AbstractController
|
|||||||
|
|
||||||
$quizCandidateRepository->createIfNotExist($quiz, $candidate);
|
$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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ final class RegistrationController extends AbstractController
|
|||||||
} catch (TransportExceptionInterface $e) {
|
} catch (TransportExceptionInterface $e) {
|
||||||
$logger->error($e->getMessage());
|
$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);
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,13 @@ class Season
|
|||||||
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
#[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();
|
||||||
@@ -166,4 +171,16 @@ class Season
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSettings(): ?SeasonSettings
|
||||||
|
{
|
||||||
|
return $this->settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSettings(SeasonSettings $settings): static
|
||||||
|
{
|
||||||
|
$this->settings = $settings;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/Entity/SeasonSettings.php
Normal file
57
src/Entity/SeasonSettings.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\SeasonSettingsRepository;
|
||||||
|
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;
|
||||||
|
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,15 +39,8 @@ class RegistrationFormType extends AbstractType
|
|||||||
'second_options' => ['label' => $this->translator->trans('Repeat Password')],
|
'second_options' => ['label' => $this->translator->trans('Repeat Password')],
|
||||||
'mapped' => false,
|
'mapped' => false,
|
||||||
'constraints' => [
|
'constraints' => [
|
||||||
new NotBlank([
|
new NotBlank(message: 'Please enter a password'),
|
||||||
'message' => 'Please enter a password',
|
new Length(min: 8, max: 4096, minMessage: 'Your password should be at least {{ limit }} characters'),
|
||||||
]),
|
|
||||||
new Length([
|
|
||||||
'min' => 8,
|
|
||||||
'minMessage' => 'Your password should be at least {{ limit }} characters',
|
|
||||||
// max length allowed by Symfony for security reasons
|
|
||||||
'max' => 4096,
|
|
||||||
]),
|
|
||||||
],
|
],
|
||||||
'translation_domain' => false,
|
'translation_domain' => false,
|
||||||
])
|
])
|
||||||
|
|||||||
35
src/Form/SettingsForm.php
Normal file
35
src/Form/SettingsForm.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\SeasonSettings;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
/** @extends AbstractType<SeasonSettings> */
|
||||||
|
class SettingsForm extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('showNumbers', options: [
|
||||||
|
'label_attr' => ['class' => 'checkbox-switch'],
|
||||||
|
'attr' => ['role' => 'switch', 'switch' => null]])
|
||||||
|
->add('confirmAnswers', options: [
|
||||||
|
'label_attr' => ['class' => 'checkbox-switch'],
|
||||||
|
'attr' => ['role' => 'switch', 'switch' => null]])
|
||||||
|
->add('save', SubmitType::class)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => SeasonSettings::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,13 +31,9 @@ class UploadQuizFormType extends AbstractType
|
|||||||
'required' => true,
|
'required' => true,
|
||||||
'translation_domain' => false,
|
'translation_domain' => false,
|
||||||
'constraints' => [
|
'constraints' => [
|
||||||
new File([
|
new File(maxSize: '1024k', mimeTypes: [
|
||||||
'maxSize' => '1024k',
|
|
||||||
'mimeTypes' => [
|
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
],
|
], mimeTypesMessage: $this->translator->trans('Please upload a valid XLSX file')),
|
||||||
'mimeTypesMessage' => $this->translator->trans('Please upload a valid XLSX file'),
|
|
||||||
]),
|
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -34,12 +34,14 @@ class CandidateRepository extends ServiceEntityRepository
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->createQueryBuilder('c')
|
return $this->getEntityManager()->createQuery(<<<DQL
|
||||||
->where('c.season = :season')
|
select c from App\Entity\Candidate c
|
||||||
->andWhere('lower(c.name) = lower(:name)')
|
where c.season = :season
|
||||||
->setParameter('season', $season)
|
and lower(c.name) = lower(:name)
|
||||||
|
DQL
|
||||||
|
)->setParameter('season', $season)
|
||||||
->setParameter('name', $name)
|
->setParameter('name', $name)
|
||||||
->getQuery()->getOneOrNullResult();
|
->getOneOrNullResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(Candidate $candidate, bool $flush = true): void
|
public function save(Candidate $candidate, bool $flush = true): void
|
||||||
@@ -54,44 +56,22 @@ class CandidateRepository extends ServiceEntityRepository
|
|||||||
/** @return ResultList */
|
/** @return ResultList */
|
||||||
public function getScores(Quiz $quiz): array
|
public function getScores(Quiz $quiz): array
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('c', 'c.id')
|
return $this->getEntityManager()->createQuery(<<<DQL
|
||||||
->select('c.id', 'c.name', 'sum(case when a.isRightAnswer = true then 1 else 0 end) as correct', 'qc.corrections', 'max(ga.created) - qc.created as time')
|
select
|
||||||
->join('c.givenAnswers', 'ga')
|
c.id,
|
||||||
->join('ga.answer', 'a')
|
c.name,
|
||||||
->join('c.quizData', 'qc')
|
sum(case when a.isRightAnswer = true then 1 else 0 end) as correct,
|
||||||
->where('qc.quiz = :quiz')
|
qc.corrections,
|
||||||
->groupBy('ga.quiz', 'c.id', 'qc.id')
|
max(ga.created) - qc.created as time,
|
||||||
->setParameter('quiz', $quiz);
|
(sum(case when a.isRightAnswer = true then 1 else 0 end) + qc.corrections) as score
|
||||||
|
from App\Entity\Candidate c
|
||||||
return $this->sortResults(
|
join c.givenAnswers ga
|
||||||
$this->calculateScore(
|
join ga.answer a
|
||||||
$qb->getQuery()->getResult(),
|
join c.quizData qc
|
||||||
),
|
where qc.quiz = :quiz and ga.quiz = :quiz
|
||||||
);
|
group by ga.quiz, c.id, qc.id
|
||||||
}
|
order by score desc, time asc
|
||||||
|
DQL
|
||||||
/**
|
)->setParameter('quiz', $quiz)->getResult();
|
||||||
* @param array<string, array{id: Uuid, name: string, correct: int, time: \DateInterval, corrections: float}> $in
|
|
||||||
*
|
|
||||||
* @return array<string, Result>
|
|
||||||
*/
|
|
||||||
private function calculateScore(array $in): array
|
|
||||||
{
|
|
||||||
return array_map(static fn ($candidate): array => [
|
|
||||||
...$candidate,
|
|
||||||
'score' => $candidate['correct'] + $candidate['corrections'],
|
|
||||||
], $in);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, Result> $results
|
|
||||||
*
|
|
||||||
* @return ResultList
|
|
||||||
* */
|
|
||||||
private function sortResults(array $results): array
|
|
||||||
{
|
|
||||||
usort($results, static fn ($a, $b): int => $b['score'] <=> $a['score']);
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Entity\Candidate;
|
use App\Entity\Candidate;
|
||||||
use App\Entity\GivenAnswer;
|
|
||||||
use App\Entity\Question;
|
use App\Entity\Question;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
@@ -22,22 +21,21 @@ class QuestionRepository extends ServiceEntityRepository
|
|||||||
|
|
||||||
public function findNextQuestionForCandidate(Candidate $candidate): ?Question
|
public function findNextQuestionForCandidate(Candidate $candidate): ?Question
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('q');
|
return $this->getEntityManager()->createQuery(<<<DQL
|
||||||
|
select q from App\Entity\Question q
|
||||||
return $qb->join('q.quiz', 'qz')
|
join q.quiz qz
|
||||||
->andWhere($qb->expr()->notIn('q.id', $this->getEntityManager()->createQueryBuilder()
|
where q.id not in (
|
||||||
->select('q1')
|
select q1.id from App\Entity\GivenAnswer ga
|
||||||
->from(GivenAnswer::class, 'ga')
|
join ga.answer a
|
||||||
->join('ga.answer', 'a')
|
join a.question q1
|
||||||
->join('a.question', 'q1')
|
where ga.candidate = :candidate
|
||||||
->andWhere($qb->expr()->isNotNull('ga.answer'))
|
and q1.quiz = :quiz
|
||||||
->andWhere('ga.candidate = :candidate')
|
)
|
||||||
->andWhere('q1.quiz = :quiz')
|
and qz = :quiz
|
||||||
->getDQL()))
|
DQL)
|
||||||
->andWhere('qz = :quiz')
|
|
||||||
->setMaxResults(1)
|
->setMaxResults(1)
|
||||||
->setParameter('candidate', $candidate)
|
->setParameter('candidate', $candidate)
|
||||||
->setParameter('quiz', $candidate->getSeason()->getActiveQuiz())
|
->setParameter('quiz', $candidate->getSeason()->getActiveQuiz())
|
||||||
->getQuery()->getOneOrNullResult();
|
->getOneOrNullResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,9 @@ class SeasonRepository extends ServiceEntityRepository
|
|||||||
/** @return list<Season> Returns an array of Season objects */
|
/** @return list<Season> Returns an array of Season objects */
|
||||||
public function getSeasonsForUser(User $user): array
|
public function getSeasonsForUser(User $user): array
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('s')
|
return $this->getEntityManager()->createQuery(<<<DQL
|
||||||
->where(':user MEMBER OF s.owners')
|
select s from App\Entity\Season s where :user member of s.owners order by s.name
|
||||||
->orderBy('s.name')
|
DQL
|
||||||
->setParameter('user', $user);
|
)->setParameter('user', $user)->getResult();
|
||||||
|
|
||||||
return $qb->getQuery()->getResult();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/Repository/SeasonSettingsRepository.php
Normal file
20
src/Repository/SeasonSettingsRepository.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\SeasonSettings;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<SeasonSettings>
|
||||||
|
*/
|
||||||
|
class SeasonSettingsRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, SeasonSettings::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ use App\Entity\Quiz;
|
|||||||
use App\Entity\Season;
|
use App\Entity\Season;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
|
||||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
|
|
||||||
/** @extends Voter<string, Season> */
|
/** @extends Voter<string, Season> */
|
||||||
@@ -37,7 +38,7 @@ final class SeasonVoter extends Voter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @param Season|Elimination|Quiz|Candidate|Answer|Question $subject */
|
/** @param Season|Elimination|Quiz|Candidate|Answer|Question $subject */
|
||||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
|
||||||
{
|
{
|
||||||
$user = $token->getUser();
|
$user = $token->getUser();
|
||||||
if (!$user instanceof User) {
|
if (!$user instanceof User) {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class QuizSpreadsheetService
|
|||||||
*
|
*
|
||||||
* @throws SpreadsheetDataException
|
* @throws SpreadsheetDataException
|
||||||
*/
|
*/
|
||||||
private function fillQuizFromArray(Quiz $quiz, array $sheet): Quiz
|
private function fillQuizFromArray(Quiz $quiz, array $sheet): void
|
||||||
{
|
{
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
@@ -110,8 +110,6 @@ class QuizSpreadsheetService
|
|||||||
if ([] !== $errors) {
|
if ([] !== $errors) {
|
||||||
throw new SpreadsheetDataException($errors);
|
throw new SpreadsheetDataException($errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $quiz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function quizToXlsx(Quiz $quiz): void {}
|
public function quizToXlsx(Quiz $quiz): void {}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% block importmap %}{{ importmap('backoffice') }}{% endblock %}
|
{% block importmap %}{{ importmap('backoffice') }}{% endblock %}
|
||||||
|
{% block title %}Tijd voor de test | {% endblock %}
|
||||||
{% block nav %}{{ include('backoffice/nav.html.twig') }}{% endblock %}
|
{% block nav %}{{ include('backoffice/nav.html.twig') }}{% endblock %}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends 'backoffice/base.html.twig' %}
|
{% extends 'backoffice/base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Hello BackofficeController!{% endblock %}
|
{% block title %}{{ parent() }}Backoffice{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="d-flex flex-row align-items-center">
|
<div class="d-flex flex-row align-items-center">
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{% extends 'backoffice/base.html.twig' %}
|
{% extends 'backoffice/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}{{ parent() }}{{ quiz.season.name }}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h2 class="py-2">{{ 'Quiz'|trans }}: {{ quiz.season.name }} - {{ quiz.name }}</h2>
|
<h2 class="py-2">{{ 'Quiz'|trans }}: {{ quiz.season.name }} - {{ quiz.name }}</h2>
|
||||||
<div class="py-2 btn-group" data-controller="bo--quiz">
|
<div class="py-2 btn-group" data-controller="bo--quiz">
|
||||||
@@ -51,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
EMPTY
|
{{ 'EMPTY'|trans }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,15 +127,16 @@
|
|||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="staticBackdropLabel">Please Confirm</h1>
|
<h1 class="modal-title fs-5" id="staticBackdropLabel">{{ 'Please Confirm'|trans }}</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
Are you sure you want to clear all the results? This will also delete al the eliminations.
|
{{ 'Are you sure you want to clear all the results? This will also delete al the eliminations.'|trans }}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
|
||||||
<a href="{{ path('app_backoffice_quiz_clear', {quiz: quiz.id}) }}" class="btn btn-danger">Yes</a>
|
<a href="{{ path('app_backoffice_quiz_clear', {quiz: quiz.id}) }}"
|
||||||
|
class="btn btn-danger">{{ 'Yes'|trans }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,19 +149,18 @@
|
|||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="staticBackdropLabel">Please Confirm</h1>
|
<h1 class="modal-title fs-5" id="staticBackdropLabel">{{ 'Please Confirm'|trans }}</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
Are you sure you want to delete this quiz?
|
{{ 'Are you sure you want to delete this quiz?'|trans }}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
|
||||||
<a href="{{ path('app_backoffice_quiz_delete', {quiz: quiz.id}) }}" class="btn btn-danger">Yes</a>
|
<a href="{{ path('app_backoffice_quiz_delete', {quiz: quiz.id}) }}"
|
||||||
|
class="btn btn-danger">{{ 'Yes'|trans }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block title %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@@ -21,5 +21,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
|
{{ parent() }}Backoffice
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends 'backoffice/base.html.twig' %}
|
{% extends 'backoffice/base.html.twig' %}
|
||||||
|
{% block title %}{{ parent() }}{{ season.name }}{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h2 class="py-2">{{ 'Season'|trans }}: {{ season.name }}</h2>
|
<h2 class="py-2">{{ 'Season'|trans }}: {{ season.name }}</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -21,13 +22,18 @@
|
|||||||
<div class="d-flex flex-row align-items-center">
|
<div class="d-flex flex-row align-items-center">
|
||||||
<h4 class="py-2 pe-2">{{ 'Candidates'|trans }}</h4>
|
<h4 class="py-2 pe-2">{{ 'Candidates'|trans }}</h4>
|
||||||
<a class="link"
|
<a class="link"
|
||||||
href="{{ path('app_backoffice_add_candidates', {seasonCode: season.seasonCode}) }}">{{ 'Add Candidate'|trans }}</a>
|
href="{{ path('app_backoffice_add_candidates', {seasonCode: season.seasonCode}) }}">{{ 'Add Candidate'|trans }}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{% for candidate in season.candidates %}
|
{% for candidate in season.candidates %}
|
||||||
<li>{{ candidate.name }}</li>{% endfor %}
|
<li>{{ candidate.name }}</li>{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="d-flex flex-row align-items-center">
|
||||||
|
<h4 class="py-2 pe-2">{{ 'Settings'|trans }}</h4>
|
||||||
|
</div>
|
||||||
|
{{ form(form) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3"></div>
|
<div class="col-12 col-md-3"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,35 @@
|
|||||||
{% extends 'quiz/base.html.twig' %}
|
{% extends 'quiz/base.html.twig' %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{{ question.question }}<br/>
|
<h4>
|
||||||
|
{% if season.settings.showNumbers %}
|
||||||
|
({{ question.ordering }}/{{ question.quiz.questions.count }})
|
||||||
|
{% endif %}{{ question.question }}<br/>
|
||||||
|
</h4>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="hidden" name="token" value="{{ csrf_token('question') }}">
|
<input type="hidden" name="token" value="{{ csrf_token('question') }}">
|
||||||
|
{% if season.settings.confirmAnswers == false %}
|
||||||
{% for answer in question.answers %}
|
{% for answer in question.answers %}
|
||||||
<div>
|
<div class="py-2">
|
||||||
<button class="btn btn-outline-success"
|
<button class="btn btn-outline-success"
|
||||||
type="submit"
|
type="submit"
|
||||||
name="answer"
|
name="answer"
|
||||||
value="{{ answer.id }}">{{ answer.text }}</button>
|
value="{{ answer.id }}">{{ answer.text }}</button>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
|
||||||
Weirdly enough this question has no answers...
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% for answer in question.answers %}
|
||||||
|
<div class="py-1">
|
||||||
|
<input type="radio" class="btn-check" name="answer" id="answer-{{ loop.index0 }}" autocomplete="off"
|
||||||
|
value="{{ answer.id }}">
|
||||||
|
<label class="btn btn-outline-secondary" for="answer-{{ loop.index0 }}">{{ answer.text }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="py-2">
|
||||||
|
<button class="btn btn-success"
|
||||||
|
type="submit"
|
||||||
|
>{{ 'Next'|trans }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|||||||
@@ -33,6 +33,14 @@
|
|||||||
<source>Already have an account? Log in</source>
|
<source>Already have an account? Log in</source>
|
||||||
<target>Heb je al een account? Log in</target>
|
<target>Heb je al een account? Log in</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Qu1euq_" resname="Are you sure you want to clear all the results? This will also delete al the eliminations.">
|
||||||
|
<source>Are you sure you want to clear all the results? This will also delete al the eliminations.</source>
|
||||||
|
<target>Weet je zeker datatype je de resultaten will leegmaken? Dit gooit ook alle eliminaties weg.</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Ec4twG8" resname="Are you sure you want to delete this quiz?">
|
||||||
|
<source>Are you sure you want to delete this quiz?</source>
|
||||||
|
<target>Weet je zeker datatype je deze test will verwijderen?</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id=".QFPbFe" resname="Back">
|
<trans-unit id=".QFPbFe" resname="Back">
|
||||||
<source>Back</source>
|
<source>Back</source>
|
||||||
<target>Terug</target>
|
<target>Terug</target>
|
||||||
@@ -89,6 +97,10 @@
|
|||||||
<source>Download Template</source>
|
<source>Download Template</source>
|
||||||
<target>Download sjabloon</target>
|
<target>Download sjabloon</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="FfYlwX8" resname="EMPTY">
|
||||||
|
<source>EMPTY</source>
|
||||||
|
<target>LEEG</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="JZi_tm0" resname="Email">
|
<trans-unit id="JZi_tm0" resname="Email">
|
||||||
<source>Email</source>
|
<source>Email</source>
|
||||||
<target>E-mail</target>
|
<target>E-mail</target>
|
||||||
@@ -137,6 +149,14 @@
|
|||||||
<source>Name</source>
|
<source>Name</source>
|
||||||
<target>Naam</target>
|
<target>Naam</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="wd1MvZW" resname="No">
|
||||||
|
<source>No</source>
|
||||||
|
<target>Nee</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="gefhnBC" resname="Next">
|
||||||
|
<source>Next</source>
|
||||||
|
<target>Volgende</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="nOHriCl" resname="No active quiz">
|
<trans-unit id="nOHriCl" resname="No active quiz">
|
||||||
<source>No active quiz</source>
|
<source>No active quiz</source>
|
||||||
<target>Geen actieve test</target>
|
<target>Geen actieve test</target>
|
||||||
@@ -157,9 +177,13 @@
|
|||||||
<source>Password</source>
|
<source>Password</source>
|
||||||
<target>Wachtwoord</target>
|
<target>Wachtwoord</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="VbgD9L8" resname="Please Confirm">
|
||||||
|
<source>Please Confirm</source>
|
||||||
|
<target>Bevestig Alsjeblieft</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="6EclFME" resname="Please Confirm your Email">
|
<trans-unit id="6EclFME" resname="Please Confirm your Email">
|
||||||
<source>Please Confirm your Email</source>
|
<source>Please Confirm your Email</source>
|
||||||
<target>messages</target>
|
<target>Bevestig je e-mailadres alsjeblieft</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="lSX_PHJ" resname="Please sign in">
|
<trans-unit id="lSX_PHJ" resname="Please sign in">
|
||||||
<source>Please sign in</source>
|
<source>Please sign in</source>
|
||||||
@@ -253,6 +277,10 @@
|
|||||||
<source>Seasons</source>
|
<source>Seasons</source>
|
||||||
<target>Seizoenen</target>
|
<target>Seizoenen</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="VXFwlwn" resname="Settings">
|
||||||
|
<source>Settings</source>
|
||||||
|
<target>Instellingen</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="pNIxNSX" resname="Sign in">
|
<trans-unit id="pNIxNSX" resname="Sign in">
|
||||||
<source>Sign in</source>
|
<source>Sign in</source>
|
||||||
<target>Log in</target>
|
<target>Log in</target>
|
||||||
@@ -277,6 +305,10 @@
|
|||||||
<source>Time</source>
|
<source>Time</source>
|
||||||
<target>Tijd</target>
|
<target>Tijd</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="pRCwpOT" resname="Yes">
|
||||||
|
<source>Yes</source>
|
||||||
|
<target>Ja</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="0afY1NF" resname="You have no seasons yet.">
|
<trans-unit id="0afY1NF" resname="You have no seasons yet.">
|
||||||
<source>You have no seasons yet.</source>
|
<source>You have no seasons yet.</source>
|
||||||
<target>Je hebt nog geen seizoenen.</target>
|
<target>Je hebt nog geen seizoenen.</target>
|
||||||
|
|||||||
Reference in New Issue
Block a user