src/Decorator/ApplicantDecorator.php line 81

Open in your IDE?
  1. <?php
  2. namespace App\Decorator;
  3. use App\Component\Captcha\Form\CaptchaType;
  4. use App\Component\Configuration\Util\Config;
  5. use App\Entity\Applicant;
  6. use App\Entity\ApplicantForm;
  7. use App\Entity\Company;
  8. use App\Entity\GdprStatement;
  9. use App\Entity\KnockoutQuestion;
  10. use App\Entity\PhoneNumber;
  11. use App\Entity\Repository\ApplicantFormRepository;
  12. use App\Entity\Status;
  13. use App\Entity\Vacancy;
  14. use App\Entity\VacancyQuestion;
  15. use App\Event\ApplicantEvent;
  16. use App\EventListener\FeatureFlagListener;
  17. use App\Form\CheckboxHtmlType;
  18. use App\Util\StringToEntityUtil;
  19. use Cocur\Slugify\Slugify;
  20. use Doctrine\ORM\EntityManagerInterface;
  21. use Flagception\Manager\FeatureManagerInterface;
  22. use League\Flysystem\Adapter\Local;
  23. use MobileDetectBundle\DeviceDetector\MobileDetector;
  24. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  25. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  26. use Symfony\Component\Form\CallbackTransformer;
  27. use Symfony\Component\Form\Exception\InvalidArgumentException;
  28. use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  29. use Symfony\Component\Form\Extension\Core\Type\FormType;
  30. use Symfony\Component\Form\Extension\Core\Type\TextType;
  31. use Symfony\Component\Form\FormBuilderInterface;
  32. use Symfony\Component\Form\FormFactoryInterface;
  33. use Symfony\Component\Form\FormInterface;
  34. use Symfony\Component\HttpFoundation\File\Exception\FileException;
  35. use Symfony\Component\HttpFoundation\File\UploadedFile;
  36. use Symfony\Contracts\Translation\TranslatorInterface;
  37. use Vich\UploaderBundle\Metadata\MetadataReader;
  38. class ApplicantDecorator extends FormDecorator
  39. {
  40.     private Local $adapter;
  41.     private EntityManagerInterface $parentEntityManager;
  42.     private ApplicantFormRetriever $applicantFormRetriever;
  43.     public function __construct(
  44.         EntityManagerInterface $manager,
  45.         FormFactoryInterface $formFactory,
  46.         EventDispatcherInterface $eventDispatcher,
  47.         ParameterBagInterface $parameterBag,
  48.         StringToEntityUtil $stringToEntityUtil,
  49.         MobileDetector $mobileDetect,
  50.         FeatureManagerInterface $featureManager,
  51.         Local $applicantUploadAdapter,
  52.         $parentEntityManager,
  53.         Config $config,
  54.         protected MetadataReader $vichMetadataReader,
  55.         ApplicantFormRetriever $applicantFormRetriever,
  56.         TranslatorInterface $translator,
  57.     ) {
  58.         parent::__construct(
  59.             $manager,
  60.             $formFactory,
  61.             $eventDispatcher,
  62.             $parameterBag,
  63.             $stringToEntityUtil,
  64.             $mobileDetect,
  65.             $featureManager,
  66.             $config,
  67.             $vichMetadataReader,
  68.             $translator
  69.         );
  70.         $this->adapter $applicantUploadAdapter;
  71.         $this->parentEntityManager $parentEntityManager;
  72.         $this->applicantFormRetriever $applicantFormRetriever;
  73.     }
  74.     /** @throws \JsonException */
  75.     public function getForm(Applicant $applicantVacancy $vacancy, ?string $action null): FormInterface
  76.     {
  77.         return $this->createForm(
  78.             $this->applicantFormRetriever->retrieve($vacancy),
  79.             $applicant,
  80.             $action,
  81.             $vacancy
  82.         )->getForm();
  83.     }
  84.     /**
  85.      * @throws \JsonException
  86.      */
  87.     public function handleOpenForm(FormInterface $formApplicant $applicant): bool
  88.     {
  89.         $applicantForm $applicant->getApplicantForm()
  90.             ?? $this->manager->getRepository(ApplicantForm::class)->findOneBy(['openForm' => true]);
  91.         if (!$applicantForm instanceof ApplicantForm) {
  92.             return false;
  93.         }
  94.         $applicantForm json_decode($applicantForm->getFields(), true512\JSON_THROW_ON_ERROR);
  95.         $data = [];
  96.         foreach ($applicantForm as $item) {
  97.             if ($value $form[$item['name']]->getData()) {
  98.                 if ($value instanceof Company) {
  99.                     $value $value->getId();
  100.                 }
  101.                 $data[$item['name']] = $value;
  102.             }
  103.         }
  104.         $applicant->setData(json_encode($data\JSON_THROW_ON_ERROR));
  105.         $applicant->setApplicantSendToApplicantMail(false);
  106.         if (!$this->featureManager->isActive(FeatureFlagListener::FEATURE_CHILD_SITE) &&
  107.             $defaultStatusObject $this->config->get('site_applicant_default_status')) {
  108.             /** @var Status $defaultStatus */
  109.             $defaultStatus $this->manager->getRepository(Status::class)->find($defaultStatusObject->getId());
  110.             if ($defaultStatus) {
  111.                 $applicant->setStatus($defaultStatus);
  112.             }
  113.         }
  114.         $this->eventDispatcher->dispatch(new ApplicantEvent($applicant$form), ApplicantEvent::EVENT_PRE_PERSIST);
  115.         $this->manager->persist($applicant);
  116.         $this->manager->flush();
  117.         $this->eventDispatcher->dispatch(new ApplicantEvent($applicant$form), ApplicantEvent::EVENT_POST_PERSIST);
  118.         return true;
  119.     }
  120.     public function getOpenForm(Applicant $applicant, ?string $action null, ?string $name null, ?string $locale null): ?FormInterface
  121.     {
  122.         $applicantForm $applicant->getApplicantForm() ?? $this->fetchOpenApplicationForm();
  123.         if (!$applicantForm instanceof ApplicantForm) {
  124.             return null;
  125.         }
  126.         try {
  127.             $form $this->createForm(
  128.                 $applicantForm,
  129.                 $applicant,
  130.                 $action,
  131.                 null,
  132.                 $name,
  133.                 $locale
  134.             );
  135.             $applicant->setApplicantForm($applicantForm);
  136.             $applicantForm->addApplicant($applicant);
  137.             return $form->getForm();
  138.         } catch (InvalidArgumentException|\JsonException) {
  139.             // Name is most likely invalid due to xss violations, should return absolutely nothing.
  140.             return null;
  141.         }
  142.     }
  143.     /**
  144.      * @throws \JsonException
  145.      */
  146.     public function createForm(
  147.         ApplicantForm $applicantForm,
  148.         Applicant $applicant,
  149.         ?string $action,
  150.         ?Vacancy $vacancy,
  151.         ?string $name null,
  152.         ?string $locale null
  153.     ): FormBuilderInterface {
  154.         if (!$name) {
  155.             $name $vacancy 'form' 'open';
  156.         }
  157.         $form $this->formFactory->createNamedBuilder(
  158.             $name,
  159.             FormType::class,
  160.             $applicant,
  161.             [
  162.                 'action' => $action,
  163.                 'attr' => [
  164.                     'class' => 'recaptcha-form',
  165.                     'data-component' => 'Form',
  166.                     'data-form-type' => $applicantForm->isOpenForm() ? 'open-apply-form' 'apply-form',
  167.                 ],
  168.             ]
  169.         );
  170.         if ($vacancy instanceof Vacancy
  171.             && $this->featureManager->isActive(FeatureFlagListener::FEATURE_VACANCY_KNOCKOUT_QUESTIONS)
  172.             && !empty($vacancy->getKnockoutQuestions())
  173.         ) {
  174.             foreach ($vacancy->getKnockoutQuestions() as $knockoutQuestion) {
  175.                 $form->add(sprintf('knockoutQuestion_%s'$knockoutQuestion->getId()), ChoiceType::class, [
  176.                     'label' => $knockoutQuestion,
  177.                     'expanded' => true,
  178.                     'mapped' => false,
  179.                     'choices' => [
  180.                         'Yes' => KnockoutQuestion::ANSWER_YES,
  181.                         'No' => KnockoutQuestion::ANSWER_NO,
  182.                     ],
  183.                     'choice_attr' => [
  184.                         'required' => true,
  185.                     ],
  186.                     'data' => KnockoutQuestion::ANSWER_YES,
  187.                 ]);
  188.             }
  189.         }
  190.         $form $this->decorateFields($formjson_decode($applicantForm->getFields(), true512\JSON_THROW_ON_ERROR), $applicant$locale);
  191.         if ($this->config->get('site_applicant_apply_via_linkedin')) {
  192.             $form->add('linkedinProfileUrl');
  193.         }
  194.         if ($vacancy instanceof Vacancy && !empty($vacancy->getVacancyQuestions())) {
  195.             $form $this->processVacancyQuestions($form$vacancy);
  196.         }
  197.         if ($applicantForm->hasPrivacyStatement()) {
  198.             $form->add('acceptedPrivacyPolicy'CheckboxHtmlType::class, [
  199.                 'label' => $applicantForm->getPrivacyStatementText(),
  200.                 'data' => false,
  201.                 'required' => $applicantForm->isPrivacyStatementIsRequired(),
  202.             ]);
  203.         }
  204.         if (
  205.             $this->featureManager->isActive(FeatureFlagListener::FEATURE_GDPR) &&
  206.             !$applicantForm->getGdprStatements()->isEmpty()
  207.         ) {
  208.             $defaultValue $this->config->get('applicant_fallback_gdpr');
  209.             if ($defaultValue) {
  210.                 $defaultValue $this->manager->getRepository(GdprStatement::class)->find($defaultValue->getId());
  211.             }
  212.             if (=== $applicantForm->getGdprStatements()->count()) {
  213.                 $form->add(
  214.                     'gdpr',
  215.                     CheckboxHtmlType::class,
  216.                     [
  217.                         'label' => $applicantForm->getGdprStatements()->first()->getStatement(),
  218.                         'required' => $this->config->get('applicant_gdpr_required'),
  219.                     ]
  220.                 );
  221.                 $form->get('gdpr')->addModelTransformer(new CallbackTransformer(
  222.                     function ($value) {
  223.                         return !empty($value);
  224.                     },
  225.                     function ($value) use ($applicantForm$defaultValue) {
  226.                         if ($value) {
  227.                             return $applicantForm->getGdprStatements()->first();
  228.                         }
  229.                         return $defaultValue;
  230.                     }
  231.                 ));
  232.             } else {
  233.                 $firstInCollection = !$applicantForm->getGdprStatements()->isEmpty() ?
  234.                     $applicantForm->getGdprStatements()->first()->getId() : null;
  235.                 $defaultValue $defaultValue ?? $firstInCollection;
  236.                 $form->add('gdpr'ChoiceType::class, [
  237.                     'label' => false,
  238.                     'expanded' => true,
  239.                     'multiple' => false,
  240.                     'choices' => array_column($applicantForm->getGdprStatements()->toArray(), 'id''statement'),
  241.                     'required' => $this->config->get('applicant_gdpr_required'),
  242.                     'data' => $defaultValue?->getId(),
  243.                     'placeholder' => false,
  244.                 ]);
  245.                 $form->get('gdpr')->addModelTransformer(new CallbackTransformer(
  246.                     function ($value) {
  247.                         return $value;
  248.                     },
  249.                     function ($value) use ($applicantForm$defaultValue) {
  250.                         if (!$value) {
  251.                             return $defaultValue;
  252.                         }
  253.                         $gdprStatements $applicantForm->getGdprStatements();
  254.                         $gdprStatements->filter(function (GdprStatement $gdprStatement) use ($value) {
  255.                             return $value === $gdprStatement->getId();
  256.                         });
  257.                         return $gdprStatements->first();
  258.                     }
  259.                 ));
  260.             }
  261.         }
  262.         if ($applicantForm->hasCaptcha()) {
  263.             $form->add('captcha'CaptchaType::class);
  264.         }
  265.         return $form;
  266.     }
  267.     public function processVacancyQuestions(FormBuilderInterface $formVacancy $vacancy): FormBuilderInterface
  268.     {
  269.         foreach ($vacancy->getVacancyQuestions() as $question) {
  270.             switch ($question->getType()) {
  271.                 case 1:
  272.                 case 2:
  273.                 case 3:
  274.                     $choices = [];
  275.                     foreach ($question->getAnswers() as $answer) {
  276.                         $choices[$answer->getValue()] = $answer->getExternalReference();
  277.                     }
  278.                     $form->add(sprintf('vacancyQuestion%s'$question->getExternalReference()), ChoiceType::class, [
  279.                         'multiple' => === $question->getType(),
  280.                         'expanded' => \in_array($question->getType(), [23], true),
  281.                         'choices' => $choices,
  282.                         'mapped' => false,
  283.                         'label' => $question->getValue(),
  284.                     ]);
  285.                     break;
  286.                 case 4:
  287.                     $form->add(sprintf('vacancyQuestion%s'$question->getExternalReference()), TextType::class, [
  288.                         'mapped' => false,
  289.                         'required' => true,
  290.                         'label' => $question->getValue(),
  291.                     ]);
  292.                     break;
  293.             }
  294.         }
  295.         return $form;
  296.     }
  297.     /**
  298.      * @throws \JsonException
  299.      */
  300.     public function handleForm(FormInterface $formApplicant $applicantVacancy $vacancy): bool
  301.     {
  302.         $applicantForm $this->applicantFormRetriever->retrieve($vacancy);
  303.         $data = [];
  304.         $questionAnswers = [];
  305.         foreach (json_decode($applicantForm->getFields(), true512\JSON_THROW_ON_ERROR) as $item) {
  306.             if ($value $form[$item['name']]->getData()) {
  307.                 if ($value instanceof UploadedFile && !$form[$item['name']]->getConfig()->getMapped()) {
  308.                     $originalFilename pathinfo($value->getClientOriginalName(), \PATHINFO_FILENAME);
  309.                     // this is needed to safely include the file name as part of the URL
  310.                     $safeFilename = (new Slugify())->slugify($originalFilename);
  311.                     $newFilename sprintf('%s-%s.%s'$safeFilenameuniqid(''true), $value->guessExtension());
  312.                     try {
  313.                         $value->move(
  314.                             $this->adapter->getPathPrefix(),
  315.                             $newFilename
  316.                         );
  317.                     } catch (FileException) {
  318.                     }
  319.                     $value '__FILE__'.$newFilename;
  320.                 }
  321.                 if ($value instanceof PhoneNumber && !method_exists($applicant'set'.$item['name'])) {
  322.                     $value serialize($value);
  323.                 }
  324.                 $data[$item['name']] = $value;
  325.             }
  326.         }
  327.         if (!empty($vacancy->getVacancyQuestions())) {
  328.             foreach ($vacancy->getVacancyQuestions() as $question) {
  329.                 /** @var VacancyQuestion $question */
  330.                 if ($value $form[sprintf('vacancyQuestion%s'$question->getExternalReference())]->getData()) {
  331.                     $questionAnswers[$question->getExternalReference()] = $value;
  332.                 }
  333.             }
  334.         }
  335.         $applicant->setData(json_encode($data\JSON_THROW_ON_ERROR));
  336.         $applicant->setQuestionAnswerData(json_encode($questionAnswers\JSON_THROW_ON_ERROR));
  337.         $applicant->setVacancy($vacancy);
  338.         $applicant->setApplicantSendToApplicantMail(false);
  339.         if (!$this->featureManager->isActive(FeatureFlagListener::FEATURE_CHILD_SITE) &&
  340.             $defaultStatusObject $this->config->get('site_applicant_default_status')) {
  341.             /** @var Status $defaultStatus */
  342.             $defaultStatus $this->manager->getRepository(Status::class)->find($defaultStatusObject->getId());
  343.             if ($defaultStatus) {
  344.                 $applicant->setStatus($defaultStatus);
  345.             }
  346.         }
  347.         $this->eventDispatcher->dispatch(
  348.             new ApplicantEvent($applicant$formfalse),
  349.             ApplicantEvent::EVENT_PRE_PERSIST
  350.         );
  351.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_APPLICANT_DONT_SAVE_TO_DB)) {
  352.             $this->eventDispatcher->dispatch(
  353.                 new ApplicantEvent($applicant$form),
  354.                 ApplicantEvent::EVENT_POST_PERSIST
  355.             );
  356.             return true;
  357.         }
  358.         $fileName $applicant->getFileName();
  359.         $fileSize $applicant->getFileSize();
  360.         $fileNameMotivation $applicant->getFileNameMotivation();
  361.         $fileSizeMotivation $applicant->getFileSizeMotivation();
  362.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_VACANCY_KNOCKOUT_QUESTIONS)
  363.             && !empty($vacancy->getKnockoutQuestions())
  364.         ) {
  365.             $answerData = [];
  366.             $rejectApplicant false;
  367.             foreach ($vacancy->getKnockoutQuestions() as $knockoutQuestion) {
  368.                 $questionAnswer $form->get(sprintf('knockoutQuestion_%s'$knockoutQuestion->getId()))->getData();
  369.                 $answerData[$knockoutQuestion->getId()] = $questionAnswer;
  370.                 if (KnockoutQuestion::ANSWER_NO === $questionAnswer) {
  371.                     $rejectApplicant true;
  372.                 }
  373.             }
  374.             $applicant
  375.                 ->setRejectedByKnockout($rejectApplicant)
  376.                 ->setKnockoutQuestionAnswers($answerData);
  377.         }
  378.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_CHILD_SITE)) {
  379.             $this->parentEntityManager->persist($applicant);
  380.             $this->parentEntityManager->flush();
  381.         } else {
  382.             $this->manager->persist($applicant);
  383.             $this->manager->flush();
  384.         }
  385.         if ($fileName && $fileSize) {
  386.             $applicant->setFileName($fileName);
  387.             $applicant->setFileSize($fileSize);
  388.         }
  389.         if ($fileNameMotivation && $fileSizeMotivation) {
  390.             $applicant->setFileNameMotivation($fileNameMotivation);
  391.             $applicant->setFileSizeMotivation($fileSizeMotivation);
  392.         }
  393.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_CHILD_SITE)) {
  394.             $this->parentEntityManager->persist($applicant);
  395.             $this->parentEntityManager->flush();
  396.         } else {
  397.             $this->manager->persist($applicant);
  398.             $this->manager->flush();
  399.         }
  400.         $this->eventDispatcher->dispatch(
  401.             new ApplicantEvent($applicant$form),
  402.             ApplicantEvent::EVENT_POST_PERSIST
  403.         );
  404.         return true;
  405.     }
  406.     public function fetchOpenApplicationForm(int $applicantFormId 0): ?ApplicantForm
  407.     {
  408.         /** @var ApplicantFormRepository $applicantFormRepository */
  409.         $applicantFormRepository $this->manager->getRepository(ApplicantForm::class);
  410.         if (!== $applicantFormId
  411.             && ($applicantForm $applicantFormRepository->find($applicantFormId)) instanceof ApplicantForm
  412.         ) {
  413.             return $applicantForm;
  414.         }
  415.         $applicantForm $this->config->get('site_vacancy_mobile_open_application_form');
  416.         if ($this->mobileDetect->isMobile() && $applicantForm instanceof ApplicantForm) {
  417.             return $applicantForm;
  418.         }
  419.         return $applicantFormRepository->findOneBy(['openForm' => true]);
  420.     }
  421. }