<?php
namespace App\Form;
use App\Component\Captcha\Form\CaptchaType;
use App\Component\Configuration\Util\Config;
use App\Entity\Company;
use App\Entity\JobAlert;
use App\Entity\JobAlertSetting;
use App\Entity\Option;
use App\Entity\OptionValue;
use App\Entity\Repository\OptionValueRepository;
use App\EventListener\FeatureFlagListener;
use App\Form\Setting\JobAlertSettingType;
use App\Service\SiteService;
use App\Transformer\StringToIntegerTransformer;
use App\Util\StringToEntityUtil;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManager;
use Flagception\Manager\FeatureManagerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\RangeType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
use Symfony\Component\Validator\Constraints\LessThanOrEqual;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
class JobAlertType extends AbstractType
{
private EntityManager $em;
private SelectDecorator $optionDecorator;
/**
* @var Option[]
*/
private $optionGroups;
/**
* @var bool|null
*/
private $showLocationAndRadius;
/**
* @var int|null
*/
private $privacyStatementStrategy;
/**
* @var string|null
*/
private $privacyStatementText;
private Config $config;
private ParameterBagInterface $parameterBag;
/**
* JobAlertType constructor.
*/
public function __construct(
EntityManager $entityManager,
SelectDecorator $optionDecorator,
ParameterBagInterface $parameterBag,
StringToEntityUtil $stringToEntityUtil,
SiteService $siteService,
FeatureManagerInterface $featureManager,
Config $config,
private readonly StringToIntegerTransformer $stringToIntegerTransformer,
) {
$this->em = $entityManager;
$this->config = $config;
$this->parameterBag = $parameterBag;
$optionGroups = $this->config->get('site_jobalert_selectable_options');
if (
$featureManager->isActive(FeatureFlagListener::FEATURE_MULTI_SITE) &&
($currentSite = $siteService->getSite()) &&
$this->config->get('site_jobalert_options_different_per_site')
) {
if (!empty($parameterBag->get('site_jobalert_selectable_options')[$currentSite->getId()])) {
$this->optionGroups = $stringToEntityUtil->stringToEntity($parameterBag->get('site_jobalert_selectable_options')[$currentSite->getId()]);
if ($this->optionGroups instanceof Collection) {
$this->optionGroups = $this->optionGroups->toArray();
}
}
} else {
$optionGroups = $stringToEntityUtil->stringToEntity($optionGroups);
if ($optionGroups instanceof ArrayCollection) {
$optionGroupIds = $optionGroups->map(function (Option $option) {
return $option->getId();
});
$this->optionGroups = $this->em->getRepository(Option::class)
->findBy(['id' => $optionGroupIds->toArray()], ['position' => 'ASC']);
}
}
$this->optionDecorator = $optionDecorator;
$this->showLocationAndRadius = $this->config->get('site_jobalert_show_location_and_radius');
$this->privacyStatementStrategy = $this->config->get('site_jobalert_privacy_statement_option');
$this->privacyStatementText = $this->config->get('site_jobalert_privacy_statement_text');
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (JobAlert::KEYWORD_FIELD_REQUIRED === $this->config->get('site_jobalert_show_search_field')) {
$builder->add('keyword', TextType::class, [
'label' => 'Function or keyword',
]);
} elseif (JobAlert::KEYWORD_FIELD_OPTIONAL === $this->config->get('site_jobalert_show_search_field')) {
$builder->add('keyword', TextType::class, [
'label' => 'Function or keyword',
'required' => false,
]);
}
$builder
->add('captcha', CaptchaType::class);
if ($this->parameterBag->get('site_jobalert_select_companies_enabled')) {
$builder->add('company', Select2EntityType::class, [
'class' => Company::class,
'required' => false,
'multiple' => false,
'label' => 'Company',
]);
}
if ($this->parameterBag->get('site_jobalert_select_options_enabled')) {
$builder
->add(
$this->optionDecorator->getFormBuilder(
$options['is_expanded'],
$this->optionGroups ? new ArrayCollection($this->optionGroups) : null
)
);
$builder->get('optionValues')
->addModelTransformer(new CallbackTransformer(
function (Collection $value) use ($options) {
$optionsMap = [];
/** @var OptionValue $optionValue */
foreach ($value->toArray() as $optionValue) {
if (isset($options['data']->isFeatured) && !$options['data']->isFeatured()) {
$optionsMap[$optionValue->getOption()?->getSlug()] = $optionValue->getId();
continue;
}
if (!isset($optionsMap[$optionValue->getOption()?->getSlug()])) {
$optionsMap[$optionValue->getOption()?->getSlug()] = [];
}
$optionsMap[$optionValue->getOption()?->getSlug()][] = $optionValue->getId();
}
return $optionsMap;
}, function ($value) {
$optionValues = new ArrayCollection();
if (!\is_array($value)) {
throw new TransformationFailedException('Value is not an array');
}
/** @var OptionValueRepository $optionValueRepo */
$optionValueRepo = $this->em->getRepository(OptionValue::class);
$optionValuesIdsMap = array_values($value);
$optionValuesIds = [];
foreach ($optionValuesIdsMap as $optionValuesGroup) {
// Array merge if field is multiple => true
if (\is_array($optionValuesGroup)) {
$optionValuesIds = array_merge($optionValuesIds, $optionValuesGroup);
}
// Array push if field is multiple => false
if (is_numeric($optionValuesGroup)) {
$optionValuesIds[] = $optionValuesGroup;
}
}
foreach ($optionValuesIds as $id) {
$optionValue = $optionValueRepo->find($id);
if (!$optionValue) {
throw new TransformationFailedException(sprintf('OptionValue with id %s does not exist', $id));
}
$optionValues->add($optionValue);
}
return $optionValues;
}
));
}
if (empty($this->config->get('site_jobalert_default_interval'))) {
$builder
->add('frequency', ChoiceType::class, [
'choices' => [
'Daily' => JobAlert::FREQUENCY_DAILY,
'Weekly' => JobAlert::FREQUENCY_WEEKLY,
'Monthly' => JobAlert::FREQUENCY_MONTHLY,
],
'label' => 'How often do you wish to receive vacancies in your mailbox?',
]);
}
$personalizationStrategy = $this->config->get('site_jobalert_enable_personalizations');
if ($personalizationStrategy >= JobAlertSettingType::JOB_ALERT_PERSONALIZATION_YES) {
$isRequired = JobAlertSettingType::JOB_ALERT_PERSONALIZATION_YES_REQUIRED === $personalizationStrategy;
$builder
->add('firstName', TextType::class, [
'required' => $isRequired,
'row_attr' => [
'class' => 'form-group w-50',
],
])
->add('lastName', TextType::class, [
'required' => $isRequired,
'row_attr' => [
'class' => 'form-group w-50',
],
]);
}
$builder
->add('email', EmailType::class);
if ($this->showLocationAndRadius) {
$minRange = 5;
$maxRange = 100;
$builder->add('location', TextType::class, [
'attr' => [
'class' => 'jobalert-location',
'id' => 'jobalertbundle_jobalert_location',
],
]);
$builder->add('locationLatitude', HiddenType::class, [
'mapped' => false,
'attr' => [
'data-component' => 'location[latitude]',
],
]);
$builder->add('locationLongitude', HiddenType::class, [
'mapped' => false,
'attr' => [
'data-component' => 'location[longitude]',
],
]);
$builder->add('locationName', HiddenType::class, [
'mapped' => false,
'attr' => [
'data-component' => 'location[name]',
],
]);
$builder->add('range', RangeType::class, [
'label' => 'Range',
'attr' => [
'min' => $minRange,
'max' => $maxRange,
'step' => 5,
'data-component-source' => 'range',
],
'constraints' => [
new NotNull(),
new GreaterThanOrEqual($minRange),
new LessThanOrEqual($maxRange),
],
]);
$builder->get('range')->addViewTransformer($this->stringToIntegerTransformer);
}
if (JobAlertSetting::PRIVACY_STATEMENT_STRATEGY_TEXT_AND_CHECKBOX === $this->privacyStatementStrategy) {
$builder->add('privacy_statement', CheckboxHtmlType::class, [
'label' => strip_tags((string) $this->privacyStatementText, ['a', 'b', 'strong', 'em', 'i']),
'mapped' => false,
'required' => true,
'constraints' => [
new NotBlank(),
],
]);
}
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'data_class' => JobAlert::class,
'attr' => ['class' => 'recaptcha-form', 'novalidate' => true],
])
->setRequired(['is_expanded']);
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'jobalertbundle_jobalert';
}
}