Skip to content

Commit

Permalink
move forms to database, resolves #293
Browse files Browse the repository at this point in the history
  • Loading branch information
solverat committed Oct 12, 2021
1 parent 1a7f740 commit 78031b6
Show file tree
Hide file tree
Showing 39 changed files with 555 additions and 401 deletions.
7 changes: 6 additions & 1 deletion UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
> 💀 Be careful while migrating to production!
> A lot of things (including form configuration) have changed and will break your installation if you're ignoring the migration guide below!
### Migration
- Execute `bin/console doctrine:migrations:migrate --prefix 'FormBuilderBundle\Migrations'` after you've installed FormBuilder

### Global Changes
- Deprecations have been removed:
- `FormBuilderBundle\Storage\Form` needs to be `FormBuilderBundle\Model\FormDefinition` now
Expand All @@ -15,7 +18,7 @@
- `FormBuilderBundle\Event\MailEvent` has been removed, use `FormBuilderBundle\Event\OutputWorkflow\ChannelSubjectGuardEvent` instead
- Method `FormBuilderBundle\Assembler\FormAssembler::setFormOptionsResolver` has been removed. `FormBuilderBundle\Assembler\FormAssembler::assembleViewVars($optionsResolver)` directly requires FormOptionsResolver now
- CSV export types `Only Admin-Mail` and `Only User-Mail (Copy)` have been removed. Instead, you're now able to filter CSV export by available output workflows
- Mail layout fallback (stored in `formbuilder_forms.mailLayout`) has been removed. Please migrate layouts to email channels. This column will be removed with FormBuilder 5.0
- Mail layout fallback feature (which was enabled if no workflows have been defined and have been stored in `formbuilder_forms.mailLayout`) has been removed. Please migrate layouts to email channels. This column will be removed with FormBuilder 5.0
- PHP8 return type declarations added: you may have to adjust your extensions accordingly
- Email properties (`mail_successfully_sent`, `mail_ignore_fields`, `mail_force_plain_text`, `mail_disable_default_mail_body`) have been removed and won't be recognized anymore
- Area-Brick Configuration does not allow `sendMailTmplate` and `sendCopyMailTemplate` fallbacks anymore. They must be configured by output workflows now
Expand All @@ -32,6 +35,8 @@
### New Features
- Conditional Logic Action `Switch Output Workflow` added
- [Configurable Html2Text Options](./docs/OutputWorkflow/10_EmailChannel.md#configure-html2text-options)
- Yaml file storage migrated to Database storage
- Import/Export Improvement: You're able to export/import the complete form dataset (form, workflows and channels)

***

Expand Down
14 changes: 2 additions & 12 deletions src/FormBuilderBundle/Assembler/FormAssembler.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,8 @@ public function assembleViewVars(FormOptionsResolver $optionsResolver): array
if (!empty($formId)) {
try {
$formDefinition = $this->formDefinitionManager->getById($formId);
if (!$formDefinition instanceof FormDefinitionInterface || !$this->formDefinitionManager->configurationFileExists($formId)) {
$errorMessage = [];
if (!$formDefinition instanceof FormDefinitionInterface) {
$errorMessage[] = sprintf('Form with id "%s" is not valid.', $formId);
}

if (!$this->formDefinitionManager->configurationFileExists($formId)) {
$formConfigurationPath = $this->formDefinitionManager->getConfigurationPath($formId);
$errorMessage[] = sprintf('Configuration file is not available. This file needs to be generated as "%s".', $formConfigurationPath);
}

$exceptionMessage = join(' ', $errorMessage);
if (!$formDefinition instanceof FormDefinitionInterface) {
$exceptionMessage = sprintf('Form with id "%s" is not valid.', $formId);
$builderError = true;
}
} catch (\Exception $e) {
Expand Down
2 changes: 1 addition & 1 deletion src/FormBuilderBundle/Builder/ExtJsFormBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public function generateExtJsForm(FormDefinitionInterface $formDefinition): arra
'id' => $formDefinition->getId(),
'name' => $formDefinition->getName(),
'group' => $formDefinition->getGroup(),
'config' => $formDefinition->getConfig(),
'config' => $formDefinition->getConfiguration(),
'has_output_workflows' => $formDefinition->hasOutputWorkflows(),
'meta' => [
'creation_date' => $formDefinition->getCreationDate(),
Expand Down
2 changes: 1 addition & 1 deletion src/FormBuilderBundle/Builder/FrontendFormBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public function buildForm(FormDefinitionInterface $formDefinition, array $formRu
}

$request = $this->requestStack->getCurrentRequest();
$formDefinitionConfig = $formDefinition->getConfig();
$formDefinitionConfig = $formDefinition->getConfiguration();

$formAttributes = [];

Expand Down
2 changes: 0 additions & 2 deletions src/FormBuilderBundle/Configuration/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

class Configuration
{
public const SYSTEM_CONFIG_DIR_PATH = PIMCORE_PRIVATE_VAR . '/bundles/FormBuilderBundle';
public const STORE_PATH = PIMCORE_PRIVATE_VAR . '/bundles/FormBuilderBundle/forms';
public const INVALID_FIELD_NAMES = [
'name',
'date',
Expand Down
60 changes: 60 additions & 0 deletions src/FormBuilderBundle/Controller/Admin/ExportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
use FormBuilderBundle\Model\FormDefinitionInterface;
use FormBuilderBundle\Model\FormFieldDefinitionInterface;
use FormBuilderBundle\Model\OutputWorkflowInterface;
use FormBuilderBundle\Tool\ImportExportProcessor;
use Pimcore\Model\Tool\Email;
use Pimcore\Bundle\AdminBundle\Controller\AdminController;
use FormBuilderBundle\Manager\FormDefinitionManager;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Component\Yaml\Yaml;

class ExportController extends AdminController
{
Expand All @@ -25,6 +30,61 @@ public function __construct(FormDefinitionManager $formDefinitionManager)
$this->formDefinitionManager = $formDefinitionManager;
}

public function importFormAction(Request $request, ImportExportProcessor $importExportProcessor): JsonResponse
{
$formId = (int) $request->request->get('formId');
/** @var UploadedFile $file */
$file = $request->files->get('formData');
$data = file_get_contents($file->getPathname());
$encoding = \Pimcore\Tool\Text::detectEncoding($data);

if ($encoding) {
$data = iconv($encoding, 'UTF-8', $data);
}

$response = [
'success' => true,
'formId' => $formId,
'message' => null,
];

try {
$importExportProcessor->processYamlToFormDefinition($formId, $data);
} catch (\Throwable $e) {
$response['success'] = false;
$response['message'] = sprintf('Error while importing form definition: %s', $e->getMessage());
}

return new JsonResponse(json_encode($response, JSON_THROW_ON_ERROR), 200, ['Content-Type' => 'text/plain'], true);
}

public function exportFormAction(Request $request, ImportExportProcessor $importExportProcessor): Response
{
$formId = $request->get('id');

if (!is_numeric($formId)) {
throw new NotFoundHttpException(sprintf('form with id %d not found', $formId));
}

try {
$data = $importExportProcessor->processFormDefinitionToYaml((int) $formId);
} catch (\Throwable $e) {
throw new UnprocessableEntityHttpException(sprintf('Error while preparing form definition for export: %s', $e->getMessage()));
}

$response = new Response($data);
$exportName = 'form_export_' . $formId . '.yml';

$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$exportName
);

$response->headers->set('Content-Disposition', $disposition);

return $response;
}

public function exportFormEmailsAction(Request $request): Response
{
$formId = $request->get('id', 0);
Expand Down
70 changes: 0 additions & 70 deletions src/FormBuilderBundle/Controller/Admin/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,8 @@
use FormBuilderBundle\Model\FormDefinitionInterface;
use FormBuilderBundle\Tool\FormDependencyLocator;
use Pimcore\Bundle\AdminBundle\Controller\AdminController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Yaml\Yaml;

class SettingsController extends AdminController
{
Expand Down Expand Up @@ -248,71 +243,6 @@ public function saveFormAction(Request $request): JsonResponse
]);
}

/**
* @throws \Exception
*/
public function importFormAction(Request $request): JsonResponse
{
/** @var UploadedFile $file */
$file = $request->files->get('formData');
$data = file_get_contents($file->getPathname());
$encoding = \Pimcore\Tool\Text::detectEncoding($data);

if ($encoding) {
$data = iconv($encoding, 'UTF-8', $data);
}

$response = [
'success' => true,
'data' => [],
'message' => 'Success!',
];

try {
$formContent = Yaml::parse($data);
$formContent['fields'] = $this->extJsFormBuilder->generateExtJsFields($formContent['fields']);
$response['data'] = $formContent;
} catch (\Exception $e) {
$response['success'] = false;
$response['message'] = $e->getMessage();
}

if (!$this->container->has('serializer')) {
throw new \LogicException('No serializer found.');
}

$jsonData = $this->container->get('serializer')->serialize($response, 'json', array_merge([
'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
], []));

return new JsonResponse($jsonData, 200, ['Content-Type' => 'text/plain'], true);
}

public function exportFormAction(Request $request): Response
{
$formId = $request->get('id');

if (!is_numeric($formId)) {
throw new NotFoundHttpException('no form with id ' . $formId . ' found.');
}

$exportName = 'form_export_' . $formId . '.yml';
$exportFile = Configuration::STORE_PATH . '/main_' . $formId . '.yml';
if (!file_exists($exportFile)) {
throw new NotFoundHttpException('no form configuration with id ' . $formId . ' found.');
}

$response = new Response(file_get_contents($exportFile));
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$exportName
);

$response->headers->set('Content-Disposition', $disposition);

return $response;
}

public function getGroupTemplatesAction(): JsonResponse
{
$areaConfig = $this->configuration->getConfig('area');
Expand Down
120 changes: 120 additions & 0 deletions src/FormBuilderBundle/Doctrine/Type/FormBuilderFieldsType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php declare(strict_types=1);

namespace FormBuilderBundle\Doctrine\Type;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\Type;
use FormBuilderBundle\Factory\FormDefinitionFactoryInterface;
use FormBuilderBundle\Model\FieldDefinitionInterface;
use FormBuilderBundle\Model\Fragment\EntityToArrayAwareInterface;
use FormBuilderBundle\Model\Fragment\SubFieldsAwareInterface;

class FormBuilderFieldsType extends Type
{
public const FORM_BUILDER_FIELDS = 'form_builder_fields';

protected FormDefinitionFactoryInterface $formDefinitionFactory;

public function setFormDefinitionFactory(FormDefinitionFactoryInterface $formDefinitionFactory): void
{
$this->formDefinitionFactory = $formDefinitionFactory;
}

public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
return $platform->getClobTypeDeclarationSQL($column);
}

public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
$formFields = [];

if (!is_array($value)) {
return null;
}

foreach ($value as $field) {
if ($field instanceof EntityToArrayAwareInterface) {
$formFields[] = $field->toArray();
}
}

return serialize($formFields);
}

public function convertToPHPValue($value, AbstractPlatform $platform): array
{
if ($value === null) {
return [];
}

$value = is_resource($value) ? stream_get_contents($value) : $value;

set_error_handler(function (int $code, string $message): bool {
throw ConversionException::conversionFailedUnserialization($this->getName(), $message);
});

try {
$fields = unserialize($value, ['allowed_classes' => false]);
} finally {
restore_error_handler();
}

if (!is_array($fields)) {
return [];
}

$data = [];
foreach ($fields as $field) {
if ($field['type'] === 'container') {
$formField = $this->formDefinitionFactory->createFormFieldContainerDefinition();
$this->populateFormField($formField, $field);
if ($formField instanceof SubFieldsAwareInterface && isset($field['fields']) && is_array($field['fields'])) {
$subFields = [];
foreach ($field['fields'] as $subField) {
$subFormField = $this->formDefinitionFactory->createFormFieldDefinition();
$subFields[] = $this->populateFormField($subFormField, $subField);
}
$formField->setFields($subFields);
}
} else {
$formField = $this->formDefinitionFactory->createFormFieldDefinition();
$this->populateFormField($formField, $field);
}

$data[$field['name']] = $formField;
}

return $data;
}

public function getName(): string
{
return self::FORM_BUILDER_FIELDS;
}

public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}

protected function populateFormField($formField, array $field): FieldDefinitionInterface
{
foreach ($field as $fieldName => $fieldValue) {
$setter = 'set' . $this->camelize($fieldName);
if (!is_callable([$formField, $setter])) {
continue;
}
$formField->$setter($fieldValue);
}

return $formField;
}

protected function camelize(string $input, string $separator = '_'): string
{
return ucfirst(str_replace($separator, '', ucwords($input, $separator)));
}

}
4 changes: 1 addition & 3 deletions src/FormBuilderBundle/Document/Areabrick/Form/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ public function __construct(
public function action(Document\Editable\Area\Info $info): ?Response
{
$formId = null;
$isEditMode = $info->getParam('editmode');

$info->setParams(array_merge($info->getParams(), ['forceEditInView' => true]));
$isEditMode = $info->getEditable()->getEditmode();

/** @var Document\Editable\Select $formPresetSelection */
$formPresetSelection = $this->getDocumentEditable($info->getDocument(), 'select', 'formPreset');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public function findFormIdByRequest(Request $request): ?int

protected function detectFormRuntimeDataInRequest(Request $request, FormDefinitionInterface $formDefinition): ?array
{
$formDefinitionConfig = $formDefinition->getConfig();
$formDefinitionConfig = $formDefinition->getConfiguration();

$data = null;
$name = sprintf('formbuilder_%s', $formDefinition->getId());
Expand Down
Loading

0 comments on commit 78031b6

Please sign in to comment.