3 Commits

10 changed files with 256 additions and 22 deletions

View File

@@ -4,11 +4,14 @@ declare(strict_types=1);
namespace App\Controller;
use App\Entity\Candidate;
use App\Entity\Season;
use App\Enum\FlashType;
use App\Form\EnterNameType;
use App\Form\SelectSeasonType;
use App\Helpers\Base64;
use Safe\Exceptions\UrlException;
use App\Repository\CandidateRepository;
use App\Repository\QuestionRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -60,13 +63,26 @@ class QuizController extends AbstractController
name: 'quiz_page',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX],
)]
public function quizPage(Season $season, string $nameHash)
{
try {
$name = Base64::base64_url_decode($nameHash);
} catch (UrlException $e) {
public function quizPage(
Season $season,
string $nameHash,
CandidateRepository $candidateRepository,
QuestionRepository $questionRepository,
): Response {
$candidate = $candidateRepository->getCandidateByHash($season, $nameHash);
if (!$candidate instanceof Candidate) {
if ($season->isPreregisterCandidates() === false) {
// create candidate
}
$this->addFlash(FlashType::Danger->value, "Candidate {${Base64::base64_url_decode($nameHash)}} not found");
return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]);
}
return $this->render('quiz/question.twig', ['season' => $season, 'name' => $name]);
$question = $questionRepository->findNextQuestionForCandidate($candidate);
return $this->render('quiz/question.twig', ['candidate' => $candidate, 'question' => $question]);
}
}

View File

@@ -34,20 +34,25 @@ class KrtekFixtures extends Fixture
->addCandidate(new Candidate('Philine'))
->addCandidate(new Candidate('Remy'))
->addCandidate(new Candidate('Robbert'))
->addCandidate(new Candidate('Tom'))
->addQuiz($this->createQuiz1($season));
->addCandidate(new Candidate('Tom'));
$quiz1 = $this->createQuiz1($season);
$season->addQuiz($quiz1)
->setActiveQuiz($quiz1)
->addQuiz($this->createQuiz2($season));
$manager->flush();
}
private function createQuiz1(Season $season): Quiz {
private function createQuiz1(Season $season): Quiz
{
return (new Quiz())
->setName('Quiz 1')
->setSeason($season)
->addQuestion((new Question())
->setQuestion('Is de Krtek een man of een vrouw?')
->addAnswer(new Answer('Ja', true))
->addAnswer(new Answer('Nee'))
->addAnswer(new Answer('Vrouw', true))
->addAnswer(new Answer('Man'))
)
->addQuestion((new Question())
@@ -187,4 +192,152 @@ class KrtekFixtures extends Fixture
)
;
}
private function createQuiz2(Season $season): Quiz
{
return (new Quiz())
->setName('Quiz 2')
->setSeason($season)
->addQuestion((new Question())
->setQuestion('Is de Krtek een man of een vrouw?')
->addAnswer(new Answer('Man'))
->addAnswer(new Answer('Vrouw', true))
)
->addQuestion((new Question())
->setQuestion('Heeft de Krtek dieetwensen of allergieën?')
->addAnswer(new Answer('nee'))
->addAnswer(new Answer('De Krtek is vegetariër', true))
->addAnswer(new Answer('De Krtek is flexitariër'))
->addAnswer(new Answer('De Krtek heeft een allergie'))
->addAnswer(new Answer('De Krtek heeft een intolerantie'))
->addAnswer(new Answer('De Krtek eet geen rundvlees'))
->addAnswer(new Answer('De Krtek eet geen waterdieren'))
)
->addQuestion((new Question())
->setQuestion('Hoe heet het huisdier/de huisdieren van de Krtek?')
->addAnswer(new Answer('Amy, Karel en Floyd'))
->addAnswer(new Answer('Flip en Majoor'))
->addAnswer(new Answer('Benji'))
->addAnswer(new Answer('Sini'))
->addAnswer(new Answer('Tom'))
->addAnswer(new Answer('De huisdieren van de Krtek hebben geen naam'))
->addAnswer(new Answer('De Krtek heeft geen huisdieren', true))
)
->addQuestion((new Question())
->setQuestion('Wat dronk de Krtek deze ochtend bij het ontbijt?')
->addAnswer(new Answer('Koffie'))
->addAnswer(new Answer('Thee'))
->addAnswer(new Answer('Water', true))
->addAnswer(new Answer('Melk'))
->addAnswer(new Answer('Sap'))
->addAnswer(new Answer('Niks'))
)
->addQuestion((new Question())
->setQuestion('Waar ging de eerste vakantie die de Krtek zich nog herinnert heen?')
->addAnswer(new Answer('Denemarken'))
->addAnswer(new Answer('Drenthe'))
->addAnswer(new Answer('Mallorca'))
->addAnswer(new Answer('Marokko'))
->addAnswer(new Answer('Oostenrijk'))
->addAnswer(new Answer('Turkije'))
->addAnswer(new Answer('Zweden', true))
)
->addQuestion((new Question())
->setQuestion('Met welk groepje ging de Krtek als eerste het Douanespel in?')
->addAnswer(new Answer('Het eerste groepje', true))
->addAnswer(new Answer('Het tweede groepje'))
->addAnswer(new Answer('Het derde groepje'))
->addAnswer(new Answer('Het vierde groepje'))
->addAnswer(new Answer('Het vijfde groepje'))
)
->addQuestion((new Question())
->setQuestion('Gelooft de Krtek ergens in?')
->addAnswer(new Answer('Nee'))
->addAnswer(new Answer('Het universum', true))
->addAnswer(new Answer('Toeval'))
->addAnswer(new Answer('De Krtek is hindoeïstisch'))
)
->addQuestion((new Question())
->setQuestion('At de Krtek op vrijdagavond heksenkaas tijdens het diner?')
->addAnswer(new Answer('Ja', true))
->addAnswer(new Answer('Nee'))
)
->addQuestion((new Question())
->setQuestion('Hoe laat ging de Krtek gisteravond naar bed?')
->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 2:00 en 2:59 uur'))
->addAnswer(new Answer('Na 3:00'))
)
->addQuestion((new Question())
->setQuestion('Hoeveel batterijen heeft de Krtek naar het bord gebracht bij het douanespel?')
->addAnswer(new Answer('1'))
->addAnswer(new Answer('2'))
->addAnswer(new Answer('3'))
->addAnswer(new Answer('geen', true))
)
->addQuestion((new Question())
->setQuestion('Wat keek de Krtek als kind graag op TV?')
->addAnswer(new Answer('Digimon', true))
->addAnswer(new Answer('Floris'))
->addAnswer(new Answer('Het huis Anubis'))
->addAnswer(new Answer('Sesamstraat'))
->addAnswer(new Answer('Spongebob Squarepants'))
->addAnswer(new Answer('Teletubbies'))
)
->addQuestion((new Question())
->setQuestion('Waarin zat op de heenreis de bagage van de Krtek (voornamelijk)?')
->addAnswer(new Answer('In koffer(s)', true))
->addAnswer(new Answer('In losse tas(sen)'))
->addAnswer(new Answer('In een rugzak'))
)
->addQuestion((new Question())
->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('Smakkende mensen'))
->addAnswer(new Answer('Een vork die over een bord schraapt'))
->addAnswer(new Answer('Schuren met schuurpapier'))
->addAnswer(new Answer('Nagels op een krijtbord'))
->addAnswer(new Answer('Servies dat tegen elkaar klettert'))
->addAnswer(new Answer('Het geroekoe van een duif', true))
->addAnswer(new Answer('Piepschuim'))
)
->addQuestion((new Question())
->setQuestion('Wilde de Krtek penningmeester worden?')
->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true))
)
->addQuestion((new Question())
->setQuestion('Wie is de Krtek?')
->addAnswer(new Answer('Claudia', true))
->addAnswer(new Answer('Eelco'))
->addAnswer(new Answer('Elise'))
->addAnswer(new Answer('Gert-Jan'))
->addAnswer(new Answer('Iris'))
->addAnswer(new Answer('Jari'))
->addAnswer(new Answer('Lara'))
->addAnswer(new Answer('Lotte'))
->addAnswer(new Answer('Myrthe'))
->addAnswer(new Answer('Philine'))
->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom'))
)
;
}
}

View File

@@ -31,7 +31,7 @@ class Season
private bool $preregisterCandidates;
/** @var Collection<int, Quiz> */
#[ORM\OneToMany(targetEntity: Quiz::class, mappedBy: 'season', orphanRemoval: true)]
#[ORM\OneToMany(targetEntity: Quiz::class, mappedBy: 'season', cascade: ['persist'], orphanRemoval: true)]
private Collection $quizzes;
/** @var Collection<int, Candidate> */

17
src/Enum/FlashType.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Enum;
enum FlashType: string
{
case Primary = 'primary';
case Secondary = 'secondary';
case Success = 'success';
case Danger = 'danger';
case Warning = 'warning';
case Info = 'info';
case Ligt = 'light';
case Dark = 'dark';
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Helpers;
use Safe\Exceptions\UrlException;
use function rtrim;
class Base64
{
@@ -12,14 +13,22 @@ class Base64
{
}
public static function base64_url_encode(string $input): string
/**
* @param string $name name to hash
* @return string hashed name
*/
public static function base64_url_encode(string $name): string
{
return strtr(base64_encode($input), '+/=', '-_.');
return rtrim(strtr(base64_encode($name), '+/', '-_'), '=');
}
/** @throws UrlException */
public static function base64_url_decode(string $input): string
/**
* @param string $hash hashed name
* @return string plaintext name
* @throws UrlException
*/
public static function base64_url_decode(string $hash): string
{
return \Safe\base64_decode(strtr($input, '-_.', '+/='), true);
return \Safe\base64_decode(strtr($hash, '-_', '+/'), true);
}
}

View File

@@ -29,6 +29,11 @@ class CandidateRepository extends ServiceEntityRepository
return null;
}
return $this->findOneBy(['season' => $season, 'name' => $name]);
return $this->createQueryBuilder('c')
->where('c.season = :season')
->andWhere('lower(c.name) = lower(:name)')
->setParameter('season', $season)
->setParameter('name', $name)
->getQuery()->getOneOrNullResult();
}
}

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Repository;
use App\Entity\Candidate;
use App\Entity\GivenAnswer;
use App\Entity\Question;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@@ -17,4 +19,25 @@ class QuestionRepository extends ServiceEntityRepository
{
parent::__construct($registry, Question::class);
}
public function findNextQuestionForCandidate(Candidate $candidate): Question
{
$qb = $this->createQueryBuilder('q');
return $qb->join('q.quiz', 'qz')
->andWhere($qb->expr()->notIn('q.id', $this->getEntityManager()->createQueryBuilder()
->select('ga.id')
->from(GivenAnswer::class, 'ga')
->join('ga.answer', 'a')
->join('a.question', 'q1')
->andWhere($qb->expr()->isNotNull('ga.answer'))
->andWhere('ga.candidate = :candidate')
->andWhere('q1.quiz = :quiz')
->getDQL()))
->andWhere('qz = :quiz')
->setMaxResults(1)
->setParameter('candidate', $candidate)
->setParameter('quiz', $candidate->getSeason()->getActiveQuiz())
->getQuery()->getSingleResult();
}
}

View File

@@ -32,6 +32,14 @@
<body>
<main>
<div class="container">
{% for label, messages in app.flashes() %}
{% for message in messages %}
<div class="alert alert-{{ label }} alert-dismissible " role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endfor %}
{% block body %}
{% endblock body %}
</div>

View File

@@ -1,6 +1,9 @@
{% extends "quiz/base.html.twig" %}
{% block body %}
{{ season.name }}
{{ name }}
<h1>Hello World!</h1>
Candiadte: {{ candidate.name }}<br/>
{{ question.question }}<br/>
{% for answer in question.answers %}
<input type="radio" name="answer" value="{{ answer.id }}"> {{ answer.text }}
{% endfor %}
{% endblock body %}