mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-07-04 22:50:15 +02:00
Added Gedmo stuff, fix translations (#117)
* Added Gedmo stuff, fix translations * Add CSRF token validation across backoffice forms - Added CSRF validations to candidate correction, penalty, answer saving, and elimination forms. - Updated corresponding Twig templates to include CSRF token inputs. - Adjusted column count in `tab_result` template to maintain layout consistency. * Add unique index constraint for `quiz_candidate` with soft delete support - Updated migration to include a unique index on `quiz_candidate` table that excludes soft-deleted records. - Adjusted `QuizCandidate` entity to reflect the new unique constraint with `deleted_at` condition. * Add CSRF token validation for quiz-related actions - Added CSRF validation to `enableQuiz`, `clearQuiz`, `deleteQuiz`, `toggleCandidate`, and `prepareElimination` actions. - Updated Twig templates to replace links with POST forms to include CSRF tokens. - Set HTTP method restrictions for related endpoints to `POST`. * Fix unique index condition for `quiz_candidate` with soft deletes - Updated condition in unique index definition of `quiz_candidate` to add parentheses for clarity. - Adjusted related migration to reflect the revised condition. * Remove if for post an use methods in Route instead * Refactor CSRF token validation in backoffice controllers - Applied `#[IsCsrfTokenValid]` attribute for CSRF checks to simplify and standardize validation. - Removed manual `isCsrfTokenValid` calls and associated exception throwing. - Updated method signatures across affected endpoints to remove unnecessary `Request` dependency. - Ensured consistency in route HTTP method restrictions where applicable. * Add rector and phpstan * Add validation for answering incorrect quiz question - Added logic to prevent candidates from answering questions out of sequence in `QuizController`. - Updated Dutch translations to include the new error message. * Things
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<form method="post">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('prepare_elimination') }}">
|
||||
{%~ for candidate, colour in elimination.data %}
|
||||
<div class="row mb-3">
|
||||
<label for="colour-{{ candidate|lower }}" class="col-4 col-form-label">{{ candidate }}</label>
|
||||
|
||||
@@ -35,14 +35,16 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ path('tvdt_backoffice_toggle_candidate', {quiz: quiz.id, candidate: candidate.id}) }}"
|
||||
class="btn btn-sm btn-outline-secondary">
|
||||
{% if quizCandidate == null or quizCandidate.active %}
|
||||
{{ 'Deactivate'|trans }}
|
||||
{% else %}
|
||||
{{ 'Activate'|trans }}
|
||||
{% endif %}
|
||||
</a>
|
||||
<form action="{{ path('tvdt_backoffice_toggle_candidate', {quiz: quiz.id, candidate: candidate.id}) }}" method="POST">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('toggle_candidate') }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary">
|
||||
{% if quizCandidate == null or quizCandidate.active %}
|
||||
{{ 'Deactivate'|trans }}
|
||||
{% else %}
|
||||
{{ 'Activate'|trans }}
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
<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">
|
||||
|
||||
{% if quiz is same as (season.activeQuiz) %}
|
||||
<a class="btn btn-secondary"
|
||||
href="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: 'null'}) }}">{{ 'Deactivate Quiz'|trans }}</a>
|
||||
<form action="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: 'null'}) }}" method="POST">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('enable_quiz') }}">
|
||||
<button type="submit" class="btn btn-secondary rounded-0 rounded-start">
|
||||
{{ 'Deactivate Quiz'|trans }}
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="btn btn-primary"
|
||||
href="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: quiz.id}) }}">{{ 'Make active'|trans }}</a>
|
||||
<form action="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: quiz.id}) }}" method="POST">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('enable_quiz') }}">
|
||||
<button type="submit" class="btn btn-primary rounded-0 rounded-start">
|
||||
{{ 'Make active'|trans }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<button class="btn btn-danger" data-action="click->bo--quiz#clearQuiz">
|
||||
{{ 'Clear Quiz...'|trans }}
|
||||
</button>
|
||||
<button class="btn btn-danger" data-action="click->bo--quiz#deleteQuiz">
|
||||
<button class="btn btn-danger rounded-0 rounded-end" data-action="click->bo--quiz#deleteQuiz">
|
||||
{{ 'Delete Quiz...'|trans }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -28,7 +36,8 @@
|
||||
data-bs-target="#question-{{ loop.index0 }}"
|
||||
aria-controls="question-{{ loop.index0 }}">
|
||||
{% 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 -}}
|
||||
</button>
|
||||
</h2>
|
||||
@@ -73,8 +82,10 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
|
||||
<a href="{{ path('tvdt_backoffice_quiz_clear', {quiz: quiz.id}) }}"
|
||||
class="btn btn-danger">{{ 'Yes'|trans }}</a>
|
||||
<form action="{{ path('tvdt_backoffice_quiz_clear', {quiz: quiz.id}) }}" method="POST">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('clear_quiz') }}">
|
||||
<button type="submit" class="btn btn-danger">{{ 'Yes'|trans }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,8 +107,10 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
|
||||
<a href="{{ path('tvdt_backoffice_quiz_delete', {quiz: quiz.id}) }}"
|
||||
class="btn btn-danger">{{ 'Yes'|trans }}</a>
|
||||
<form action="{{ path('tvdt_backoffice_quiz_delete', {quiz: quiz.id}) }}" method="POST">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete_quiz') }}">
|
||||
<button type="submit" class="btn btn-danger">{{ 'Yes'|trans }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,77 +1,80 @@
|
||||
<h4 class="mb-3">{{ 'Score'|trans }}</h4>
|
||||
<div class="btn-toolbar mb-3" role="toolbar">
|
||||
<div class="btn-group me-2">
|
||||
{# <a class="btn btn-primary">{{ 'Start Elimination'|trans }}</a> #}
|
||||
<a href="{{ path('tvdt_prepare_elimination', {seasonCode: season.seasonCode, quiz: quiz.id}) }}"
|
||||
class="btn btn-secondary">{{ 'Prepare Custom Elimination'|trans }}</a>
|
||||
{%~ if not quiz.eliminations.empty %}
|
||||
<button class="btn btn-secondary dropdown-toggle"
|
||||
data-bs-toggle="dropdown">{{ 'Load Prepared Elimination'|trans }}</button>
|
||||
<ul class="dropdown-menu">
|
||||
{%~ for elimination in quiz.eliminations %}
|
||||
<li><a class="dropdown-item"
|
||||
href="{{ path('tvdt_prepare_elimination_view', {elimination: elimination.id}) }}">{{ elimination.created|format_datetime() }}</a>
|
||||
</li>
|
||||
{%~ endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="btn-toolbar mb-3" role="toolbar">
|
||||
<div class="btn-group me-2">
|
||||
{# <a class="btn btn-primary">{{ 'Start Elimination'|trans }}</a> #}
|
||||
<form action="{{ path('tvdt_prepare_elimination', {seasonCode: season.seasonCode, quiz: quiz.id}) }}" method="POST">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('prepare_elimination') }}">
|
||||
<button type="submit" class="btn btn-secondary rounded-0 rounded-start">{{ 'Prepare Custom Elimination'|trans }}</button>
|
||||
</form>
|
||||
{%~ if not quiz.eliminations.empty %}
|
||||
<button class="btn btn-secondary dropdown-toggle"
|
||||
data-bs-toggle="dropdown">{{ 'Load Prepared Elimination'|trans }}</button>
|
||||
<ul class="dropdown-menu">
|
||||
{%~ for elimination in quiz.eliminations %}
|
||||
<li><a class="dropdown-item"
|
||||
href="{{ path('tvdt_prepare_elimination_view', {elimination: elimination.id}) }}">{{ elimination.createdAt|format_datetime() }}</a>
|
||||
</li>
|
||||
{%~ endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="mb-3">{{ 'Number of dropouts:'|trans }} {{ quiz.dropouts }} </p>
|
||||
<table class="table table-hover mb-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ 'Candidate'|trans }}</th>
|
||||
<th style="width: 15%" scope="col">{{ 'Correct Answers'|trans }}</th>
|
||||
<th style="width: 20%" scope="col">{{ 'Corrections'|trans }}</th>
|
||||
<th style="width: 20%" scope="col">{{ 'Penalty'|trans }}</th>
|
||||
<th style="width: 10%" scope="col">{{ 'Score'|trans }}</th>
|
||||
<th style="width: 20%" scope="col">{{ 'Time'|trans }}</th>
|
||||
</div>
|
||||
<p class="mb-3">{{ 'Number of dropouts:'|trans }} {{ quiz.dropouts }} </p>
|
||||
<table class="table table-hover mb-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ 'Candidate'|trans }}</th>
|
||||
<th style="width: 15%" scope="col">{{ 'Correct Answers'|trans }}</th>
|
||||
<th style="width: 20%" scope="col">{{ 'Corrections'|trans }}</th>
|
||||
<th style="width: 20%" scope="col">{{ 'Penalty'|trans }}</th>
|
||||
<th style="width: 10%" scope="col">{{ 'Score'|trans }}</th>
|
||||
<th style="width: 20%" scope="col">{{ 'Time'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%~ for candidate in result ~%}
|
||||
<tr class="table-{% if loop.revindex > quiz.dropouts %}success{% else %}danger{% endif %}">
|
||||
<td>{{ candidate.name }}</td>
|
||||
<td>{{ candidate.correct|default('0') }}</td>
|
||||
<td>
|
||||
<form method="post"
|
||||
action="{{ path('tvdt_backoffice_modify_correction', {quiz: quiz.id, candidate: candidate.id}) }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('candidate_correction') }}">
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<input class="form-control form-control-sm" type="number"
|
||||
value="{{ candidate.corrections }}" step="0.5"
|
||||
name="corrections">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button class="btn btn-sm btn-primary" type="submit">{{ 'Save'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post"
|
||||
action="{{ path('tvdt_backoffice_modify_penalty', {quiz: quiz.id, candidate: candidate.id}) }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('candidate_penalty') }}">
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<input class="form-control form-control-sm" type="number"
|
||||
value="{{ candidate.penaltySeconds }}" step="1"
|
||||
name="penalty">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button class="btn btn-sm btn-primary" type="submit">{{ 'Save'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
<td>{{ candidate.score|default('x') }}</td>
|
||||
<td>{{ candidate.time.format('%i:%S') }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%~ for candidate in result ~%}
|
||||
<tr class="table-{% if loop.revindex > quiz.dropouts %}success{% else %}danger{% endif %}">
|
||||
<td>{{ candidate.name }}</td>
|
||||
<td>{{ candidate.correct|default('0') }}</td>
|
||||
<td>
|
||||
<form method="post"
|
||||
action="{{ path('tvdt_backoffice_modify_correction', {quiz: quiz.id, candidate: candidate.id}) }}">
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<input class="form-control form-control-sm" type="number"
|
||||
value="{{ candidate.corrections }}" step="0.5"
|
||||
name="corrections">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button class="btn btn-sm btn-primary" type="submit">{{ 'Save'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post"
|
||||
action="{{ path('tvdt_backoffice_modify_penalty', {quiz: quiz.id, candidate: candidate.id}) }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('candidate_answer') }}">
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<input class="form-control form-control-sm" type="number"
|
||||
value="{{ candidate.penaltySeconds }}" step="1"
|
||||
name="penalty">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button class="btn btn-sm btn-primary" type="submit">{{ 'Save'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
<td>{{ candidate.score|default('x') }}</td>
|
||||
<td>{{ candidate.time.format('%i:%S') }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5">{{ 'No results'|trans }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6">{{ 'No results'|trans }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<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_row(form.name) }}
|
||||
{{ form_row(form.sheet) }}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% for label, messages in flashes %}
|
||||
{% for message in messages %}
|
||||
<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>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
Reference in New Issue
Block a user