src/Controller/ExposedCheckinController.php line 50

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Answer;
  4. use App\Entity\Declaration;
  5. use App\Entity\Package;
  6. use App\Entity\CustomInput;
  7. use App\Entity\Participant;
  8. use App\Entity\ParticipantAnswer;
  9. use App\Entity\ParticipantFile;
  10. use App\Entity\Question;
  11. use App\Entity\Setting;
  12. use App\Form\AddParticipantType;
  13. use App\Form\AnswerQuestionsType;
  14. use App\Repository\QuestionRepository;
  15. use Endroid\QrCode\Builder\BuilderInterface;
  16. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  17. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  18. use Symfony\Component\HttpFoundation\File\UploadedFile;
  19. use Symfony\Component\HttpFoundation\JsonResponse;
  20. use App\Form\RegistrationType;
  21. use Symfony\Component\HttpFoundation\RedirectResponse;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\Response;
  24. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  25. use Symfony\Component\HttpKernel\KernelInterface;
  26. use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
  27. use Symfony\Component\Routing\Annotation\Route;
  28. use Dompdf\Dompdf;
  29. use Dompdf\Options;
  30. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  31. use Symfony\Component\Serializer\Exception\ExceptionInterface;
  32. use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
  33. use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
  34. use Symfony\Component\Serializer\Serializer;
  35. class ExposedCheckinController extends BaseController
  36. {
  37.     /**
  38.      * @Route("/", name="exposed-participant")
  39.      *
  40.      * @param Request $request
  41.      * @param InviteRegistrationController $inviteRegistrationController
  42.      * @param BuilderInterface $customQrCodeBuilder
  43.      * @param KernelInterface $kernel
  44.      * @return RedirectResponse|Response
  45.      * @throws ExceptionInterface
  46.      */
  47.     public function addParticipant(
  48.         Request $request,
  49.         InviteRegistrationController $inviteRegistrationController,
  50.         BuilderInterface $customQrCodeBuilder,
  51.         KernelInterface $kernel
  52.     )
  53.     {
  54.         $participant = new Participant();
  55.         $defaultPackage $this->em->find(Package::class, 1);
  56.         $form $this->createForm(AddParticipantType::class, $participant,
  57.             [
  58.                 'defaultPackage' => $defaultPackage,
  59.                 'isExposed' => true
  60.             ]
  61.         );
  62.         $form->handleRequest($request);
  63.         if ($form->isSubmitted() && $form->isValid()) {
  64.             $participant $form->getData();
  65.             $this->em->persist($participant);
  66.             $this->em->flush();
  67.             if ($this->em->getRepository(Setting::class)->findOneBy(['name' => Setting::CUSTOM_FIELDS_KEY])->getValue()) {
  68.                 return $this->redirectToRoute('exposed_participant_registration_form', [
  69.                     'code' => $participant->getSecretCode()
  70.                 ]);
  71.             }
  72.             if ($this->em->getRepository(Setting::class)->findOneBy(['name' => Setting::QUESTIONS_KEY])->getValue()) {
  73.                 return $this->redirectToRoute('exposed-participant-questionnaire',
  74.                     ['code' => $participant->getSecretCode()]
  75.                 );
  76.             }
  77.             $this->addFlash('sweetAlert', [
  78.                     'success' => $this->translator->trans('general.info.success')
  79.                 ]
  80.             );
  81.             $inviteRegistrationController->sendEmailUponRegistration($request$participant$customQrCodeBuilder$kernel);
  82.             return $this->redirectToRoute('exposed-participant');
  83.         }
  84.         $termsAndConditions $this->em->getRepository(Setting::class)->findOneBy(['name' => Setting::FORM_TERMS_AND_CONDITIONS_KEY])->getValue();
  85.         $normalizer = new ObjectNormalizer(nullnullnull, new ReflectionExtractor());
  86.         $serializer = new Serializer([new DateTimeNormalizer(), $normalizer]);
  87.         $startDate $serializer->denormalize($this->em->getRepository(Setting::class)->findOneBy(['id' => Setting::START_DATE_ID])->getValue(), \DateTime::class, 'json');
  88.         $endDate $serializer->denormalize($this->em->getRepository(Setting::class)->findOneBy(['id' => Setting::END_DATE_ID])->getValue(), \DateTime::class, 'json');
  89.         $availableAtMessage $this->em->getRepository(Setting::class)->findOneBy(['id' => Setting::AVAILABLE_AT_MESSAGE_ID])->getValue();
  90.         $endedAtMessage $this->em->getRepository(Setting::class)->findOneBy(['id' => Setting::ENDED_AT_MESSAGE_ID])->getValue();
  91.         $availability = [
  92.             'startDate' => $startDate,
  93.             'endDate' => $endDate,
  94.             'availableAtMessage' => $availableAtMessage,
  95.             'endedAtMessage' => $endedAtMessage
  96.         ];
  97.         return $this->render('exposed_checkin\index.html.twig', [
  98.             'form' => $form->createView(),
  99.             'isExposed' => true,
  100.             'termsAndConditions' => $termsAndConditions,
  101.             'availability' => $availability
  102.         ]);
  103.     }
  104.     /**
  105.      * @Route("/questionnaire/{code}", name="exposed-participant-questionnaire")
  106.      * @ParamConverter("participant", options={"mapping": {"code": "secretCode"}} )
  107.      *
  108.      * @param Participant $participant
  109.      * @param Request $request
  110.      * @param QuestionRepository $questionRepository
  111.      * @param InviteRegistrationController $inviteRegistrationController
  112.      * @param BuilderInterface $customQrCodeBuilder
  113.      * @param KernelInterface $kernel
  114.      * @return Response
  115.      */
  116.     public function getAnswersOfParticipant(
  117.         Participant $participant,
  118.         Request $request,
  119.         QuestionRepository $questionRepository,
  120.         InviteRegistrationController $inviteRegistrationController,
  121.         BuilderInterface $customQrCodeBuilder,
  122.         KernelInterface $kernel
  123.     ): Response
  124.     {
  125.         $activeQuestions $questionRepository->findBy(['status' => true], ['sortOrder' => 'ASC']);
  126.         foreach ($activeQuestions as $question) {
  127.             $questions[$question->getId()] = $question;
  128.         }
  129.         /** @noinspection PhpUndefinedVariableInspection */
  130.         $options['questions'] = $questions;
  131.         $options['participantAnswers'] = [];
  132.         foreach ($participant->getParticipantAnswers() as $answer) {
  133.             $options['participantAnswers'][$answer->getQuestion()->getId()] = $answer->getAnswer()->getId();
  134.         }
  135.         $form $this->createForm(AnswerQuestionsType::class, null$options);
  136.         $form->handleRequest($request);
  137.         if ($form->isSubmitted() && $form->isValid()) {
  138.             foreach ($participant->getParticipantAnswers() as $answer) {
  139.                 $this->em->remove($answer);
  140.             }
  141.             $participantQuestionsAndAnswers $form->getData();
  142.             $questions array_keys($participantQuestionsAndAnswers);
  143.             $answers array_values($participantQuestionsAndAnswers);
  144.             // used to be a simple search by arrays, but they were returning in the database's order
  145.             // now we have this BS
  146.             $questions $this->em->getRepository(Question::class)
  147.                 ->createQueryBuilder("question")
  148.                 ->where('question.id IN (:ids)')
  149.                 ->andWhere('question.status = :active')
  150.                 // Add the new orderBy clause specifying the order given in the ids array
  151.                 ->add("orderBy""FIELD(question.id, :ids)")
  152.                 // Set the primary keys of the register that you want to search for
  153.                 ->setParameter('ids'$questions)
  154.                 ->setParameter('active'true)
  155.                 ->getQuery()
  156.                 ->getResult();
  157.             $answers $this->em->getRepository(Answer::class)
  158.                 ->createQueryBuilder("a")
  159.                 ->where('a.id IN (:ids)')
  160.                 ->andWhere('a.status = :active')
  161.                 // Add the new orderBy clause specifying the order given in the ids array
  162.                 ->add("orderBy""FIELD(a.id, :ids)")
  163.                 // Set the primary keys of the register that you want to search for
  164.                 ->setParameter('ids'$answers)
  165.                 ->setParameter('active'true)
  166.                 ->getQuery()
  167.                 ->getResult();
  168.             $score 0;
  169.             foreach ($questions as $index => $question) {
  170.                 $participantAnswer = new ParticipantAnswer();
  171.                 $participantAnswer
  172.                     ->setParticipant($participant)
  173.                     ->setQuestion($question)
  174.                     ->setAnswer($answers[$index])
  175.                     ->setStatus(true);
  176.                 $score += $answers[$index]->getScore();
  177.                 $this->em->persist($participantAnswer);
  178.             }
  179.             $participant->setScore($score);
  180.             $this->em->persist($participant);
  181.             $this->em->flush();
  182.             $this->addFlash('sweetAlert', [
  183.                 'success' => $this->translator->trans('general.info.success')
  184.                 ]
  185.             );
  186.             $inviteRegistrationController->sendEmailUponRegistration($request$participant$customQrCodeBuilder$kernel);
  187.             return $this->redirectToRoute('exposed-participant');
  188.         }
  189.         return $this->render('checkin/answerQuestions.html.twig',
  190.             [
  191.                 'form' => $form->createView()
  192.             ]
  193.         );
  194.     }
  195.     /**
  196.      * @Route("/welcome/form/{code}", name="exposed_participant_registration_form")
  197.      * @ParamConverter("participant", options={"mapping": {"code": "secretCode"}} )
  198.      * @param Participant $participant
  199.      * @param Request $request
  200.      * @param InviteRegistrationController $inviteRegistrationController
  201.      * @param BuilderInterface $customQrCodeBuilder
  202.      * @param KernelInterface $kernel
  203.      * @return Response
  204.      */
  205.     public function registration(
  206.         Participant $participant,
  207.         Request $request,
  208.         InviteRegistrationController $inviteRegistrationController,
  209.         BuilderInterface $customQrCodeBuilder,
  210.         KernelInterface $kernel
  211.     ): Response
  212.     {
  213.         $customInputs $this->em->getRepository(CustomInput::class)->findBy(
  214.             ['active' => true'shownByDefault' => true], ['sortOrder' => 'ASC']
  215.         );
  216.         $form $this->createForm(RegistrationType::class, $participant->getCustomInputs(), [
  217.             'customInputs' => $customInputs,
  218.         ]);
  219.         $form->handleRequest($request);
  220.         if ($form->isSubmitted() && $form->isValid()) {
  221.             $data $form->getData();
  222.             foreach ($data as $index => $field) {
  223.                 if (is_object($field)) {
  224.                     if (get_class($field) === UploadedFile::class) {
  225.                         $filesToUpload[$index] = $field;
  226.                     }
  227.                 }
  228.             }
  229.             if (!empty($filesToUpload)) {
  230.                 if (!empty($participant->getCustomInputs())) {
  231.                     $filesToBeRemoved $participant->getParticipantFiles();
  232.                     foreach ($filesToBeRemoved as $fileToBeRemoved) {
  233.                         $this->em->remove($fileToBeRemoved);
  234.                     }
  235.                 }
  236.                 foreach ($filesToUpload as $index => $file) {
  237.                     $customInputThatHasFile $this->em->getRepository(CustomInput::class)->findOneBy(
  238.                         ['fieldName' => $index]
  239.                     );
  240.                     $declarationType $customInputThatHasFile->getDeclarationType();
  241.                     $participantFile = (new ParticipantFile())
  242.                         ->setParticipant($participant)
  243.                         ->setDeclarationType($declarationType)
  244.                         ->setFileName($declarationType->getTitle());
  245.                     $this->saveDeclarationPdf($participant$declarationType$file);
  246.                     $this->em->persist($participantFile);
  247.                     $this->em->flush();
  248.                 }
  249.             }
  250.             $participant->setCustomInputs($data);
  251.             $this->em->persist($participant);
  252.             $this->em->flush();
  253.             if ($this->em->getRepository(Setting::class)->findOneBy(['name' => Setting::QUESTIONS_KEY])->getValue()) {
  254.                 return $this->redirectToRoute('exposed-participant-questionnaire',
  255.                     ['code' => $participant->getSecretCode()]
  256.                 );
  257.             }
  258.             $this->addFlash('sweetAlert', [
  259.                     'success' => $this->translator->trans('general.info.success')
  260.                 ]);
  261.             $inviteRegistrationController->sendEmailUponRegistration($request$participant$customQrCodeBuilder$kernel);
  262.             return $this->redirectToRoute('exposed-participant');
  263.         }
  264.         return $this->renderForm('registration/registration.html.twig',
  265.         [
  266.             'form' => $form
  267.         ]);
  268.     }
  269.     /**
  270.      * @Route("/declaration-download/{declaration}", name="declaration-download")
  271.      *
  272.      * @param Declaration $declaration
  273.      * @return void
  274.      */
  275.     public function downloadDeclaration(Declaration $declaration)
  276.     {
  277.         // Configure Dompdf according to your needs
  278.         $pdfOptions = (new Options())
  279.             ->set('defaultFont''DejaVu Serif')
  280.             ->setIsRemoteEnabled(true)
  281.             ->setIsHtml5ParserEnabled(true);
  282.         // Instantiate Dompdf with our options
  283.         $dompdf = new Dompdf($pdfOptions);
  284.         $pattern '/font-family:[- ,a-zA-Z"]+">/';
  285.         $replacement '">';
  286.         $declarationHtml preg_replace(
  287.             $pattern,
  288.             $replacement,
  289.             html_entity_decode($declaration->getBody(), ENT_QUOTES|ENT_SUBSTITUTE'UTF-8')
  290.         );
  291.         $html $declarationHtml;
  292.         // Load HTML to Dompdf
  293.         $dompdf->loadHtml($html'utf-8');
  294.         // (Optional) Setup the paper size and orientation 'portrait' or 'portrait'
  295.         $dompdf->setPaper('A4''portrait');
  296.         // Render the HTML as PDF
  297.         $dompdf->render();
  298.         // Output the generated PDF to Browser (force download)
  299.         $dompdf->stream($declaration->getTitle().uniqid().".pdf", [
  300.             "Attachment" => true
  301.         ]);
  302.         exit();
  303.     }
  304.     /**
  305.      * @Route("/customer-declaration-download/{participantFile}", name="customer-declaration-download")
  306.      *
  307.      * @param ParticipantFile $participantFile
  308.      * @return BinaryFileResponse
  309.      */
  310.     public function downloadCustomerDeclaration(ParticipantFile $participantFile)
  311.     {
  312.         $response = new BinaryFileResponse(
  313.             $this->getDownloadPath($participantFile->getParticipant(), $participantFile->getDeclarationType())
  314.         );
  315.         $response->setContentDisposition(
  316.             ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  317.             $participantFile->getDeclarationType()->getTitle() . uniqid() . ".pdf"
  318.         );
  319.         return $response;
  320.     }
  321.     /**
  322.      * @Route("/upload-declaration/{participant}/{declaration}", name="upload-declaration")
  323.      *
  324.      * @param Request $request
  325.      * @param Participant $participant
  326.      * @param Declaration $declaration
  327.      * @return JsonResponse
  328.      */
  329.     public function uploadDeclaration(Request $requestParticipant $participantDeclaration $declaration): JsonResponse
  330.     {
  331.         try {
  332.             /** @var UploadedFile $attachment */
  333.             $pdf $request->files->get('file');
  334.             // digital signature not needed in 'backend'
  335.             /*if (!$this->isStringInFile($pdf, 'adbe.pkcs7.detached')) {
  336.                 return new JsonResponse(
  337.                     [
  338.                         'is_error' => true,
  339.                         'message' => $this->getTranslator()->trans('file.error.notSigned')
  340.                     ]
  341.                 );
  342.             }*/
  343.             $participantFile = (new ParticipantFile())
  344.                 ->setParticipant($participant)
  345.                 ->setDeclarationType($declaration)
  346.                 ->setFileName($declaration->getTitle());
  347.             $this->saveDeclarationPdf($participant$declaration$pdf);
  348.             $this->em->persist($participantFile);
  349.             $this->em->flush();
  350.         } catch (\Exception $exception) {
  351.             return new JsonResponse(
  352.                 [
  353.                     'is_error' => true,
  354.                     'message' => $exception->getMessage()
  355.                 ]
  356.             );
  357.         }
  358.         return new JsonResponse(
  359.             ['is_error' => false]
  360.         );
  361.     }
  362.     /**
  363.      * @param Participant $participant
  364.      * @param Declaration $declaration
  365.      * @param $pdf
  366.      * @return bool
  367.      */
  368.     public function saveDeclarationPdf(
  369.         Participant $participant,
  370.         Declaration $declaration,
  371.         $pdf
  372.     ) : bool {
  373.         $filepath $this->getDownloadPath($participant$declaration);
  374.         $this->createDownloadDirectory($participant);
  375.         $save file_put_contents(
  376.             $filepath,
  377.             file_get_contents($pdf->getPathName())
  378.         );
  379.         return $save;
  380.     }
  381.     public function getDownloadPath(Participant $participantDeclaration $declaration) : string
  382.     {
  383.         $filepath $this->getParameter('app.downloadPath')
  384.             . '/' $participant->getSecretCode()
  385.             . '/' $declaration->getId() . '.pdf';
  386.         return $filepath;
  387.     }
  388.     /**
  389.      * @param Participant $participant
  390.      * @return void
  391.      */
  392.     private function createDownloadDirectory(Participant $participant): void
  393.     {
  394.         $rootDir $this->getParameter('app.downloadPath');
  395.         $concurrentDirectory $rootDir.'/'.$participant->getSecretCode();
  396.         if (!is_dir($concurrentDirectory) && !mkdir($concurrentDirectory)) {
  397.             throw new \RuntimeException(sprintf('Directory "%s" was not created'$concurrentDirectory));
  398.         }
  399.     }
  400.     /**
  401.      * @Route("/customer-declaration-remove/{participantFile}/{secretParticipantCode}", name="customer-declaration-remove")
  402.      *
  403.      * @param string $secretParticipantCode
  404.      * @param ParticipantFile $participantFile
  405.      * @param UrlGeneratorInterface $router
  406.      * @return RedirectResponse
  407.      */
  408.     public function deleteCustomerDeclaration(
  409.         string $secretParticipantCode,
  410.         ParticipantFile $participantFile,
  411.         UrlGeneratorInterface $router
  412.     ): RedirectResponse {
  413.         if ($secretParticipantCode !== $participantFile->getParticipant()->getSecretCode()) {
  414.             $this->addFlash('error'$this->translator->trans('participant.error.invalidRequest'));
  415.             return $this->redirectToRoute('participant', ['id' => $participantFile->getParticipant()->getId()]);
  416.         }
  417.         $this->em->remove($participantFile);
  418.         $this->em->flush();
  419.         $url $router->generate('participant', ['id' => $participantFile->getParticipant()->getId()]);
  420.         return $this->redirect($url."#tabs-3");
  421.     }
  422.     /**
  423.      * @param UploadedFile $file
  424.      * @param string $string
  425.      * @return bool
  426.      */
  427.     function isStringInFile(UploadedFile $filestring $string): bool {
  428.         $valid false// init as false
  429.         $buffer file_get_contents($file->getPathName());
  430.         if ($buffer !== false) {
  431.             if (strpos($buffer$string) !== false) {
  432.                 $valid true;
  433.             }
  434.         }
  435.         return $valid;
  436.     }
  437.     /**
  438.      * @Route("/download/{settingName}", name="app_exposedcheckin_downloadFile")
  439.      * @ParamConverter("setting", options={"mapping": {"settingName" : "name"}})
  440.      *
  441.      * @param Setting $setting
  442.      * @return BinaryFileResponse
  443.      */
  444.     public function downloadFile(Setting $setting): ?BinaryFileResponse
  445.     {
  446.         if (empty($setting->getValue())) {
  447.             return null;
  448.         }
  449.         $response = new BinaryFileResponse(
  450.             $setting->getValue()
  451.         );
  452.         $response->setContentDisposition(
  453.             ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  454.             $setting->getName() . '.' pathinfo($setting->getValue())['extension']
  455.         );
  456.         return $response;
  457.     }
  458.     /**
  459.      * @Route("/gdpr", name="app_gdpr")
  460.      */
  461.     public function viewGdpr() {
  462.         $gdprSetting $this->em->find(Setting::class, Setting::GDPR_ID);
  463.         $gdprHtml $gdprSetting->getValue();
  464.         return $this->render(
  465.             'exposed_checkin/gdpr.html.twig',
  466.             [
  467.                 'gdpr' => $gdprHtml,
  468.             ]
  469.         );
  470.     }
  471. }