vendor/dachcom-digital/formbuilder/src/FormBuilderBundle/EventSubscriber/FormBuilderSubscriber.php line 83

Open in your IDE?
  1. <?php
  2. namespace FormBuilderBundle\EventSubscriber;
  3. use FormBuilderBundle\Validator\Constraints\DynamicMultiFileNotBlank;
  4. use FormBuilderBundle\Model\FieldDefinitionInterface;
  5. use FormBuilderBundle\Model\FormFieldContainerDefinitionInterface;
  6. use FormBuilderBundle\Model\FormFieldDefinitionInterface;
  7. use FormBuilderBundle\Model\FormFieldDynamicDefinitionInterface;
  8. use FormBuilderBundle\Configuration\Configuration;
  9. use FormBuilderBundle\Form\Data\FormDataInterface;
  10. use FormBuilderBundle\Event\Form\PostSetDataEvent;
  11. use FormBuilderBundle\Event\Form\PreSetDataEvent;
  12. use FormBuilderBundle\Event\Form\PreSubmitEvent;
  13. use FormBuilderBundle\FormBuilderEvents;
  14. use FormBuilderBundle\Validation\ConditionalLogic\Dispatcher\Dispatcher;
  15. use FormBuilderBundle\Validation\ConditionalLogic\Dispatcher\Module\Data\DataInterface;
  16. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  17. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  18. use Symfony\Component\Form\FormEvent;
  19. use Symfony\Component\Form\FormEvents;
  20. use Symfony\Component\Form\FormInterface;
  21. use Symfony\Component\Form\FormRegistryInterface;
  22. use Symfony\Component\Validator\Constraints\NotBlank;
  23. class FormBuilderSubscriber implements EventSubscriberInterface
  24. {
  25.     protected Configuration $configuration;
  26.     protected EventDispatcherInterface $eventDispatcher;
  27.     protected Dispatcher $dispatcher;
  28.     protected FormRegistryInterface $formRegistry;
  29.     private array $availableConstraints;
  30.     private array $availableFormTypes;
  31.     public function __construct(
  32.         Configuration $configuration,
  33.         EventDispatcherInterface $eventDispatcher,
  34.         Dispatcher $dispatcher,
  35.         FormRegistryInterface $formRegistry
  36.     ) {
  37.         $this->configuration $configuration;
  38.         $this->eventDispatcher $eventDispatcher;
  39.         $this->dispatcher $dispatcher;
  40.         $this->formRegistry $formRegistry;
  41.         $this->availableConstraints $this->configuration->getAvailableConstraints();
  42.         $this->availableFormTypes $this->configuration->getConfig('types');
  43.     }
  44.     public static function getSubscribedEvents(): array
  45.     {
  46.         return [
  47.             FormEvents::PRE_SET_DATA  => ['onPreSetData'],
  48.             FormEvents::POST_SET_DATA => ['onPostSetData'],
  49.             FormEvents::PRE_SUBMIT    => ['onPreSubmit']
  50.         ];
  51.     }
  52.     /**
  53.      * @throws \Exception
  54.      */
  55.     public function getFormOptions(FormEvent $event): ?array
  56.     {
  57.         $form $event->getForm();
  58.         if (!$form->has('formRuntimeData')) {
  59.             throw new \Exception('No runtime options in form found.');
  60.         }
  61.         $data $form->get('formRuntimeData')->getData();
  62.         // remove legacy email config node.
  63.         if (isset($data['email'])) {
  64.             unset($data['email']);
  65.         }
  66.         return $data;
  67.     }
  68.     /**
  69.      * @throws \Exception
  70.      */
  71.     public function onPreSetData(FormEvent $event): void
  72.     {
  73.         $preSetDataEvent = new PreSetDataEvent($event$this->getFormOptions($event));
  74.         $this->eventDispatcher->dispatch($preSetDataEventFormBuilderEvents::FORM_PRE_SET_DATA);
  75.     }
  76.     /**
  77.      * @throws \Exception
  78.      */
  79.     public function onPostSetData(FormEvent $event): void
  80.     {
  81.         $postSetDataEvent = new PostSetDataEvent($event$this->getFormOptions($event));
  82.         $this->eventDispatcher->dispatch($postSetDataEventFormBuilderEvents::FORM_POST_SET_DATA);
  83.         $this->populateForm($event->getForm(), $event->getData());
  84.     }
  85.     /**
  86.      * @throws \Exception
  87.      */
  88.     public function onPreSubmit(FormEvent $event): void
  89.     {
  90.         $preSubmitEvent = new PreSubmitEvent($event$this->getFormOptions($event));
  91.         $this->eventDispatcher->dispatch($preSubmitEventFormBuilderEvents::FORM_PRE_SUBMIT);
  92.         $this->populateForm($event->getForm(), $event->getForm()->getData(), $event->getData());
  93.     }
  94.     /**
  95.      * @throws \Exception
  96.      */
  97.     private function populateForm(FormInterface $formFormDataInterface $formData, array $data = []): void
  98.     {
  99.         $orderedFields $formData->getFormDefinition()->getFields();
  100.         usort($orderedFields, function (FieldDefinitionInterface $aFieldDefinitionInterface $b) {
  101.             return ($a->getOrder() < $b->getOrder()) ? -1;
  102.         });
  103.         $data $this->preFillData($orderedFields$data);
  104.         $formRuntimeOptions = !$form->has('formRuntimeData') ? [] : $form->get('formRuntimeData')->getData();
  105.         $conditionalLogicBaseOptions = [
  106.             'formRuntimeOptions' => $formRuntimeOptions,
  107.             'conditionalLogic'   => $formData->getFormDefinition()->getConditionalLogic()
  108.         ];
  109.         foreach ($orderedFields as $field) {
  110.             if ($field instanceof FormFieldDynamicDefinitionInterface) {
  111.                 $formTypeData $this->addDynamicField($field);
  112.                 $form->add($formTypeData['name'], $formTypeData['type'], $formTypeData['options']);
  113.             } elseif ($field instanceof FormFieldContainerDefinitionInterface) {
  114.                 $conditionalLogicOptions array_merge(['formData' => $data'field' => null], $conditionalLogicBaseOptions);
  115.                 $formTypeData $this->addFormBuilderContainerField($field$conditionalLogicOptions);
  116.                 $form->add($formTypeData['name'], $formTypeData['type'], $formTypeData['options']);
  117.             } else {
  118.                 $conditionalLogicOptions array_merge(['formData' => $data'field' => $field], $conditionalLogicBaseOptions);
  119.                 $formTypeData $this->addFormBuilderField($field$conditionalLogicOptions);
  120.                 $form->add($formTypeData['name'], $formTypeData['type'], $formTypeData['options']);
  121.             }
  122.         }
  123.     }
  124.     /**
  125.      * @throws \Exception
  126.      */
  127.     private function addFormBuilderContainerField(FormFieldContainerDefinitionInterface $fieldContainer, array $conditionalLogicOptions): array
  128.     {
  129.         $fields = [];
  130.         foreach ($fieldContainer->getFields() as $subField) {
  131.             $fields[] = $this->addFormBuilderField($subFieldarray_merge($conditionalLogicOptions, ['field' => $subField]));
  132.         }
  133.         $typeClass $this->configuration->getContainerFieldClass($fieldContainer->getSubType());
  134.         $configuration $fieldContainer->getConfiguration();
  135.         $containerAttributes $configuration['attr'] ?? [];
  136.         $containerClasses = ['formbuilder-container formbuilder-container-' strtolower($fieldContainer->getSubType())];
  137.         if (isset($containerAttributes['class']) && is_string($containerAttributes['class'])) {
  138.             $containerClasses[] = $containerAttributes['class'];
  139.         }
  140.         // merge core and attributes class definition
  141.         $containerAttributes['class'] = implode(' '$containerClasses);
  142.         // options enrichment: conditional logic class mapping
  143.         $conditionalContainerClassData $this->dispatchConditionalLogicModule('form_type_classes'array_merge($conditionalLogicOptions, ['field' => $fieldContainer]));
  144.         if ($conditionalContainerClassData->hasData()) {
  145.             $attrDataTemplate = isset($containerAttributes['data-template']) ? [$containerAttributes['data-template']] : [];
  146.             $attrDataTemplate array_merge($attrDataTemplate$conditionalContainerClassData->getData());
  147.             $containerAttributes['data-template'] = implode(' '$attrDataTemplate);
  148.         }
  149.         return [
  150.             'name'    => $fieldContainer->getName(),
  151.             'type'    => $typeClass,
  152.             'options' => [
  153.                 'attr'                      => $containerAttributes,
  154.                 'formbuilder_configuration' => $configuration,
  155.                 'entry_options'             => [
  156.                     'fields'         => $fields,
  157.                     'container_type' => $fieldContainer->getSubType()
  158.                 ]
  159.             ]
  160.         ];
  161.     }
  162.     /**
  163.      * @throws \Exception
  164.      */
  165.     private function addFormBuilderField(FormFieldDefinitionInterface $field, array $conditionalLogicOptions): array
  166.     {
  167.         $options $field->getOptions();
  168.         $optional $field->getOptional();
  169.         $object $this->formRegistry->getType($this->availableFormTypes[$field->getType()]['class'])->getOptionsResolver();
  170.         $availableOptions $object->getDefinedOptions();
  171.         $constraints = [];
  172.         $constraintNames = [];
  173.         $templateClasses = [];
  174.         // options enrichment: tweak preferred choice options
  175.         if (in_array($field->getType(), $this->getChoiceFieldTypes(), true)) {
  176.             if (isset($options['multiple']) && $options['multiple'] === false
  177.                 && isset($options['data'])
  178.                 && is_array($options['data'])
  179.                 && !empty($options['data'])
  180.             ) {
  181.                 $options['data'] = $options['data'][0];
  182.             }
  183.         }
  184.         // options enrichment: add constraints
  185.         if (in_array('constraints'$availableOptions)) {
  186.             $conditionalConstraintData $this->dispatchConditionalLogicModule('constraints'$conditionalLogicOptions);
  187.             // add field constraints to data attribute since we need them for the frontend cl applier.
  188.             foreach ($field->getConstraints() as $constraint) {
  189.                 $constraintNames[] = $constraint['type'];
  190.             }
  191.             if ($conditionalConstraintData->hasData()) {
  192.                 $constraints $conditionalConstraintData->getData();
  193.                 $options['constraints'] = $constraints;
  194.             }
  195.         }
  196.         $options['attr']['data-initial-constraints'] = join(','$constraintNames);
  197.         // options enrichment: check required state
  198.         if (in_array('required'$availableOptions)) {
  199.             $options['required'] = count(
  200.                     array_filter($constraints, function ($constraint) {
  201.                         return $constraint instanceof NotBlank || $constraint instanceof DynamicMultiFileNotBlank;
  202.                     })
  203.                 ) === 1;
  204.         }
  205.         // options enrichment: check for custom radio / checkbox layout
  206.         if ($this->configuration->getConfigFlag('use_custom_radio_checkbox') === true) {
  207.             if (in_array('label_attr'$availableOptions)) {
  208.                 if ($field->getType() === 'checkbox') {
  209.                     $options['label_attr'] = ['class' => 'checkbox-custom'];
  210.                 } elseif (in_array($field->getType(), $this->getChoiceFieldTypes())) {
  211.                     if (isset($options['expanded']) && $options['expanded'] === true) {
  212.                         $options['label_attr'] = ['class' => $options['multiple'] === true 'checkbox-custom' 'radio-custom'];
  213.                     }
  214.                 }
  215.             }
  216.         }
  217.         // options enrichment: set template
  218.         if (isset($optional['template'])) {
  219.             $templateClasses[] = $optional['template'];
  220.         }
  221.         // options enrichment: conditional logic class mapping
  222.         $conditionalClassData $this->dispatchConditionalLogicModule('form_type_classes'$conditionalLogicOptions);
  223.         if ($conditionalClassData->hasData()) {
  224.             $templateClasses array_merge($templateClasses$conditionalClassData->getData());
  225.         }
  226.         if (!empty($templateClasses)) {
  227.             $options['attr']['data-template'] = implode(' '$templateClasses);
  228.         }
  229.         return [
  230.             'name'    => $field->getName(),
  231.             'type'    => $this->availableFormTypes[$field->getType()]['class'],
  232.             'options' => $options
  233.         ];
  234.     }
  235.     /**
  236.      * @throws \Exception
  237.      */
  238.     private function dispatchConditionalLogicModule(string $dispatcherModule, array $options): DataInterface
  239.     {
  240.         $moduleOptions = [];
  241.         if ($dispatcherModule === 'constraints') {
  242.             $moduleOptions = [
  243.                 'availableConstraints' => $this->availableConstraints
  244.             ];
  245.         }
  246.         return $this->dispatcher->runFieldDispatcher($dispatcherModule$options$moduleOptions);
  247.     }
  248.     private function addDynamicField(FormFieldDynamicDefinitionInterface $field): array
  249.     {
  250.         $options $field->getOptions();
  251.         $optional $field->getOptional();
  252.         //set optional template
  253.         if (isset($optional['template'])) {
  254.             $options['attr']['data-template'] = $optional['template'];
  255.         }
  256.         return [
  257.             'name'    => $field->getName(),
  258.             'type'    => $field->getType(),
  259.             'options' => $options
  260.         ];
  261.     }
  262.     private function getChoiceFieldTypes(): array
  263.     {
  264.         return ['choice''dynamic_choice''country'];
  265.     }
  266.     private function preFillData(array $fields, array &$data): array
  267.     {
  268.         /** @var FormFieldDefinitionInterface $field */
  269.         foreach ($fields as $field) {
  270.             if (!empty($data[$field->getName()])) {
  271.                 continue;
  272.             }
  273.             if ($field instanceof FormFieldContainerDefinitionInterface) {
  274.                 if (!isset($data[$field->getName()])) {
  275.                     $data[$field->getName()] = [];
  276.                 }
  277.                 $this->preFillData($field->getFields(), $data[$field->getName()]);
  278.                 continue;
  279.             }
  280.             $fieldOptions $field->getOptions();
  281.             if (isset($fieldOptions['data'])) {
  282.                 $data[$field->getName()] = $fieldOptions['data'];
  283.             }
  284.         }
  285.         return $data;
  286.     }
  287. }