Added Gedmo stuff, fix translations

This commit is contained in:
2026-05-23 12:32:33 +02:00
parent c033965652
commit e9b2b8c344
17 changed files with 180 additions and 102 deletions
+1
View File
@@ -73,6 +73,7 @@
<inspection_tool class="PhpFullyQualifiedNameUsageInspection" 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" /> <option name="IGNORE_GLOBAL_NAMESPACE" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="PhpPublicPropertyModifierCanBeOmittedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<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"> <inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="optionConfiguration"> <option name="optionConfiguration">
+4
View File
@@ -22,6 +22,10 @@ doctrine:
identity_generation_preferences: identity_generation_preferences:
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
auto_mapping: true auto_mapping: true
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: true
mappings: mappings:
Tvdt: Tvdt:
type: attribute type: attribute
@@ -5,3 +5,4 @@ stof_doctrine_extensions:
orm: orm:
default: default:
timestampable: true timestampable: true
softdeleteable: true
+34
View File
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260523095205 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add soft-delete support (deleted_at columns) and rename elimination.created to created_at';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE elimination ADD deleted_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE elimination RENAME COLUMN created TO created_at');
$this->addSql('ALTER TABLE given_answer ADD deleted_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE quiz_candidate ADD deleted_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE elimination DROP deleted_at');
$this->addSql('ALTER TABLE elimination RENAME COLUMN created_at TO created');
$this->addSql('ALTER TABLE given_answer DROP deleted_at');
$this->addSql('ALTER TABLE quiz_candidate DROP deleted_at');
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260523095302 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add updated_at column to elimination table and set it to created_at';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE elimination ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE');
$this->addSql('ALTER TABLE elimination ALTER created_at TYPE TIMESTAMP(0) WITHOUT TIME ZONE');
$this->addSql('UPDATE elimination SET updated_at = created_at');
$this->addSql('ALTER TABLE elimination ALTER updated_at SET NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE elimination DROP updated_at');
$this->addSql('ALTER TABLE elimination ALTER created_at TYPE TIMESTAMP(0) WITH TIME ZONE');
}
}
+2 -2
View File
@@ -55,7 +55,7 @@ final class EliminationController extends AbstractController
$candidate = $this->candidateRepository->getCandidateByHash($elimination->quiz->season, $candidateHash); $candidate = $this->candidateRepository->getCandidateByHash($elimination->quiz->season, $candidateHash);
if (!$candidate instanceof Candidate) { if (!$candidate instanceof Candidate) {
$this->addFlash(FlashType::Warning, $this->addFlash(FlashType::Warning,
t('Cound not find candidate with name %name%', ['%name%' => Base64::base64UrlDecode($candidateHash)])->trans($this->translator), t('Could not find candidate with name {name}', ['name' => Base64::base64UrlDecode($candidateHash)])->trans($this->translator),
); );
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->id]); return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->id]);
@@ -64,7 +64,7 @@ final class EliminationController extends AbstractController
$screenColour = $elimination->getScreenColour($candidate->name); $screenColour = $elimination->getScreenColour($candidate->name);
if (null === $screenColour) { if (null === $screenColour) {
$this->addFlash(FlashType::Warning, $this->translator->trans('Cound not find candidate with name %name% in elimination.', ['%name%' => $candidate->name])); $this->addFlash(FlashType::Warning, $this->translator->trans('Could not find candidate with name {name} in elimination.', ['name' => $candidate->name]));
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->id]); return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->id]);
} }
+1
View File
@@ -8,6 +8,7 @@ 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\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Tvdt\Enum\FlashType; use Tvdt\Enum\FlashType;
+5 -5
View File
@@ -7,15 +7,19 @@ namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo; use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\HttpFoundation\InputBag; use Symfony\Component\HttpFoundation\InputBag;
use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\EliminationRepository; use Tvdt\Repository\EliminationRepository;
#[Gedmo\SoftDeleteable]
#[ORM\Entity(repositoryClass: EliminationRepository::class)] #[ORM\Entity(repositoryClass: EliminationRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Elimination class Elimination
{ {
use SoftDeleteableEntity;
use TimestampableEntity;
public const string SCREEN_GREEN = 'green'; public const string SCREEN_GREEN = 'green';
public const string SCREEN_RED = 'red'; public const string SCREEN_RED = 'red';
@@ -30,10 +34,6 @@ class Elimination
#[ORM\Column(type: Types::JSONB)] #[ORM\Column(type: Types::JSONB)]
public array $data = []; public array $data = [];
#[Gedmo\Timestampable(on: 'create')]
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: false)]
public private(set) \DateTimeImmutable $created;
public function __construct( public function __construct(
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
#[ORM\ManyToOne(inversedBy: 'eliminations')] #[ORM\ManyToOne(inversedBy: 'eliminations')]
+4
View File
@@ -7,13 +7,17 @@ namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo; use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
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; use Tvdt\Repository\GivenAnswerRepository;
#[Gedmo\SoftDeleteable]
#[ORM\Entity(repositoryClass: GivenAnswerRepository::class)] #[ORM\Entity(repositoryClass: GivenAnswerRepository::class)]
class GivenAnswer class GivenAnswer
{ {
use SoftDeleteableEntity;
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
+1 -1
View File
@@ -42,7 +42,7 @@ class Quiz
/** @var Collection<int, Elimination> */ /** @var Collection<int, Elimination> */
#[ORM\OneToMany(targetEntity: Elimination::class, mappedBy: 'quiz', cascade: ['persist'], orphanRemoval: true)] #[ORM\OneToMany(targetEntity: Elimination::class, mappedBy: 'quiz', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['created' => 'DESC'])] #[ORM\OrderBy(['createdAt' => 'DESC'])]
public private(set) Collection $eliminations; public private(set) Collection $eliminations;
public function __construct() public function __construct()
+3
View File
@@ -7,14 +7,17 @@ namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo; use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
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\QuizCandidateRepository; use Tvdt\Repository\QuizCandidateRepository;
#[Gedmo\SoftDeleteable]
#[ORM\Entity(repositoryClass: QuizCandidateRepository::class)] #[ORM\Entity(repositoryClass: QuizCandidateRepository::class)]
#[ORM\UniqueConstraint(columns: ['candidate_id', 'quiz_id'])] #[ORM\UniqueConstraint(columns: ['candidate_id', 'quiz_id'])]
class QuizCandidate class QuizCandidate
{ {
use SoftDeleteableEntity;
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\GeneratedValue(strategy: 'CUSTOM')]
-7
View File
@@ -89,13 +89,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this->password; return $this->password;
} }
/** @see UserInterface */
public function eraseCredentials(): void
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function addSeason(Season $season): static public function addSeason(Season $season): static
{ {
if (!$this->seasons->contains($season)) { if (!$this->seasons->contains($season)) {
@@ -1,5 +1,5 @@
<div data-controller="bo--quiz"> <div data-controller="bo--quiz">
<h4 class="mb-3">Quick actions</h4> <h4 class="mb-3">{{ 'Quick actions'|trans }}</h4>
<div class="mb-3 btn-group"> <div class="mb-3 btn-group">
{% if quiz is same as (season.activeQuiz) %} {% if quiz is same as (season.activeQuiz) %}
@@ -28,7 +28,8 @@
data-bs-target="#question-{{ loop.index0 }}" data-bs-target="#question-{{ loop.index0 }}"
aria-controls="question-{{ loop.index0 }}"> aria-controls="question-{{ loop.index0 }}">
{% set questionError = questionErrors[question.id.toString] ?? null %} {% set questionError = questionErrors[question.id.toString] ?? null %}
<span class="badge rounded-pill me-2{% if questionError %} text-bg-danger{% else %} invisible{% endif %}"{% if questionError %} data-bs-toggle="tooltip" title="{{ questionError }}"{% endif %}>!</span> <span
class="badge rounded-pill me-2{% if questionError %} text-bg-danger{% else %} invisible{% endif %}"{% if questionError %} data-bs-toggle="tooltip" title="{{ questionError }}"{% endif %}>!</span>
{{~ loop.index -}}. {{ question.question -}} {{~ loop.index -}}. {{ question.question -}}
</button> </button>
</h2> </h2>
@@ -1,5 +1,5 @@
<h4 class="mb-3">{{ 'Score'|trans }}</h4> <h4 class="mb-3">{{ 'Score'|trans }}</h4>
<div class="btn-toolbar mb-3" role="toolbar"> <div class="btn-toolbar mb-3" role="toolbar">
<div class="btn-group me-2"> <div class="btn-group me-2">
{# <a class="btn btn-primary">{{ 'Start Elimination'|trans }}</a> #} {# <a class="btn btn-primary">{{ 'Start Elimination'|trans }}</a> #}
<a href="{{ path('tvdt_prepare_elimination', {seasonCode: season.seasonCode, quiz: quiz.id}) }}" <a href="{{ path('tvdt_prepare_elimination', {seasonCode: season.seasonCode, quiz: quiz.id}) }}"
@@ -10,15 +10,15 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{%~ for elimination in quiz.eliminations %} {%~ for elimination in quiz.eliminations %}
<li><a class="dropdown-item" <li><a class="dropdown-item"
href="{{ path('tvdt_prepare_elimination_view', {elimination: elimination.id}) }}">{{ elimination.created|format_datetime() }}</a> href="{{ path('tvdt_prepare_elimination_view', {elimination: elimination.id}) }}">{{ elimination.createdAt|format_datetime() }}</a>
</li> </li>
{%~ endfor %} {%~ endfor %}
</ul> </ul>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<p class="mb-3">{{ 'Number of dropouts:'|trans }} {{ quiz.dropouts }} </p> <p class="mb-3">{{ 'Number of dropouts:'|trans }} {{ quiz.dropouts }} </p>
<table class="table table-hover mb-3"> <table class="table table-hover mb-3">
<thead> <thead>
<tr> <tr>
<th scope="col">{{ 'Candidate'|trans }}</th> <th scope="col">{{ 'Candidate'|trans }}</th>
@@ -74,4 +74,4 @@
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
+1 -1
View File
@@ -13,7 +13,7 @@
{% block body %} {% block body %}
<div class="row"> <div class="row">
<div class="col-md-6 col-12"> <div class="col-md-6 col-12">
<h2 class="mb-3">{{ t('Add a quiz to %name%', {'%name%': season.name})|trans }} </h2> <h2 class="mb-3">{{ t('Add a quiz to {name}', {name: season.name})|trans }} </h2>
{{ form_start(form) }} {{ form_start(form) }}
{{ form_row(form.name) }} {{ form_row(form.name) }}
{{ form_row(form.sheet) }} {{ form_row(form.sheet) }}
+1 -1
View File
@@ -4,7 +4,7 @@
{% for label, messages in flashes %} {% for label, messages in flashes %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{{ label }} alert-dismissible " role="alert"> <div class="alert alert-{{ label }} alert-dismissible " role="alert">
{{ message }} {{ message|trans }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
{% endfor %} {% endfor %}
+13 -9
View File
@@ -37,9 +37,9 @@
<source>Add Quiz</source> <source>Add Quiz</source>
<target>Test toevoegen</target> <target>Test toevoegen</target>
</trans-unit> </trans-unit>
<trans-unit id="ehB6pAw" resname="Add a quiz to %name%"> <trans-unit id="apgpM9w" resname="Add a quiz to {name}">
<source>Add a quiz to %name%</source> <source>Add a quiz to {name}</source>
<target>Voeg een test toe aan %name%</target> <target>Voeg een test toe aan {name}</target>
</trans-unit> </trans-unit>
<trans-unit id="qiXD5ve" resname="All Seasons"> <trans-unit id="qiXD5ve" resname="All Seasons">
<source>All Seasons</source> <source>All Seasons</source>
@@ -109,13 +109,13 @@
<source>Corrections</source> <source>Corrections</source>
<target>Jokers</target> <target>Jokers</target>
</trans-unit> </trans-unit>
<trans-unit id="Lu7u8U2" resname="Cound not find candidate with name %name%"> <trans-unit id="9JHLsoe" resname="Could not find candidate with name {name}">
<source>Cound not find candidate with name %name%</source> <source>Could not find candidate with name {name}</source>
<target>Kon kandidaat met naam %name% niet vinden</target> <target>Kon kandidaat met naam {name} niet vinden</target>
</trans-unit> </trans-unit>
<trans-unit id="YQUiB4T" resname="Cound not find candidate with name %name% in elimination."> <trans-unit id="h589jDz" resname="Could not find candidate with name {name} in elimination.">
<source>Cound not find candidate with name %name% in elimination.</source> <source>Could not find candidate with name {name} in elimination.</source>
<target>Kon geen kandidaat vinden met de naam %name% in de eliminatie</target> <target>Kon geen kandidaat vinden met de naam {name} in de eliminatie</target>
</trans-unit> </trans-unit>
<trans-unit id="0DvmToq" resname="Create a season"> <trans-unit id="0DvmToq" resname="Create a season">
<source>Create a season</source> <source>Create a season</source>
@@ -301,6 +301,10 @@
<source>Questions</source> <source>Questions</source>
<target>Vragen</target> <target>Vragen</target>
</trans-unit> </trans-unit>
<trans-unit id="1jiUI_8" resname="Quick actions">
<source>Quick actions</source>
<target>Snelle acties</target>
</trans-unit>
<trans-unit id="0tv0gq." resname="Quiz"> <trans-unit id="0tv0gq." resname="Quiz">
<source>Quiz</source> <source>Quiz</source>
<target>Test</target> <target>Test</target>