<?php
namespace App\Controller;
use App\Entity\Answer;
use App\Entity\Declaration;
use App\Entity\Package;
use App\Entity\CustomInput;
use App\Entity\Participant;
use App\Entity\ParticipantAnswer;
use App\Entity\ParticipantFile;
use App\Entity\Question;
use App\Entity\Setting;
use App\Form\AddParticipantType;
use App\Form\AnswerQuestionsType;
use App\Repository\QuestionRepository;
use Endroid\QrCode\Builder\BuilderInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Form\RegistrationType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\Routing\Annotation\Route;
use Dompdf\Dompdf;
use Dompdf\Options;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
class ExposedCheckinController extends BaseController
{
/**
* @Route("/", name="exposed-participant")
*
* @param Request $request
* @param InviteRegistrationController $inviteRegistrationController
* @param BuilderInterface $customQrCodeBuilder
* @param KernelInterface $kernel
* @return RedirectResponse|Response
* @throws ExceptionInterface
*/
public function addParticipant(
Request $request,
InviteRegistrationController $inviteRegistrationController,
BuilderInterface $customQrCodeBuilder,
KernelInterface $kernel
)
{
$participant = new Participant();
$defaultPackage = $this->em->find(Package::class, 1);
$form = $this->createForm(AddParticipantType::class, $participant,
[
'defaultPackage' => $defaultPackage,
'isExposed' => true
]
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$participant = $form->getData();
$this->em->persist($participant);
$this->em->flush();
if ($this->em->getRepository(Setting::class)->findOneBy(['name' => Setting::CUSTOM_FIELDS_KEY])->getValue()) {
return $this->redirectToRoute('exposed_participant_registration_form', [
'code' => $participant->getSecretCode()
]);
}
if ($this->em->getRepository(Setting::class)->findOneBy(['name' => Setting::QUESTIONS_KEY])->getValue()) {
return $this->redirectToRoute('exposed-participant-questionnaire',
['code' => $participant->getSecretCode()]
);
}
$this->addFlash('sweetAlert', [
'success' => $this->translator->trans('general.info.success')
]
);
$inviteRegistrationController->sendEmailUponRegistration($request, $participant, $customQrCodeBuilder, $kernel);
return $this->redirectToRoute('exposed-participant');
}
$termsAndConditions = $this->em->getRepository(Setting::class)->findOneBy(['name' => Setting::FORM_TERMS_AND_CONDITIONS_KEY])->getValue();
$normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
$serializer = new Serializer([new DateTimeNormalizer(), $normalizer]);
$startDate = $serializer->denormalize($this->em->getRepository(Setting::class)->findOneBy(['id' => Setting::START_DATE_ID])->getValue(), \DateTime::class, 'json');
$endDate = $serializer->denormalize($this->em->getRepository(Setting::class)->findOneBy(['id' => Setting::END_DATE_ID])->getValue(), \DateTime::class, 'json');
$availableAtMessage = $this->em->getRepository(Setting::class)->findOneBy(['id' => Setting::AVAILABLE_AT_MESSAGE_ID])->getValue();
$endedAtMessage = $this->em->getRepository(Setting::class)->findOneBy(['id' => Setting::ENDED_AT_MESSAGE_ID])->getValue();
$availability = [
'startDate' => $startDate,
'endDate' => $endDate,
'availableAtMessage' => $availableAtMessage,
'endedAtMessage' => $endedAtMessage
];
return $this->render('exposed_checkin\index.html.twig', [
'form' => $form->createView(),
'isExposed' => true,
'termsAndConditions' => $termsAndConditions,
'availability' => $availability
]);
}
/**
* @Route("/questionnaire/{code}", name="exposed-participant-questionnaire")
* @ParamConverter("participant", options={"mapping": {"code": "secretCode"}} )
*
* @param Participant $participant
* @param Request $request
* @param QuestionRepository $questionRepository
* @param InviteRegistrationController $inviteRegistrationController
* @param BuilderInterface $customQrCodeBuilder
* @param KernelInterface $kernel
* @return Response
*/
public function getAnswersOfParticipant(
Participant $participant,
Request $request,
QuestionRepository $questionRepository,
InviteRegistrationController $inviteRegistrationController,
BuilderInterface $customQrCodeBuilder,
KernelInterface $kernel
): Response
{
$activeQuestions = $questionRepository->findBy(['status' => true], ['sortOrder' => 'ASC']);
foreach ($activeQuestions as $question) {
$questions[$question->getId()] = $question;
}
/** @noinspection PhpUndefinedVariableInspection */
$options['questions'] = $questions;
$options['participantAnswers'] = [];
foreach ($participant->getParticipantAnswers() as $answer) {
$options['participantAnswers'][$answer->getQuestion()->getId()] = $answer->getAnswer()->getId();
}
$form = $this->createForm(AnswerQuestionsType::class, null, $options);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
foreach ($participant->getParticipantAnswers() as $answer) {
$this->em->remove($answer);
}
$participantQuestionsAndAnswers = $form->getData();
$questions = array_keys($participantQuestionsAndAnswers);
$answers = array_values($participantQuestionsAndAnswers);
// used to be a simple search by arrays, but they were returning in the database's order
// now we have this BS
$questions = $this->em->getRepository(Question::class)
->createQueryBuilder("question")
->where('question.id IN (:ids)')
->andWhere('question.status = :active')
// Add the new orderBy clause specifying the order given in the ids array
->add("orderBy", "FIELD(question.id, :ids)")
// Set the primary keys of the register that you want to search for
->setParameter('ids', $questions)
->setParameter('active', true)
->getQuery()
->getResult();
$answers = $this->em->getRepository(Answer::class)
->createQueryBuilder("a")
->where('a.id IN (:ids)')
->andWhere('a.status = :active')
// Add the new orderBy clause specifying the order given in the ids array
->add("orderBy", "FIELD(a.id, :ids)")
// Set the primary keys of the register that you want to search for
->setParameter('ids', $answers)
->setParameter('active', true)
->getQuery()
->getResult();
$score = 0;
foreach ($questions as $index => $question) {
$participantAnswer = new ParticipantAnswer();
$participantAnswer
->setParticipant($participant)
->setQuestion($question)
->setAnswer($answers[$index])
->setStatus(true);
$score += $answers[$index]->getScore();
$this->em->persist($participantAnswer);
}
$participant->setScore($score);
$this->em->persist($participant);
$this->em->flush();
$this->addFlash('sweetAlert', [
'success' => $this->translator->trans('general.info.success')
]
);
$inviteRegistrationController->sendEmailUponRegistration($request, $participant, $customQrCodeBuilder, $kernel);
return $this->redirectToRoute('exposed-participant');
}
return $this->render('checkin/answerQuestions.html.twig',
[
'form' => $form->createView()
]
);
}
/**
* @Route("/welcome/form/{code}", name="exposed_participant_registration_form")
* @ParamConverter("participant", options={"mapping": {"code": "secretCode"}} )
* @param Participant $participant
* @param Request $request
* @param InviteRegistrationController $inviteRegistrationController
* @param BuilderInterface $customQrCodeBuilder
* @param KernelInterface $kernel
* @return Response
*/
public function registration(
Participant $participant,
Request $request,
InviteRegistrationController $inviteRegistrationController,
BuilderInterface $customQrCodeBuilder,
KernelInterface $kernel
): Response
{
$customInputs = $this->em->getRepository(CustomInput::class)->findBy(
['active' => true, 'shownByDefault' => true], ['sortOrder' => 'ASC']
);
$form = $this->createForm(RegistrationType::class, $participant->getCustomInputs(), [
'customInputs' => $customInputs,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
foreach ($data as $index => $field) {
if (is_object($field)) {
if (get_class($field) === UploadedFile::class) {
$filesToUpload[$index] = $field;
}
}
}
if (!empty($filesToUpload)) {
if (!empty($participant->getCustomInputs())) {
$filesToBeRemoved = $participant->getParticipantFiles();
foreach ($filesToBeRemoved as $fileToBeRemoved) {
$this->em->remove($fileToBeRemoved);
}
}
foreach ($filesToUpload as $index => $file) {
$customInputThatHasFile = $this->em->getRepository(CustomInput::class)->findOneBy(
['fieldName' => $index]
);
$declarationType = $customInputThatHasFile->getDeclarationType();
$participantFile = (new ParticipantFile())
->setParticipant($participant)
->setDeclarationType($declarationType)
->setFileName($declarationType->getTitle());
$this->saveDeclarationPdf($participant, $declarationType, $file);
$this->em->persist($participantFile);
$this->em->flush();
}
}
$participant->setCustomInputs($data);
$this->em->persist($participant);
$this->em->flush();
if ($this->em->getRepository(Setting::class)->findOneBy(['name' => Setting::QUESTIONS_KEY])->getValue()) {
return $this->redirectToRoute('exposed-participant-questionnaire',
['code' => $participant->getSecretCode()]
);
}
$this->addFlash('sweetAlert', [
'success' => $this->translator->trans('general.info.success')
]);
$inviteRegistrationController->sendEmailUponRegistration($request, $participant, $customQrCodeBuilder, $kernel);
return $this->redirectToRoute('exposed-participant');
}
return $this->renderForm('registration/registration.html.twig',
[
'form' => $form
]);
}
/**
* @Route("/declaration-download/{declaration}", name="declaration-download")
*
* @param Declaration $declaration
* @return void
*/
public function downloadDeclaration(Declaration $declaration)
{
// Configure Dompdf according to your needs
$pdfOptions = (new Options())
->set('defaultFont', 'DejaVu Serif')
->setIsRemoteEnabled(true)
->setIsHtml5ParserEnabled(true);
// Instantiate Dompdf with our options
$dompdf = new Dompdf($pdfOptions);
$pattern = '/font-family:[- ,a-zA-Z"]+">/';
$replacement = '">';
$declarationHtml = preg_replace(
$pattern,
$replacement,
html_entity_decode($declaration->getBody(), ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8')
);
$html = $declarationHtml;
// Load HTML to Dompdf
$dompdf->loadHtml($html, 'utf-8');
// (Optional) Setup the paper size and orientation 'portrait' or 'portrait'
$dompdf->setPaper('A4', 'portrait');
// Render the HTML as PDF
$dompdf->render();
// Output the generated PDF to Browser (force download)
$dompdf->stream($declaration->getTitle().uniqid().".pdf", [
"Attachment" => true
]);
exit();
}
/**
* @Route("/customer-declaration-download/{participantFile}", name="customer-declaration-download")
*
* @param ParticipantFile $participantFile
* @return BinaryFileResponse
*/
public function downloadCustomerDeclaration(ParticipantFile $participantFile)
{
$response = new BinaryFileResponse(
$this->getDownloadPath($participantFile->getParticipant(), $participantFile->getDeclarationType())
);
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$participantFile->getDeclarationType()->getTitle() . uniqid() . ".pdf"
);
return $response;
}
/**
* @Route("/upload-declaration/{participant}/{declaration}", name="upload-declaration")
*
* @param Request $request
* @param Participant $participant
* @param Declaration $declaration
* @return JsonResponse
*/
public function uploadDeclaration(Request $request, Participant $participant, Declaration $declaration): JsonResponse
{
try {
/** @var UploadedFile $attachment */
$pdf = $request->files->get('file');
// digital signature not needed in 'backend'
/*if (!$this->isStringInFile($pdf, 'adbe.pkcs7.detached')) {
return new JsonResponse(
[
'is_error' => true,
'message' => $this->getTranslator()->trans('file.error.notSigned')
]
);
}*/
$participantFile = (new ParticipantFile())
->setParticipant($participant)
->setDeclarationType($declaration)
->setFileName($declaration->getTitle());
$this->saveDeclarationPdf($participant, $declaration, $pdf);
$this->em->persist($participantFile);
$this->em->flush();
} catch (\Exception $exception) {
return new JsonResponse(
[
'is_error' => true,
'message' => $exception->getMessage()
]
);
}
return new JsonResponse(
['is_error' => false]
);
}
/**
* @param Participant $participant
* @param Declaration $declaration
* @param $pdf
* @return bool
*/
public function saveDeclarationPdf(
Participant $participant,
Declaration $declaration,
$pdf
) : bool {
$filepath = $this->getDownloadPath($participant, $declaration);
$this->createDownloadDirectory($participant);
$save = file_put_contents(
$filepath,
file_get_contents($pdf->getPathName())
);
return $save;
}
public function getDownloadPath(Participant $participant, Declaration $declaration) : string
{
$filepath = $this->getParameter('app.downloadPath')
. '/' . $participant->getSecretCode()
. '/' . $declaration->getId() . '.pdf';
return $filepath;
}
/**
* @param Participant $participant
* @return void
*/
private function createDownloadDirectory(Participant $participant): void
{
$rootDir = $this->getParameter('app.downloadPath');
$concurrentDirectory = $rootDir.'/'.$participant->getSecretCode();
if (!is_dir($concurrentDirectory) && !mkdir($concurrentDirectory)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
}
}
/**
* @Route("/customer-declaration-remove/{participantFile}/{secretParticipantCode}", name="customer-declaration-remove")
*
* @param string $secretParticipantCode
* @param ParticipantFile $participantFile
* @param UrlGeneratorInterface $router
* @return RedirectResponse
*/
public function deleteCustomerDeclaration(
string $secretParticipantCode,
ParticipantFile $participantFile,
UrlGeneratorInterface $router
): RedirectResponse {
if ($secretParticipantCode !== $participantFile->getParticipant()->getSecretCode()) {
$this->addFlash('error', $this->translator->trans('participant.error.invalidRequest'));
return $this->redirectToRoute('participant', ['id' => $participantFile->getParticipant()->getId()]);
}
$this->em->remove($participantFile);
$this->em->flush();
$url = $router->generate('participant', ['id' => $participantFile->getParticipant()->getId()]);
return $this->redirect($url."#tabs-3");
}
/**
* @param UploadedFile $file
* @param string $string
* @return bool
*/
function isStringInFile(UploadedFile $file, string $string): bool {
$valid = false; // init as false
$buffer = file_get_contents($file->getPathName());
if ($buffer !== false) {
if (strpos($buffer, $string) !== false) {
$valid = true;
}
}
return $valid;
}
/**
* @Route("/download/{settingName}", name="app_exposedcheckin_downloadFile")
* @ParamConverter("setting", options={"mapping": {"settingName" : "name"}})
*
* @param Setting $setting
* @return BinaryFileResponse
*/
public function downloadFile(Setting $setting): ?BinaryFileResponse
{
if (empty($setting->getValue())) {
return null;
}
$response = new BinaryFileResponse(
$setting->getValue()
);
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$setting->getName() . '.' . pathinfo($setting->getValue())['extension']
);
return $response;
}
/**
* @Route("/gdpr", name="app_gdpr")
*/
public function viewGdpr() {
$gdprSetting = $this->em->find(Setting::class, Setting::GDPR_ID);
$gdprHtml = $gdprSetting->getValue();
return $this->render(
'exposed_checkin/gdpr.html.twig',
[
'gdpr' => $gdprHtml,
]
);
}
}