Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow option configuration #458

Merged
merged 10 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

```json
"require" : {
"dachcom-digital/formbuilder" : "~5.0.0"
"dachcom-digital/formbuilder" : "~5.1.0"
}
```

Expand Down Expand Up @@ -52,6 +52,7 @@ Nothing to tell here, it's just [Symfony](https://symfony.com/doc/current/templa

## Further Information
- [Usage (Rendering Types, Configuration)](docs/0_Usage.md)
- [Headless Mode](docs/1_HeadlessMode.md)
- [SPAM Protection (Honeypot, reCAPTCHA)](docs/03_SpamProtection.md)
- [Output Workflows](docs/OutputWorkflow/0_Usage.md)
- [API Channel](docs/OutputWorkflow/09_ApiChannel.md)
Expand Down
6 changes: 6 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
- **[SECURITY FEATURE]** Add [friendly captcha field](/docs/03_SpamProtection.md#friendly-captcha)
- **[SECURITY FEATURE]** Add [cloudflare turnstile](/docs/03_SpamProtection.md#cloudflare-turnstile)
- **[BUGFIX]** Use Pimcore AdminUserTranslator for Editable Dialog Box [#450](https://github.com/dachcom-digital/pimcore-formbuilder/issues/450)
- **[IMPROVEMENT]** Improve json response success message behaviour [#416](https://github.com/dachcom-digital/pimcore-formbuilder/issues/416)
- **[IMPROVEMENT]** [#458](https://github.com/dachcom-digital/pimcore-formbuilder/pull/458)
- Allow to modify FormType options via `FORM_TYPE_OPTIONS` event
- Do not render `formRuntimeDataToken` if csrf has been disabled in form options
- Allow form assembling without request and view resolver
- Add FormDialogBuilder

## 5.0.7
- Remove `editable_root` restriction from mail editor
Expand Down
5 changes: 3 additions & 2 deletions config/services/brick.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ services:
autoconfigure: true
public: true

# area brick
FormBuilderBundle\Document\Areabrick\Form\Form:
FormBuilderBundle\Document\Areabrick\Form\FormDialogBuilder:
arguments:
$translator: '@Pimcore\Bundle\AdminBundle\Translation\AdminUserTranslator'

FormBuilderBundle\Document\Areabrick\Form\Form:
tags:
- { name: pimcore.area.brick, id: formbuilder_form }
1 change: 1 addition & 0 deletions config/services/forms/forms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ services:
arguments:
- '@Symfony\Component\Form\FormFactoryInterface'
- '@FormBuilderBundle\Configuration\Configuration'
- '@event_dispatcher'
- '@FormBuilderBundle\Registry\DynamicMultiFileAdapterRegistry'
tags:
- { name: form.type }
Expand Down
21 changes: 13 additions & 8 deletions docs/0_Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
There are **three** ways to render your form.
> **Important:** It's possible to use multiple forms per page but **never** render the same form twice on the same page!
## Headless Mode
If you want to use FormBuilder in headless mode, please check out the documentation [here](./1_HeadlessMode.md).

***

## Usage I. Area Brick
This is the most used one. Just place a form element (Area Brick) somewhere on your document.
Configure it via the available edit button.
Expand All @@ -10,14 +15,14 @@ Configure it via the available edit button.
Before we start, check out the available options.
Those are (only) needed for the twig and controller rendering type.

| Name | Description |
|------|-------------|
| `form_id` | Can you guess it? It's the Form Id, right. |
| `form_template` | Form Template, for example: `bootstrap_4_layout.html.twig` |
| `main_layout` | This option is only needed if you render a form via a controller. By default, FormBuilder extends a empty default layout. If you want do extend your custom layout, define it: `layout.html.twig` |
| `preset` | Optional: set a custom preset |
| `custom_options` | Optional (array): Add some custom options as array here to pass them through the whole submission process (available in SubmissionEvent for example |
| `output_workflow` | Define, which output workflow should get dispatched after a form has been successfully submitted. You could use the ID or Name of a output workflow |
| Name | Description |
|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `form_id` | Can you guess it? It's the Form Id, right. |
| `form_template` | Form Template, for example: `bootstrap_4_layout.html.twig` |
| `main_layout` | This option is only needed if you render a form via a controller. By default, FormBuilder extends a empty default layout. If you want do extend your custom layout, define it: `layout.html.twig` |
| `preset` | Optional: set a custom preset |
| `custom_options` | Optional (array): Add some custom options as array here to pass them through the whole submission process (available in SubmissionEvent for example |
| `output_workflow` | Define, which output workflow should get dispatched after a form has been successfully submitted. You could use the ID or Name of a output workflow |

## Usage II. Twig
Create a Form using the Twig Extension.
Expand Down
61 changes: 61 additions & 0 deletions docs/1_HeadlessMode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Headless Mode

## AreaBrick
You need to create your own editable, depending on your integration. You may want to use the `FormDialogBuilder` to simplify the area configuration section.

## Form Building
This is an example, how you may want to build FormBuilder forms in headless mode:

```php
$optionBuilder = new FormOptionsResolver();
$optionBuilder->setFormId($formId);
$optionBuilder->setFormTemplate($formTemplate);
$optionBuilder->setFormPreset($formPreset);
$optionBuilder->setOutputWorkflow($formOutputWorkflow);

$form = $this->formAssembler->assembleHeadlessForm($optionBuilder);

$form->submit($request->request->all());

if (!$form->isValid()) {

// process validation errors here

return;
}

$response = null;
$submissionEvent = new SubmissionEvent($request, $formRuntimeData, $form, null, false);
$this->eventDispatcher->dispatch($submissionEvent, FormBuilderEvents::FORM_SUBMIT_SUCCESS);

$finishResponse = $this->formSubmissionFinisher->finishWithSuccess($request, $submissionEvent);

if ($finishResponse instanceof RedirectResponse) {
return [
'success' => true,
'redirect' => $finishResponse->getTargetUrl()
];
}

if ($finishResponse instanceof JsonResponse) {
$response = json_decode($finishResponse->getContent(), true, 512, JSON_THROW_ON_ERROR);
}

if ($response === null) {
throw new \InvalidArgumentException('empty success response');
}

if ($response['success'] === false) {

if (!array_key_exists('validation_errors', $response)) {
return $response;
}

$validationErrors = $response['validation_errors']
// process validation errors here

return null;
}

return $response;
```
17 changes: 17 additions & 0 deletions docs/70_Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

It's possible to add some events to every form submission.

## Form Type Options Event
The `FORM_TYPE_OPTIONS` event is dispatched after the form builder options has been defined.
It contains the field name, type and the defined options. You're able to modify the options only.

@see \FormBuilderBundle\Event\Form\FormTypeOptionsEvent

**Example**
```php
<?php

use FormBuilderBundle\FormBuilderEvents;

[
FormBuilderEvents::FORM_TYPE_OPTIONS => 'formTypeOptions'
];
```

## Pre Set Data Event
The `FORM_PRE_SET_DATA` event is dispatched at the beginning of the Form::setData() method.
It contains the form event and also some form builder settings.
Expand Down
160 changes: 118 additions & 42 deletions src/Assembler/FormAssembler.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use FormBuilderBundle\Resolver\FormOptionsResolver;
use FormBuilderBundle\Manager\FormDefinitionManager;
use FormBuilderBundle\Model\FormDefinitionInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class FormAssembler
Expand All @@ -21,74 +22,149 @@ public function __construct(
) {
}

/**
* @throws \Exception
*/
public function assemble(FormOptionsResolver $optionsResolver): array
{
return $this->assembleViewVars($optionsResolver);
try {
$formDefinition = $this->getFormDefinition($optionsResolver);
} catch (\Throwable $e) {
return [
'message' => $e->getMessage(),
'form_layout' => $optionsResolver->getFormLayout(),
'form_template' => null,
'form_id' => null
];
}

$formAssembleEvent = $this->dispatchAssembleEvent(
FormBuilderEvents::FORM_ASSEMBLE_PRE,
[$optionsResolver, $formDefinition]
);

$viewVars = $this->getViewVars($optionsResolver);

$form = $this->buildForm(
$formDefinition,
$optionsResolver,
$formAssembleEvent->getFormData(),
);

$this->dispatchAssembleEvent(
FormBuilderEvents::FORM_ASSEMBLE_POST,
[$optionsResolver, $formDefinition, $form]
);

$viewVars['form'] = $form->createView();

return $viewVars;
}

public function assembleViewVars(FormOptionsResolver $optionsResolver): array
/**
* @throws \Exception
*/
public function assembleHeadlessForm(FormOptionsResolver $optionsResolver): FormInterface
{
$builderError = false;
$exceptionMessage = null;
$formDefinition = null;
$formDefinition = $this->getFormDefinition($optionsResolver);

$formAssembleEvent = $this->dispatchAssembleEvent(
FormBuilderEvents::FORM_ASSEMBLE_PRE,
[$optionsResolver, $formDefinition]
);

$form = $this->buildForm(
$formDefinition,
$optionsResolver,
$formAssembleEvent->getFormData(),
true
);

$this->dispatchAssembleEvent(
FormBuilderEvents::FORM_ASSEMBLE_POST,
[$optionsResolver, $formDefinition, $form]
);

return $form;
}

/**
* @throws \Exception
*/
public function getFormDefinition(FormOptionsResolver $optionsResolver): FormDefinitionInterface
{
$formId = $optionsResolver->getFormId();

if ($formId !== null) {
try {
$formDefinition = $this->formDefinitionManager->getById($formId);
if (!$formDefinition instanceof FormDefinitionInterface) {
$exceptionMessage = sprintf('Form with id "%s" is not valid.', $formId);
$builderError = true;
}
} catch (\Exception $e) {
$exceptionMessage = $e->getMessage();
$builderError = true;
}
} else {
$exceptionMessage = 'No valid form selected.';
$builderError = true;
if ($formId === null) {
throw new \Exception('No valid form selected.', 404);
}

$viewVars = [];
$viewVars['form_layout'] = $optionsResolver->getFormLayout();
$formDefinition = $this->formDefinitionManager->getById($formId);
if (!$formDefinition instanceof FormDefinitionInterface) {
throw new \Exception(sprintf('Form with id "%s" is not valid.', $formId), 404);
}

return $formDefinition;
}

/**
* @throws \Exception
*/
public function buildForm(
FormDefinitionInterface $formDefinition,
FormOptionsResolver $optionsResolver,
array $formData = [],
bool $headless = false
): FormInterface {

$systemRuntimeData = $this->getSystemRuntimeData($optionsResolver, $headless);
$formAttributes = $optionsResolver->getFormAttributes();
$useCsrfProtection = $optionsResolver->useCsrfProtection();

if ($builderError === true) {
$viewVars['message'] = $exceptionMessage;
$viewVars['form_template'] = null;
$viewVars['form_id'] = null;
$formRuntimeDataCollector = $this->formRuntimeDataAllocator->allocate($formDefinition, $systemRuntimeData);
$formRuntimeData = $formRuntimeDataCollector->getData();

return $viewVars;
if ($headless === true) {
return $this->frontendFormBuilder->buildHeadlessForm($formDefinition, $formRuntimeData, $formAttributes, $formData, $useCsrfProtection);
}

$formAssembleEvent = new FormAssembleEvent($optionsResolver, $formDefinition);
$this->eventDispatcher->dispatch($formAssembleEvent, FormBuilderEvents::FORM_ASSEMBLE_PRE);
return $this->frontendFormBuilder->buildForm($formDefinition, $formRuntimeData, $formAttributes, $formData, $useCsrfProtection);
}

$systemRuntimeData = [
'form_preset' => $optionsResolver->getFormPreset(),
'form_output_workflow' => $optionsResolver->getOutputWorkflow(),
public function getSystemRuntimeData(FormOptionsResolver $optionsResolver, bool $headless = false): array
{
$data = [
'form_preset' => $optionsResolver->getFormPreset(),
'form_output_workflow' => $optionsResolver->getOutputWorkflow(),
'custom_options' => $optionsResolver->getCustomOptions()
];

return $headless ? $data : array_merge($data, [
'form_template' => $optionsResolver->getFormTemplateName(),
'form_template_full_path' => $optionsResolver->getFormTemplate(),
'custom_options' => $optionsResolver->getCustomOptions()
];
]);
}

public function getViewVars(FormOptionsResolver $optionsResolver): array
{
$viewVars = [];

$viewVars['form_layout'] = $optionsResolver->getFormLayout();
$viewVars['form_block_template'] = $optionsResolver->getFormBlockTemplate();
$viewVars['form_template'] = $optionsResolver->getFormTemplate();
$viewVars['form_id'] = $optionsResolver->getFormId();
$viewVars['form_preset'] = $optionsResolver->getFormPreset();
$viewVars['form_output_workflow'] = $optionsResolver->getOutputWorkflow();
$viewVars['main_layout'] = $optionsResolver->getMainLayout();

$formRuntimeDataCollector = $this->formRuntimeDataAllocator->allocate($formDefinition, $systemRuntimeData);
$formRuntimeData = $formRuntimeDataCollector->getData();

$form = $this->frontendFormBuilder->buildForm($formDefinition, $formRuntimeData, $formAssembleEvent->getFormData());

$formAssembleEvent = new FormAssembleEvent($optionsResolver, $formDefinition, $form);
$this->eventDispatcher->dispatch($formAssembleEvent, FormBuilderEvents::FORM_ASSEMBLE_POST);
return $viewVars;
}

$viewVars['form'] = $form->createView();
public function dispatchAssembleEvent(string $eventName, array $arguments): FormAssembleEvent
{
$formAssembleEvent = new FormAssembleEvent(...$arguments);
$this->eventDispatcher->dispatch($formAssembleEvent, $eventName);

return $viewVars;
return $formAssembleEvent;
}
}
Loading
Loading