diff --git a/bundle/Controller/Admin/AdminController.php b/bundle/Controller/Admin/AdminController.php index b211c2f2..a377fb8e 100644 --- a/bundle/Controller/Admin/AdminController.php +++ b/bundle/Controller/Admin/AdminController.php @@ -1,24 +1,34 @@ service = $service; $this->contentService = $contentService; $this->configResolver = $configResolver; $this->anonymizer = $anonymizer; $this->translator = $translator; + $this->factory = $factory; + $this->infoCollectionRepository = $infoCollectionRepository; + $this->infoCollectionAttributeRepository = $infoCollectionAttributeRepository; + $this->builder = $builder; } /** - * Displays overview page + * Displays overview page. */ public function overviewAction(Request $request): Response { @@ -63,27 +91,27 @@ public function overviewAction(Request $request): Response $adapter = new InformationCollectionContentsAdapter($this->service, Query::countQuery()); $pager = $this->getPager($adapter, (int) $request->query->get('page')); - return $this->render("@NetgenInformationCollection/admin/overview.html.twig", ['objects' => $pager]); + return $this->render('@NetgenInformationCollection/admin/overview.html.twig', ['objects' => $pager]); } /** - * Displays list of collection for selected Content + * Displays list of collection for selected Content. */ public function collectionListAction(Request $request, Content $content): Response { $this->checkReadPermissions(); $adapter = new InformationCollectionCollectionListAdapter($this->service, ContentId::withContentId($content->id)); - $pager = $this->getPager($adapter, (int)$request->query->get('page')); + $pager = $this->getPager($adapter, (int) $request->query->get('page')); - return $this->render("@NetgenInformationCollection/admin/collection_list.html.twig", [ + return $this->render('@NetgenInformationCollection/admin/collection_list.html.twig', [ 'objects' => $pager, 'content' => $content, ]); } /** - * Handles collection search + * Handles collection search. */ public function searchAction(Request $request, Content $content): Response { @@ -92,9 +120,10 @@ public function searchAction(Request $request, Content $content): Response $query = SearchQuery::withContentAndSearchText($content->id, $request->query->get('searchText')); $adapter = new InformationCollectionCollectionListSearchAdapter($this->service, $query); - $pager = $this->getPager($adapter, (int)$request->query->get('page')); + $pager = $this->getPager($adapter, (int) $request->query->get('page')); - return $this->render("@NetgenInformationCollection/admin/collection_list.html.twig", + return $this->render( + '@NetgenInformationCollection/admin/collection_list.html.twig', [ 'objects' => $pager, 'content' => $content, @@ -103,20 +132,20 @@ public function searchAction(Request $request, Content $content): Response } /** - * Displays individual collection details + * Displays individual collection details. */ public function viewAction(Collection $collection): Response { $this->checkReadPermissions(); - return $this->render("@NetgenInformationCollection/admin/view.html.twig", [ + return $this->render('@NetgenInformationCollection/admin/view.html.twig', [ 'collection' => $collection, 'content' => $collection->getContent(), ]); } /** - * Handles actions performed on overview page + * Handles actions performed on overview page. */ public function handleContentsAction(Request $request): RedirectResponse { @@ -132,7 +161,6 @@ public function handleContentsAction(Request $request): RedirectResponse } if ($request->request->has('DeleteCollectionByContentAction')) { - $this->checkDeletePermissions(); $query = new Contents($contents); @@ -150,7 +178,7 @@ public function handleContentsAction(Request $request): RedirectResponse } /** - * Handles actions performed on collection list page + * Handles actions performed on collection list page. */ public function handleCollectionListAction(Request $request): RedirectResponse { @@ -167,10 +195,9 @@ public function handleCollectionListAction(Request $request): RedirectResponse } if ($request->request->has('DeleteCollectionAction')) { - $this->checkDeletePermissions(); - $query = new Collections($contentId, $collections); + $query = new Collections((int)$contentId, $collections); $this->service->deleteCollections($query); @@ -180,7 +207,6 @@ public function handleCollectionListAction(Request $request): RedirectResponse } if ($request->request->has('AnonymizeCollectionAction')) { - $this->checkAnonymizePermissions(); foreach ($collections as $collection) { @@ -198,7 +224,7 @@ public function handleCollectionListAction(Request $request): RedirectResponse } /** - * Handles action on collection details page + * Handles action on collection details page. */ public function handleCollectionAction(Request $request): RedirectResponse { @@ -219,7 +245,6 @@ public function handleCollectionAction(Request $request): RedirectResponse } if ($request->request->has('DeleteFieldAction')) { - $this->checkDeletePermissions(); $query = new CollectionFields($contentId, $collectionId, $fields); @@ -232,7 +257,6 @@ public function handleCollectionAction(Request $request): RedirectResponse } if ($request->request->has('AnonymizeFieldAction')) { - $this->checkAnonymizePermissions(); $this->anonymizer->anonymizeCollection($collectionId, $fields); @@ -243,25 +267,22 @@ public function handleCollectionAction(Request $request): RedirectResponse } if ($request->request->has('DeleteCollectionAction')) { - $this->checkDeletePermissions(); - $query = new Collections($contentId, [$collectionId]); + $query = new Collections((int)$contentId, [$collectionId]); $this->service->deleteCollections($query); - $this->addFlashMessage("success", "collection_removed"); + $this->addFlashMessage('success', 'collection_removed'); return $this->redirectToRoute('netgen_information_collection.route.admin.collection_list', ['contentId' => $contentId]); - } if ($request->request->has('AnonymizeCollectionAction')) { - $this->checkAnonymizePermissions(); $this->anonymizer->anonymizeCollection($collectionId); - $this->addFlashMessage("success", "collection_anonymized"); + $this->addFlashMessage('success', 'collection_anonymized'); return $this->redirectToRoute('netgen_information_collection.route.admin.view', ['collectionId' => $collectionId]); } @@ -271,10 +292,95 @@ public function handleCollectionAction(Request $request): RedirectResponse return $this->redirectToRoute('netgen_information_collection.route.admin.view', ['collectionId' => $collectionId]); } + /** + * @throws NonUniqueResultException + */ + public function editAction(Request $request, int $collectionId): RedirectResponse|Response + { + $this->checkEditPermissions(); + + $collection = $this->service->getCollection(new CollectionId($collectionId)); + + $location = $collection->getContent()->contentInfo->getMainLocation(); + + if ($location === null) { + throw $this->createNotFoundException(); + } + + $form = $this->builder->createUpdateFormForLocation($location, $collection)->getForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var InformationCollectionStruct $struct */ + $struct = $form->getData()->payload; + + /** @var ContentType $contentType */ + $contentType = $form->getData()->definition; + + $ezInfoCollection = $this->infoCollectionRepository->find($collectionId); + + if ($ezInfoCollection === null) { + throw $this->createNotFoundException(); + } + + $ezInfoCollection->setModified(time()); + + $this->infoCollectionRepository->save($ezInfoCollection); + + foreach ($struct->getCollectedFields() as $fieldDefIdentifier => $value) { + if ($value === null) { + continue; + } + + $fieldDefinition = $contentType->getFieldDefinition($fieldDefIdentifier); + + if ($fieldDefinition === null) { + continue; + } + + $legacyValue = $this->factory->getLegacyValue($value, $fieldDefinition); + + $ezInfoAttribute = $this->infoCollectionAttributeRepository->findByCollectionIdAndFieldDefinitionId( + $collectionId, + $fieldDefinition->id + ); + + if ($ezInfoAttribute === null) { + $ezInfoAttribute = $this->infoCollectionAttributeRepository->getInstance(); + $ezInfoAttribute->setContentObjectId($collection->getContent()->id); + $ezInfoAttribute->setContentObjectAttributeId($collection->getContent()->getField($fieldDefinition->identifier)->id); + $ezInfoAttribute->setContentClassAttributeId($fieldDefinition->id); + $ezInfoAttribute->setInformationCollectionId($collection->getId()); + } + + $ezInfoAttribute->setDataInt($legacyValue->getDataInt()); + $ezInfoAttribute->setDataFloat($legacyValue->getDataFloat()); + $ezInfoAttribute->setDataText($legacyValue->getDataText()); + + $this->infoCollectionAttributeRepository->save($ezInfoAttribute); + } + + return $this->redirectToRoute( + 'netgen_information_collection.route.admin.view', + [ + 'contentId' => $location->contentInfo->id, + 'collectionId' => $collection->getId(), + ] + ); + } + + return $this->render('@NetgenInformationCollection/admin/edit.html.twig', [ + 'collection' => $collection, + 'content' => $location->getContent(), + 'form' => $form->createView(), + ]); + } + /** * Adds a flash message with specified parameters. */ - protected function addFlashMessage(string $messageType, string $message, int $count = 1, array $parameters = array()): void + protected function addFlashMessage(string $messageType, string $message, int $count = 1, array $parameters = []): void { $parameters = array_merge($parameters, ['count' => $count]); @@ -289,7 +395,7 @@ protected function addFlashMessage(string $messageType, string $message, int $co } /** - * Returns configured instance of Pagerfanta + * Returns configured instance of Pagerfanta. */ protected function getPager(AdapterInterface $adapter, int $currentPage): Pagerfanta { @@ -320,4 +426,10 @@ protected function checkAnonymizePermissions(): void $attribute = new Attribute('infocollector', 'anonymize'); $this->denyAccessUnlessGranted($attribute); } + + protected function checkEditPermissions(): void + { + $attribute = new Attribute('infocollector', 'edit'); + $this->denyAccessUnlessGranted($attribute); + } } diff --git a/bundle/Controller/Admin/Export/Export.php b/bundle/Controller/Admin/Export/Export.php index acfc0cad..be878557 100644 --- a/bundle/Controller/Admin/Export/Export.php +++ b/bundle/Controller/Admin/Export/Export.php @@ -6,7 +6,7 @@ use Ibexa\Contracts\Core\Repository\ContentService; use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute; -use Netgen\Bundle\InformationCollectionBundle\Form\ExportType; +use Netgen\Bundle\InformationCollectionBundle\Form\Type\ExportType; use Netgen\InformationCollection\API\Service\Exporter; use Netgen\InformationCollection\API\Value\Export\ExportCriteria; use Netgen\InformationCollection\Core\Export\ExportResponseFormatterRegistry; diff --git a/bundle/DependencyInjection/Compiler/FieldTypeHandlerRegistryPass.php b/bundle/DependencyInjection/Compiler/FieldTypeHandlerRegistryPass.php new file mode 100644 index 00000000..23c4ce7e --- /dev/null +++ b/bundle/DependencyInjection/Compiler/FieldTypeHandlerRegistryPass.php @@ -0,0 +1,41 @@ +hasDefinition('netgen_information_collection.form.fieldtype_handler_registry')) { + return; + } + + $registry = $container->getDefinition('netgen_information_collection.form.fieldtype_handler_registry'); + + foreach ($container->findTaggedServiceIds('netgen_information_collection.form.fieldtype_handler') as $id => $attributes) { + foreach ($attributes as $attribute) { + if (!isset($attribute['alias'])) { + throw new LogicException( + "'netgen_information_collection.form.fieldtype_handler' service tag " . + "needs an 'alias' attribute to identify the field type. None given." + ); + } + + $registry->addMethodCall( + 'register', + [ + $attribute['alias'], + new Reference($id), + ] + ); + } + } + } +} diff --git a/bundle/Form/Builder/FormBuilder.php b/bundle/Form/Builder/FormBuilder.php new file mode 100644 index 00000000..4827e7a9 --- /dev/null +++ b/bundle/Form/Builder/FormBuilder.php @@ -0,0 +1,82 @@ +contentInfo; + $contentType = $this->contentTypeService->loadContentType($contentInfo->contentTypeId); + $struct = new InformationCollectionStruct(); + + foreach ($collection->getAttributes() as $attribute) { + $fieldValue = $this->getFieldValueFromAttribute($attribute); + + if ($fieldValue !== null) { + $struct->setCollectedFieldValue($attribute->getField()->getFieldDefinitionIdentifier(), $fieldValue); + } + } + + $data = new DataWrapper($struct, $contentType, $location); + + $useCsrf = $this->configResolver->getParameter('information_collection.form.use_csrf', 'netgen'); + + return $this->formFactory + ->createNamedBuilder( + sprintf('%s_%d', $contentType->identifier, $location->id), + InformationCollectionUpdateType::class, + $data, + [ + 'collection' => $collection, + 'csrf_protection' => $useCsrf, + ] + ); + } + + private function getFieldValueFromAttribute(Attribute $attribute): ?ValueInterface + { + $handler = $this->registry->handle($attribute->getFieldDefinition()->defaultValue); + if (!$handler instanceof CustomLegacyFieldHandlerInterface) { + return null; + } + + $legacyData = new FieldValue( + $attribute->getField()->id, + $attribute->getValue()->getDataText(), + $attribute->getValue()->getDataInt(), + $attribute->getValue()->getDataFloat() + ); + + return $handler->fromLegacyValue($legacyData); + } +} diff --git a/bundle/Form/DataMapper.php b/bundle/Form/DataMapper.php new file mode 100644 index 00000000..711b59d9 --- /dev/null +++ b/bundle/Form/DataMapper.php @@ -0,0 +1,134 @@ +fieldTypeHandlerRegistry = $fieldTypeHandlerRegistry; + $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); + } + + public function mapDataToForms(mixed $viewData, \Traversable $forms): void + { + $empty = null === $viewData || [] === $viewData; + + if (!$empty && !is_array($viewData) && !is_object($viewData)) { + throw new UnexpectedTypeException($viewData, 'object, array or empty'); + } + + foreach ($forms as $form) { + $propertyPath = $form->getPropertyPath(); + $config = $form->getConfig(); + + if ($viewData instanceof DataWrapper && null !== $propertyPath && $config->getMapped()) { + /* @var $viewData \Netgen\Bundle\InformationCollectionBundle\Form\DataWrapper */ + $this->mapToForm($form, $viewData, $propertyPath); + } elseif (!$empty && null !== $propertyPath && $config->getMapped()) { + $form->setData($this->propertyAccessor->getValue($viewData, $propertyPath)); + } else { + $form->setData($form->getConfig()->getData()); + } + } + } + + public function mapFormsToData(\Traversable $forms, mixed &$viewData): void + { + if (null === $viewData) { + return; + } + + if (!is_array($viewData) && !is_object($viewData)) { + throw new UnexpectedTypeException($viewData, 'object, array or empty'); + } + + foreach ($forms as $form) { + $propertyPath = $form->getPropertyPath(); + $config = $form->getConfig(); + + // Write-back is disabled if the form is not synchronized (transformation failed), + // if the form was not submitted and if the form is disabled (modification not allowed) + if ( + null === $propertyPath + || !$config->getMapped() + || !$form->isSubmitted() + || !$form->isSynchronized() + || $form->isDisabled() + ) { + continue; + } + + // If $data is out ContentCreateStruct, we need to map it to the corresponding field + // in the struct + if ($viewData instanceof DataWrapper) { + $this->mapFromForm($form, $viewData, $propertyPath); + + continue; + } + + // If the field is of type DateTime and the data is the same skip the update to + // keep the original object hash + if ( + $form->getData() instanceof \DateTimeImmutable + && $form->getData() === $this->propertyAccessor->getValue($viewData, $propertyPath) + ) { + continue; + } + + // If the data is identical to the value in $data, we are + // dealing with a reference + if ( + is_object($viewData) + && $config->getByReference() + && $form->getData() === $this->propertyAccessor->getValue($viewData, $propertyPath) + ) { + continue; + } + + $this->propertyAccessor->setValue($viewData, $propertyPath, $form->getData()); + } + } + + /** + * Maps data from Ibexa Platform structure to the form. + */ + abstract protected function mapToForm( + FormInterface $form, + DataWrapper $data, + PropertyPathInterface $propertyPath + ): void; + + /** + * Maps data from form to the Ibexa Platform structure. + */ + abstract protected function mapFromForm( + FormInterface $form, + DataWrapper $data, + PropertyPathInterface $propertyPath + ): void; +} diff --git a/bundle/Form/DataMapper/InformationCollectionUpdateMapper.php b/bundle/Form/DataMapper/InformationCollectionUpdateMapper.php new file mode 100644 index 00000000..37b64a86 --- /dev/null +++ b/bundle/Form/DataMapper/InformationCollectionUpdateMapper.php @@ -0,0 +1,77 @@ +definition; + + $fieldDefinitionIdentifier = (string) $propertyPath; + $fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier); + + if ($fieldDefinition === null) { + throw new RuntimeException(sprintf('Data definition does not contain expected FieldDefinition %s', $fieldDefinitionIdentifier)); + } + + $fieldTypeIdentifier = $fieldDefinition->fieldTypeIdentifier; + + $handler = $this->fieldTypeHandlerRegistry->get($fieldTypeIdentifier); + + /** @var InformationCollectionStruct $struct */ + $struct = $data->payload; + + $collectedFieldValue = $struct->getCollectedFieldValue($fieldDefinitionIdentifier); + if ($collectedFieldValue === null) { + return; + } + + $form->setData( + $handler->convertFieldValueToForm( + $struct->getCollectedFieldValue($fieldDefinitionIdentifier), + $fieldDefinition + ) + ); + } + + protected function mapFromForm( + FormInterface $form, + DataWrapper $data, + PropertyPathInterface $propertyPath + ): void { + $payload = $data->payload; + $contentType = $data->definition; + + $fieldDefinitionIdentifier = (string) $propertyPath; + $fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier); + + if ($fieldDefinition === null) { + throw new RuntimeException(sprintf('Data definition does not contain expected FieldDefinition %s', $fieldDefinitionIdentifier)); + } + + $fieldTypeIdentifier = $fieldDefinition->fieldTypeIdentifier; + $handler = $this->fieldTypeHandlerRegistry->get($fieldTypeIdentifier); + + $payload->setCollectedFieldValue( + $fieldDefinitionIdentifier, + $handler->convertFieldValueFromForm($form->getData()) + ); + } +} diff --git a/bundle/Form/DataWrapper.php b/bundle/Form/DataWrapper.php new file mode 100644 index 00000000..03267981 --- /dev/null +++ b/bundle/Form/DataWrapper.php @@ -0,0 +1,44 @@ +payload = $payload; + $this->definition = $definition; + $this->target = $target; + } +} diff --git a/bundle/Form/FieldTypeHandler.php b/bundle/Form/FieldTypeHandler.php new file mode 100644 index 00000000..4c6f92c9 --- /dev/null +++ b/bundle/Form/FieldTypeHandler.php @@ -0,0 +1,94 @@ +buildFieldForm($formBuilder, $fieldDefinition, $languageCode); + } + + /** + * In most cases this will be the same as {@link self::buildCreateFieldForm()}. + * For this reason default implementation falls back to the internal method + * {@link self::buildFieldForm()}, which should be implemented as needed. + */ + public function buildFieldUpdateForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + Content $content, + string $languageCode + ): void { + $this->buildFieldForm($formBuilder, $fieldDefinition, $languageCode, $content); + } + + /** + * In most cases implementations of methods {@link self::buildCreateFieldForm()} + * and {@link self::buildUpdateFieldForm()} will be the same, therefore default + * handler implementation of those falls back to this method. + * + * Implement as needed. + */ + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + throw new RuntimeException('Not implemented.'); + } + + /** + * Returns default field options, created from given $fieldDefinition and $languageCode. + */ + protected function getDefaultFieldOptions( + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): array { + $options = []; + + $options['label'] = $fieldDefinition->getName($languageCode); + $options['required'] = $fieldDefinition->isRequired; + $options['ibexa_forms']['description'] = $fieldDefinition->getDescription($languageCode); + $options['ibexa_forms']['language_code'] = $languageCode; + $options['ibexa_forms']['fielddefinition'] = $fieldDefinition; + + if ($content !== null) { + $options['ibexa_forms']['content'] = $content; + } + + $options['constraints'] = []; + if ($fieldDefinition->isRequired) { + $options['constraints'][] = new Constraints\NotBlank(); + } + + return $options; + } +} diff --git a/bundle/Form/FieldTypeHandler/BinaryFile.php b/bundle/Form/FieldTypeHandler/BinaryFile.php new file mode 100644 index 00000000..454b63cd --- /dev/null +++ b/bundle/Form/FieldTypeHandler/BinaryFile.php @@ -0,0 +1,62 @@ + $data->getRealPath(), + 'fileName' => $data->getClientOriginalName(), + 'fileSize' => $data->getSize(), + ]; + + return new FileValue($fileData); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + $maxFileSize = $fieldDefinition->validatorConfiguration['FileSizeValidator']['maxFileSize'] ?? false; + + if ($maxFileSize !== false) { + $options['constraints'][] = new Constraints\File( + [ + 'maxSize' => $maxFileSize * Constraints\FileValidator::MB_BYTES, + ] + ); + } + + // Used with update for displaying current file + $options['block_name'] = 'ibexa_forms_binary_file'; + + $formBuilder->add($fieldDefinition->identifier, FileType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Birthday.php b/bundle/Form/FieldTypeHandler/Birthday.php new file mode 100644 index 00000000..0f5c0aaa --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Birthday.php @@ -0,0 +1,51 @@ +date; + } + + public function convertFieldValueFromForm(mixed $data): BirthdayValue + { + if (empty($data)) { + $data = null; + } + + return new BirthdayValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $options['input'] = 'datetime_immutable'; + $options['widget'] = 'choice'; + + $formBuilder->add( + $fieldDefinition->identifier, + BirthdayType::class, + $options + ); + } +} diff --git a/bundle/Form/FieldTypeHandler/Checkbox.php b/bundle/Form/FieldTypeHandler/Checkbox.php new file mode 100644 index 00000000..5e87388e --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Checkbox.php @@ -0,0 +1,53 @@ +fieldHelper = $fieldHelper; + } + + + /** + * @param \Ibexa\Core\FieldType\Checkbox\Value $value + */ + public function convertFieldValueToForm(Value $value, ?FieldDefinition $fieldDefinition = null): bool + { + return $value->bool; + } + + public function convertFieldValueFromForm(mixed $data): CheckboxValue + { + return new CheckboxValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + if (!$content instanceof Content && $fieldDefinition->defaultValue instanceof CheckboxValue) { + $options['data'] = $fieldDefinition->defaultValue->bool; + } + + $formBuilder->add($fieldDefinition->identifier, CheckboxType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Country.php b/bundle/Form/FieldTypeHandler/Country.php new file mode 100644 index 00000000..77fe150f --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Country.php @@ -0,0 +1,98 @@ +countryData = $countryData; + + foreach ($countryData as $countryCode => $country) { + $this->filteredCountryData[$countryCode] = $country['Name']; + } + } + + /** + * @param \Ibexa\Core\FieldType\Country\Value $value + */ + public function convertFieldValueToForm(Value $value, ?FieldDefinition $fieldDefinition = null): mixed + { + $isMultiple = true; + if ($fieldDefinition !== null) { + $fieldSettings = $fieldDefinition->getFieldSettings(); + $isMultiple = $fieldSettings['isMultiple']; + } + + if (!$isMultiple) { + if (empty($value->countries)) { + return ''; + } + + $keys = array_keys($value->countries); + + return reset($keys); + } + + return array_keys($value->countries); + } + + public function convertFieldValueFromForm(mixed $data): CountryValue + { + $country = []; + + // case if multiple is true + if (is_array($data)) { + foreach ($data as $countryCode) { + if (array_key_exists($countryCode, $this->countryData)) { + $country[$countryCode] = $this->countryData[$countryCode]; + } + } + } elseif (array_key_exists($data, $this->countryData)) { + $country[$data] = $this->countryData[$data]; + } + + return new CountryValue($country); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $options['expanded'] = false; + $options['multiple'] = $fieldDefinition->getFieldSettings()['isMultiple'] ?? false; + + $options['choices'] = array_flip($this->filteredCountryData); + + $formBuilder->add($fieldDefinition->identifier, ChoiceType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Date.php b/bundle/Form/FieldTypeHandler/Date.php new file mode 100644 index 00000000..b7f5f436 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Date.php @@ -0,0 +1,46 @@ +date; + } + + public function convertFieldValueFromForm(mixed $data): DateValue + { + return new DateValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $options['input'] = 'datetime'; + $options['widget'] = 'choice'; + $options['constraints'][] = new Assert\Date(); + + $formBuilder->add($fieldDefinition->identifier, DateType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/DateAndTime.php b/bundle/Form/FieldTypeHandler/DateAndTime.php new file mode 100644 index 00000000..71d87589 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/DateAndTime.php @@ -0,0 +1,49 @@ +value; + } + + public function convertFieldValueFromForm(mixed $data): DateTimeValue + { + return new DateTimeValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $useSeconds = $fieldDefinition->getFieldSettings()['useSeconds'] ?? false; + $options['input'] = 'datetime'; + $options['date_widget'] = 'choice'; + $options['time_widget'] = 'choice'; + $options['with_seconds'] = $useSeconds; + $options['constraints'][] = new Assert\DateTime(); + + $formBuilder->add($fieldDefinition->identifier, DateTimeType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Email.php b/bundle/Form/FieldTypeHandler/Email.php new file mode 100644 index 00000000..169854fc --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Email.php @@ -0,0 +1,45 @@ +email; + } + + public function convertFieldValueFromForm(mixed $data): EmailAddressValue + { + return new EmailAddressValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + if (isset($fieldDefinition->validatorConfiguration['EmailAddressValidator'])) { + $options['constraints'][] = new Constraints\Email(); + } + + $formBuilder->add($fieldDefinition->identifier, EmailType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/EnhancedSelection.php b/bundle/Form/FieldTypeHandler/EnhancedSelection.php new file mode 100644 index 00000000..f0f61b28 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/EnhancedSelection.php @@ -0,0 +1,83 @@ +getFieldSettings(); + $isMultiple = $fieldSettings['isMultiple']; + } + + if (!$isMultiple) { + if (empty($value->identifiers)) { + return ''; + } + + return $value->identifiers[0]; + } + + return $value->identifiers; + } + + public function convertFieldValueFromForm(mixed $data): EnhancedSelectionValue + { + if ($data === null) { + return new EnhancedSelectionValue(); + } + + return new EnhancedSelectionValue(is_array($data) ? $data : [$data]); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $fieldSettings = $fieldDefinition->getFieldSettings(); + $optionsValues = $fieldSettings['options']; + + $options['multiple'] = $fieldSettings['isMultiple']; + $options['expanded'] = $fieldSettings['isExpanded']; + $options['choices'] = $this->getValues($optionsValues); + + $formBuilder->add( + $fieldDefinition->identifier, + ChoiceType::class, + $options + ); + } + + private function getValues(array $options): array + { + $values = []; + + foreach ($options as $option) { + if (!empty($option['identifier']) && !empty($option['name'])) { + $values[$option['name']] = $option['identifier']; + } + } + + return $values; + } +} + diff --git a/bundle/Form/FieldTypeHandler/FloatHandler.php b/bundle/Form/FieldTypeHandler/FloatHandler.php new file mode 100644 index 00000000..33fb1e3f --- /dev/null +++ b/bundle/Form/FieldTypeHandler/FloatHandler.php @@ -0,0 +1,77 @@ +fieldHelper = $fieldHelper; + } + + /** + * @param \Ibexa\Core\FieldType\Float\Value $value + */ + public function convertFieldValueToForm(Value $value, ?FieldDefinition $fieldDefinition = null): float + { + return $value->value; + } + + public function convertFieldValueFromForm(mixed $data): FloatValue + { + if (!is_numeric($data)) { + $data = null; + } + + return new FloatValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + if (!empty($fieldDefinition->getValidatorConfiguration()['FloatValueValidator'])) { + $rangeConstraints = []; + + $min = $fieldDefinition->getValidatorConfiguration()['FloatValueValidator']['minFloatValue']; + $max = $fieldDefinition->getValidatorConfiguration()['FloatValueValidator']['maxFloatValue']; + + if ($min !== false) { + $rangeConstraints['min'] = $min; + } + + if ($max !== false) { + $rangeConstraints['max'] = $max; + } + + if (!empty($rangeConstraints)) { + $options['constraints'][] = new Assert\Range($rangeConstraints); + } + } + + if (!$content instanceof Content && $fieldDefinition->defaultValue instanceof FloatValue) { + $options['data'] = (float) $fieldDefinition->defaultValue->value; + } + + $formBuilder->add($fieldDefinition->identifier, NumberType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Image.php b/bundle/Form/FieldTypeHandler/Image.php new file mode 100644 index 00000000..7e117879 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Image.php @@ -0,0 +1,59 @@ + $data->getRealPath(), + 'fileName' => $data->getClientOriginalName(), + 'fileSize' => $data->getSize(), + ]; + + return new ImageValue($imageData); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $maxFileSize = $fieldDefinition->validatorConfiguration['FileSizeValidator']['maxFileSize'] ?? false; + + if ($maxFileSize !== false) { + $options['constraints'][] = new Constraints\File( + [ + 'maxSize' => $maxFileSize, + ] + ); + } + + $options['block_name'] = 'ibexa_forms_image'; + + $formBuilder->add($fieldDefinition->identifier, FileType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/IntegerHandler.php b/bundle/Form/FieldTypeHandler/IntegerHandler.php new file mode 100644 index 00000000..0e79a17b --- /dev/null +++ b/bundle/Form/FieldTypeHandler/IntegerHandler.php @@ -0,0 +1,77 @@ +fieldHelper = $fieldHelper; + } + + /** + * @param \Ibexa\Core\FieldType\Integer\Value $value + */ + public function convertFieldValueToForm(Value $value, ?FieldDefinition $fieldDefinition = null): int + { + return (int) $value->value; + } + + public function convertFieldValueFromForm(mixed $data): IntegerValue + { + if (!is_int($data)) { + $data = null; + } + + return new IntegerValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + if (!$content instanceof Content && $fieldDefinition->defaultValue instanceof IntegerValue) { + $options['data'] = (int) $fieldDefinition->defaultValue->value; + } + + if (!empty($fieldDefinition->getValidatorConfiguration()['IntegerValueValidator'])) { + $rangeConstraints = []; + + $min = $fieldDefinition->getValidatorConfiguration()['IntegerValueValidator']['minIntegerValue']; + $max = $fieldDefinition->getValidatorConfiguration()['IntegerValueValidator']['maxIntegerValue']; + + if ($min !== null) { + $rangeConstraints['min'] = $min; + } + + if ($max !== null) { + $rangeConstraints['max'] = $max; + } + + if (!empty($rangeConstraints)) { + $options['constraints'][] = new Assert\Range($rangeConstraints); + } + } + + $formBuilder->add($fieldDefinition->identifier, IntegerType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Isbn.php b/bundle/Form/FieldTypeHandler/Isbn.php new file mode 100644 index 00000000..a3c9a460 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Isbn.php @@ -0,0 +1,55 @@ +isbn; + } + + public function convertFieldValueFromForm(mixed $data): IsbnValue + { + if (empty($data)) { + $data = ''; + } + + return new IsbnValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + if ($fieldDefinition->fieldSettings['isISBN13'] ?? true) { + $options['constraints'][] = new Constraints\Isbn( + [ + 'type' => 'isbn13', + ] + ); + } else { + $options['constraints'][] = new Constraints\Isbn(); + } + + $formBuilder->add($fieldDefinition->identifier, TextType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/MapLocation.php b/bundle/Form/FieldTypeHandler/MapLocation.php new file mode 100644 index 00000000..254bf057 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/MapLocation.php @@ -0,0 +1,54 @@ + empty($value->latitude) ? null : $value->latitude, + 'longitude' => empty($value->longitude) ? null : $value->longitude, + 'address' => empty($value->address) ? null : $value->address, + ]; + } + + public function convertFieldValueFromForm(mixed $data): ?MapLocationValue + { + if (!is_array($data)) { + return null; + } + + return new MapLocationValue( + [ + 'latitude' => $data['latitude'], + 'longitude' => $data['longitude'], + 'address' => $data['address'], + ] + ); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $options['block_name'] = 'ibexa_forms_map'; + + $formBuilder->add($fieldDefinition->identifier, MapType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Relation.php b/bundle/Form/FieldTypeHandler/Relation.php new file mode 100644 index 00000000..1915a588 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Relation.php @@ -0,0 +1,66 @@ +repository = $repository; + } + + /** + * @param \Ibexa\Core\FieldType\Relation\Value $value + */ + public function convertFieldValueToForm(Value $value, ?FieldDefinition $fieldDefinition = null): mixed + { + if (empty($value->destinationContentId)) { + return null; + } + + return $value->destinationContentId; + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $selectionRoot = $fieldDefinition->getFieldSettings()['selectionRoot']; + + if (empty($selectionRoot)) { + throw new InvalidArgumentException('SelectionRoot must be defined'); + } + + $locationService = $this->repository->getLocationService(); + $location = $locationService->loadLocation($selectionRoot); + $locationList = $locationService->loadLocationChildren($location); + + $choices = []; + foreach ($locationList->locations as $child) { + /* @var Location $child */ + $choices[$child->getContent()->getName()] = $child->contentInfo->id; + } + + $formBuilder->add($fieldDefinition->identifier, ChoiceType::class, [ + 'choices' => $choices, + 'expanded' => false, + 'multiple' => false, + ]); + } +} diff --git a/bundle/Form/FieldTypeHandler/RelationList.php b/bundle/Form/FieldTypeHandler/RelationList.php new file mode 100644 index 00000000..614a99b9 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/RelationList.php @@ -0,0 +1,104 @@ +repository = $repository; + } + + public function convertFieldValueToForm(Value $value, ?FieldDefinition $fieldDefinition = null): ?array + { + if (empty($value->destinationContentIds)) { + return null; + } + + /** @var $value \Ibexa\Core\FieldType\RelationList\Value */ + return $value->destinationContentIds; + } + + public function convertFieldValueFromForm(mixed $data): RelationListValue + { + return new RelationListValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $fieldSettings = $fieldDefinition->getFieldSettings(); + + $selectionMethod = $fieldSettings['selectionMethod']; + + $defaultLocation = $fieldSettings['selectionDefaultLocation']; + $contentTypes = $fieldSettings['selectionContentTypes']; + + /* TODO: implement different selection methods */ + switch ($fieldSettings['selectionMethod']) { + case self::MULTIPLE_SELECTION: + $locationService = $this->repository->getLocationService(); + $location = $locationService->loadLocation($defaultLocation ?: 2); + $locationList = $locationService->loadLocationChildren($location); + + $choices = []; + + foreach ($locationList->locations as $child) { + $choices[$child->getContent()->getName()] = $child->contentInfo->id; + } + + $formBuilder->add($fieldDefinition->identifier, ChoiceType::class, [ + 'choices' => $choices, + 'expanded' => false, + 'multiple' => true, + ] + $options); + + break; + + default: + $locationService = $this->repository->getLocationService(); + $location = $locationService->loadLocation($defaultLocation ?: 2); + $locationList = $locationService->loadLocationChildren($location); + + $choices = []; + + foreach ($locationList->locations as $child) { + $choices[$child->getContent()->getName()] = $child->contentInfo->id; + } + + $formBuilder->add($fieldDefinition->identifier, ChoiceType::class, [ + 'choices' => $choices, + 'expanded' => false, + 'multiple' => false, + ] + $options); + + break; + } + } +} diff --git a/bundle/Form/FieldTypeHandler/Selection.php b/bundle/Form/FieldTypeHandler/Selection.php new file mode 100644 index 00000000..e5cddea6 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Selection.php @@ -0,0 +1,62 @@ +getFieldSettings(); + $isMultiple = $fieldSettings['isMultiple']; + } + + if (!$isMultiple) { + if (empty($value->selection)) { + return ''; + } + + return $value->selection[0]; + } + + return $value->selection; + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $values = $fieldDefinition->getFieldSettings()['options']; + + $options['expanded'] = false; + $options['multiple'] = $fieldDefinition->getFieldSettings()['isMultiple']; + + $options['choices'] = array_flip($values); + + $formBuilder->add($fieldDefinition->identifier, ChoiceType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/TextBlock.php b/bundle/Form/FieldTypeHandler/TextBlock.php new file mode 100644 index 00000000..419e7f10 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/TextBlock.php @@ -0,0 +1,46 @@ +text; + } + + public function convertFieldValueFromForm(mixed $data): TextBlockValue + { + if (empty($data)) { + $data = ''; + } + + return new TextBlockValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $options['attr']['rows'] = $fieldDefinition->fieldSettings['textRows']; + + $formBuilder->add($fieldDefinition->identifier, TextareaType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/TextLine.php b/bundle/Form/FieldTypeHandler/TextLine.php new file mode 100644 index 00000000..12481deb --- /dev/null +++ b/bundle/Form/FieldTypeHandler/TextLine.php @@ -0,0 +1,64 @@ +text; + } + + public function convertFieldValueFromForm(mixed $data): TextLineValue + { + if (empty($data)) { + $data = ''; + } + + return new TextLineValue($data); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + if (!empty($fieldDefinition->validatorConfiguration['StringLengthValidator'])) { + $lengthConstraints = []; + + $minStringLength = $fieldDefinition->validatorConfiguration['StringLengthValidator']['minStringLength']; + $maxStringLength = $fieldDefinition->validatorConfiguration['StringLengthValidator']['maxStringLength']; + + if (!empty($minStringLength)) { + $lengthConstraints['min'] = $minStringLength; + } + + if (!empty($maxStringLength)) { + $lengthConstraints['max'] = $maxStringLength; + } + + if (!empty($lengthConstraints)) { + $options['constraints'][] = new Constraints\Length($lengthConstraints); + } + } + + $formBuilder->add($fieldDefinition->identifier, TextType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Time.php b/bundle/Form/FieldTypeHandler/Time.php new file mode 100644 index 00000000..7531d944 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Time.php @@ -0,0 +1,63 @@ +time; + if (is_int($time)) { + return new DateTime("@{$time}"); + } + + return new DateTime(); + } + + public function convertFieldValueFromForm(mixed $data): TimeValue + { + if ($data instanceof DateTime) { + return TimeValue::fromDateTime($data); + } + + if (is_int($data)) { + return new TimeValue($data); + } + + return new TimeValue(null); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $useSeconds = $fieldDefinition->getFieldSettings()['useSeconds']; + $options['input'] = 'datetime'; + $options['widget'] = 'choice'; + $options['with_seconds'] = $useSeconds; + $options['constraints'][] = new Assert\Time(); + + $formBuilder->add($fieldDefinition->identifier, TimeType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandler/Url.php b/bundle/Form/FieldTypeHandler/Url.php new file mode 100644 index 00000000..b64a2383 --- /dev/null +++ b/bundle/Form/FieldTypeHandler/Url.php @@ -0,0 +1,47 @@ + $value->link, 'text' => $value->text]; + } + + public function convertFieldValueFromForm(mixed $data): UrlValue + { + if (!is_array($data)) { + $data = []; + $data['url'] = null; + $data['text'] = null; + } + + return new UrlValue($data['url'], $data['text']); + } + + protected function buildFieldForm( + FormBuilderInterface $formBuilder, + FieldDefinition $fieldDefinition, + string $languageCode, + ?Content $content = null + ): void { + $options = $this->getDefaultFieldOptions($fieldDefinition, $languageCode, $content); + + $formBuilder->add($fieldDefinition->identifier, UrlType::class, $options); + } +} diff --git a/bundle/Form/FieldTypeHandlerInterface.php b/bundle/Form/FieldTypeHandlerInterface.php new file mode 100644 index 00000000..fac828fd --- /dev/null +++ b/bundle/Form/FieldTypeHandlerInterface.php @@ -0,0 +1,53 @@ +map = $map; + } + + /** + * Register a $service for FieldType $identifier. + */ + public function register(string $identifier, FieldTypeHandlerInterface $handler): void + { + $this->map[$identifier] = $handler; + } + + /** + * Returns a FieldTypeHandlerInterface for FieldType $identifier. + * + * @throws \OutOfBoundsException + * @throws \RuntimeException When type is not a FieldTypeHandlerInterface instance nor a callable factory + */ + public function get(string $identifier): FieldTypeHandlerInterface + { + if (!isset($this->map[$identifier])) { + throw new OutOfBoundsException("No handler registered for FieldType '{$identifier}'."); + } + if (!$this->map[$identifier] instanceof FieldTypeHandlerInterface) { + + $factory = $this->map[$identifier]; + $this->map[$identifier] = $factory(); + + if (!$this->map[$identifier] instanceof FieldTypeHandlerInterface) { + throw new RuntimeException( + "FieldTypeHandler '{$identifier}' callable did not return a FieldTypeHandlerInterface instance, " . + 'instead: ' . gettype($this->map[$identifier]) + ); + } + } + + return $this->map[$identifier]; + } +} diff --git a/bundle/Form/Payload/InformationCollectionStruct.php b/bundle/Form/Payload/InformationCollectionStruct.php new file mode 100644 index 00000000..374a609c --- /dev/null +++ b/bundle/Form/Payload/InformationCollectionStruct.php @@ -0,0 +1,37 @@ +collectedData[$fieldDefIdentifier] ?? null; + } + + /** + * This method returns the complete fields collection. + */ + public function getCollectedFields(): array + { + return $this->collectedData; + } + + /** + * Sets value for $fieldDefIdentifier. + */ + public function setCollectedFieldValue(string $fieldDefIdentifier, mixed $value): void + { + $this->collectedData[$fieldDefIdentifier] = $value; + } +} diff --git a/bundle/Form/Type/AbstractContentType.php b/bundle/Form/Type/AbstractContentType.php new file mode 100644 index 00000000..2c624ff2 --- /dev/null +++ b/bundle/Form/Type/AbstractContentType.php @@ -0,0 +1,22 @@ +fieldTypeHandlerRegistry = $fieldTypeHandlerRegistry; + $this->dataMapper = $dataMapper; + } +} diff --git a/bundle/Form/CaptchaType.php b/bundle/Form/Type/CaptchaType.php similarity index 96% rename from bundle/Form/CaptchaType.php rename to bundle/Form/Type/CaptchaType.php index 43de5d84..696a9deb 100644 --- a/bundle/Form/CaptchaType.php +++ b/bundle/Form/Type/CaptchaType.php @@ -1,6 +1,6 @@ configResolver = $configResolver; + } + + public function getName(): string + { + return $this->getBlockPrefix(); + } + + /** + * Returns the prefix of the template block name for this type. + */ + public function getBlockPrefix(): string + { + return 'ezforms_information_collection_update'; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setRequired('collection'); + $resolver->setAllowedTypes('collection', Collection::class); + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + /** @var DataWrapper $dataWrapper */ + $dataWrapper = $options['data']; + + if (!$dataWrapper instanceof DataWrapper) { + throw new RuntimeException(sprintf('Data must be an instance of %s', DataWrapper::class)); + } + + /** @var InformationCollectionStruct $payload */ + $payload = $dataWrapper->payload; + + if (!$payload instanceof InformationCollectionStruct) { + throw new RuntimeException(sprintf('Data payload must be an instance of %s', InformationCollectionStruct::class)); + } + + /** @var ContentType $contentType */ + $contentType = $dataWrapper->definition; + + if (!$contentType instanceof ContentType) { + throw new RuntimeException(sprintf('Data definition must be an instance of %s', ContentType::class)); + } + + $builder->setDataMapper($this->dataMapper); + + foreach ($contentType->getFieldDefinitions() as $fieldDefinition) { + if ($fieldDefinition->fieldTypeIdentifier === 'ezuser') { + continue; + } + + if (!$fieldDefinition->isInfoCollector) { + continue; + } + + $handler = $this->fieldTypeHandlerRegistry->get($fieldDefinition->fieldTypeIdentifier); + + $handler->buildFieldUpdateForm( + $builder, + $fieldDefinition, + $dataWrapper->target->content, + $this->getLanguageCode($contentType) + ); + } + } + + /** + * If ContentType language code is in languages array then use it, else use the main language. + * + * @param ContentType $contentType + * + * @return string + */ + protected function getLanguageCode(ContentType $contentType): string + { + $contentTypeLanguages = array_keys($contentType->getNames()); + $languages = $this->configResolver->getParameter('languages'); + + foreach ($languages as $languageCode) { + if (in_array($languageCode, $contentTypeLanguages, true)) { + return $languageCode; + } + } + + return $contentType->mainLanguageCode; + } +} diff --git a/bundle/Form/Type/MapType.php b/bundle/Form/Type/MapType.php new file mode 100644 index 00000000..d5b33647 --- /dev/null +++ b/bundle/Form/Type/MapType.php @@ -0,0 +1,33 @@ +add('address', TextType::class, [ + 'label' => 'ibexa_forms.form.map.address.label', + ]); + + $builder->add('latitude', NumberType::class, [ + 'label' => 'ibexa_forms.form.map.latitude.label', + ]); + + $builder->add('longitude', NumberType::class, [ + 'label' => 'ibexa_forms.form.map.longitude.label', + ]); + } + + public function getBlockPrefix(): string + { + return 'ibexa_forms_map'; + } +} diff --git a/bundle/Form/Type/UrlType.php b/bundle/Form/Type/UrlType.php new file mode 100644 index 00000000..31a628a1 --- /dev/null +++ b/bundle/Form/Type/UrlType.php @@ -0,0 +1,32 @@ +add( + 'url', + CoreUrlType::class, + [ + 'constraints' => new Assert\Url(), + ] + ); + + $builder->add('text', TextType::class); + } + + public function getBlockPrefix(): string + { + return 'ibexa_forms_url'; + } +} diff --git a/bundle/Ibexa/ContentForms/InformationCollectionType.php b/bundle/Ibexa/ContentForms/InformationCollectionType.php index dcdeed6c..ab492408 100644 --- a/bundle/Ibexa/ContentForms/InformationCollectionType.php +++ b/bundle/Ibexa/ContentForms/InformationCollectionType.php @@ -5,7 +5,7 @@ namespace Netgen\Bundle\InformationCollectionBundle\Ibexa\ContentForms; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; -use Netgen\Bundle\InformationCollectionBundle\Form\CaptchaType; +use Netgen\Bundle\InformationCollectionBundle\Form\Type\CaptchaType; use Netgen\InformationCollection\API\Service\CaptchaService; use Netgen\InformationCollection\API\Value\InformationCollectionStruct; use Symfony\Component\Form\AbstractType; diff --git a/bundle/NetgenInformationCollectionBundle.php b/bundle/NetgenInformationCollectionBundle.php index b11eaedf..c638babe 100644 --- a/bundle/NetgenInformationCollectionBundle.php +++ b/bundle/NetgenInformationCollectionBundle.php @@ -3,6 +3,7 @@ namespace Netgen\Bundle\InformationCollectionBundle; use Ibexa\Bundle\Core\DependencyInjection\IbexaCoreExtension; +use Netgen\Bundle\InformationCollectionBundle\DependencyInjection\Compiler\FieldTypeHandlerRegistryPass; use Netgen\Bundle\InformationCollectionBundle\Ibexa\PolicyProvider\InformationCollectionPolicyProvider; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -11,6 +12,7 @@ class NetgenInformationCollectionBundle extends Bundle { public function build(ContainerBuilder $container): void { + $container->addCompilerPass(new FieldTypeHandlerRegistryPass()); parent::build($container); $ibexaExtension = $container->getExtension('ibexa'); diff --git a/bundle/Resources/config/controllers.yml b/bundle/Resources/config/controllers.yml index bb45643b..073bd430 100644 --- a/bundle/Resources/config/controllers.yml +++ b/bundle/Resources/config/controllers.yml @@ -17,6 +17,10 @@ services: - "@ibexa.api.service.content" - "@ibexa.config.resolver" - "@translator" + - '@netgen_information_collection.factory.field_data' + - '@netgen_information_collection.repository.ez_info_collection' + - '@netgen_information_collection.repository.ez_info_collection_attribute' + - '@netgen_information_collection.form.builder' netgen_information_collection.controller.export.export: class: Netgen\Bundle\InformationCollectionBundle\Controller\Admin\Export\Export diff --git a/bundle/Resources/config/routing.yaml b/bundle/Resources/config/routing.yaml index b3a25989..9f138568 100644 --- a/bundle/Resources/config/routing.yaml +++ b/bundle/Resources/config/routing.yaml @@ -18,6 +18,11 @@ netgen_information_collection.route.admin.view: defaults: { _controller: netgen_information_collection.controller.admin:viewAction } methods: [GET] +netgen_information_collection.route.admin.edit: + path: /netgen/informationcollection/edit/{collectionId} + defaults: { _controller: netgen_information_collection.controller.admin:editAction } + methods: [ GET, POST ] + netgen_information_collection.route.admin.handle_contents: path: /netgen/informationcollection/handle/contents defaults: { _controller: netgen_information_collection.controller.admin:handleContentsAction } diff --git a/bundle/Resources/config/services.yml b/bundle/Resources/config/services.yml index d051d4b0..211b9ec8 100644 --- a/bundle/Resources/config/services.yml +++ b/bundle/Resources/config/services.yml @@ -32,3 +32,14 @@ services: - '@request_stack' - '@netgen_information_collection.captcha.service' - '@translator' + + netgen_information_collection.form.builder: + class: Netgen\Bundle\InformationCollectionBundle\Form\Builder\FormBuilder + arguments: + - '@form.factory' + - '@ibexa.api.service.content_type' + - '@router' + - '@ibexa.config.resolver' + - '@netgen_information_collection.factory.field_data' + - '@netgen_information_collection.field_handler.registry' + diff --git a/bundle/Resources/public/admin/css/ic_forms.css b/bundle/Resources/public/admin/css/ic_forms.css new file mode 100644 index 00000000..c2cc8839 --- /dev/null +++ b/bundle/Resources/public/admin/css/ic_forms.css @@ -0,0 +1,81 @@ + .ic-content { + margin-bottom: 2em; + } + .ic-content .row-input { + margin-bottom: 1em; + } + .ic-content label { + display: block; + font-weight: normal; + font-size: 14px; + margin: 0; + } + .ic-content input[type=text], + .ic-content input[type=number], + .ic-content input[type=url], + .ic-content input[type=email], + .ic-content textarea, + .ic-content select { + display: block; + margin: 0 0 1em; + border-radius: 2px; + width: 40%; + padding: 0 0.75em; + font-size: 14px; + height: 44px; + border: 1px solid #b3b3b3; + background: #fff; + } + .ic-content input[type=text]::placeholder, + .ic-content input[type=number]::placeholder, + .ic-content input[type=url]::placeholder, + .ic-content input[type=email]::placeholder, + .ic-content textarea::placeholder, + .ic-content select::placeholder { + color: #b3b3b3; + } + .ic-content input[type=text][disabled], + .ic-content input[type=number][disabled], + .ic-content input[type=url][disabled], + .ic-content input[type=email][disabled], + .ic-content textarea[disabled], + .ic-content select[disabled] { + color: #999999; + border-color: #cccccc; + cursor: not-allowed; + } + .ic-content select[multiple] { + background-image: none; + height: auto; + padding: 0.25em 0; + } + .ic-content select[multiple] option { + padding: 0.25em 0.5em; + } + .ic-content textarea { + height: auto; + min-height: 120px; + padding-top: 0.375em; + resize: vertical; + } + .ic-content .file-input-group .filename { + margin-left: 1em; + font-size: 0.875em; + } + .ic-content .birthday div { + display: flex; + } + .ic-content .birthday div select { + width: 13%; + margin-right: 7px; + } + .ic-content .error-input ul { + margin-left: 0; + } + .ic-content .error-input ul li { + list-style-type: none; + color: red; + } + .ic-content form div { + padding: 8px 0; + } \ No newline at end of file diff --git a/bundle/Resources/translations/netgen_information_collection_admin.en.yml b/bundle/Resources/translations/netgen_information_collection_admin.en.yml index aa20034e..fbe33836 100644 --- a/bundle/Resources/translations/netgen_information_collection_admin.en.yml +++ b/bundle/Resources/translations/netgen_information_collection_admin.en.yml @@ -58,3 +58,6 @@ netgen_information_collection_admin_export_no_formatters: 'No formatters found' netgen_information_collection_admin_collected_information: 'Collected information (%count%)' netgen_information_collection_admin_missing_location: 'Content does not have a valid location.' + +netgen_information_collection_admin_collection_edit: 'Edit' +netgen_information_collection_admin_collection_save: 'Save' diff --git a/bundle/Resources/views/admin/collection_list.html.twig b/bundle/Resources/views/admin/collection_list.html.twig index d550aeae..8be80c82 100644 --- a/bundle/Resources/views/admin/collection_list.html.twig +++ b/bundle/Resources/views/admin/collection_list.html.twig @@ -56,6 +56,7 @@ {{ 'netgen_information_collection_admin_collection_created'|trans }} {{ 'netgen_information_collection_admin_collection_modified'|trans }} {{ 'netgen_information_collection_admin_collection_collection_id'|trans }} + @@ -81,6 +82,11 @@ {{ collection_id }} + + + {{ 'netgen_information_collection_admin_collection_edit'|trans }} + + {% endfor %} diff --git a/bundle/Resources/views/admin/edit.html.twig b/bundle/Resources/views/admin/edit.html.twig new file mode 100644 index 00000000..60f1527f --- /dev/null +++ b/bundle/Resources/views/admin/edit.html.twig @@ -0,0 +1,17 @@ +{% extends netgen_information_collection_admin.pageLayoutTemplate %} + +{% trans_default_domain 'netgen_information_collection_admin' %} + +{% form_theme form '@NetgenInformationCollection/admin/form.html.twig' %} + +{% block content %} + {{ form_start(form, {attr: {novalidate: 'novalidate'}}) }} + {% for child in form.children %} + {{ form_row(child) }} + {% endfor %} + + {{ form_rest(form) }} + + + {{ form_end(form) }} +{% endblock %} diff --git a/bundle/Resources/views/admin/form.html.twig b/bundle/Resources/views/admin/form.html.twig new file mode 100644 index 00000000..114f2f5f --- /dev/null +++ b/bundle/Resources/views/admin/form.html.twig @@ -0,0 +1,21 @@ +{%- block form_row -%} +
+ {%- if 'checkbox' in block_prefixes or 'radio' in block_prefixes -%} + {{- form_widget(form) -}} + {{- form_label(form) -}} + {{- form_errors(form) -}} + {%- else -%} + {{- form_label(form) -}} + {{- form_widget(form) -}} + {{- form_errors(form) -}} + {%- endif -%} +
+{%- endblock form_row -%} + +{%- block birthday_row -%} +
+ {{- form_label(form) -}} + {{- form_widget(form) -}} + {{- form_errors(form) -}} +
+{%- endblock birthday_row -%} diff --git a/bundle/Resources/views/admin/stylesheets.html.twig b/bundle/Resources/views/admin/stylesheets.html.twig index 4b98fa53..c3dacce6 100644 --- a/bundle/Resources/views/admin/stylesheets.html.twig +++ b/bundle/Resources/views/admin/stylesheets.html.twig @@ -1 +1,2 @@ + diff --git a/bundle/Resources/views/admin/view.html.twig b/bundle/Resources/views/admin/view.html.twig index 54cf4a85..ad942734 100644 --- a/bundle/Resources/views/admin/view.html.twig +++ b/bundle/Resources/views/admin/view.html.twig @@ -16,6 +16,10 @@
+ + {{ 'netgen_information_collection_admin_collection_edit'|trans }} + + diff --git a/composer.json b/composer.json index 78681af4..595f74b6 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ } ], "require": { - "php": "^7.4 || ^8.0", + "php": ">=8.1", "ext-pdo": "*", "doctrine/orm": "^2.5", "ibexa/admin-ui": "^4.0", @@ -40,7 +40,7 @@ "phpunit/phpunit": "^8.2", "matthiasnoback/symfony-config-test": "~4.0", "matthiasnoback/symfony-dependency-injection-test": "~4.0", - "friendsofphp/php-cs-fixer": "^3.3" + "friendsofphp/php-cs-fixer": "^3.9" }, "autoload": { "psr-4": { diff --git a/doc/cookbook/override_something_on_form.rst b/doc/cookbook/override_something_on_form.rst index 4e266eee..2c3d07dd 100644 --- a/doc/cookbook/override_something_on_form.rst +++ b/doc/cookbook/override_something_on_form.rst @@ -10,7 +10,7 @@ How to override something on info collector form namespace Acme\Form; use Acme\Validator\Constraints\MyValidator; - use Netgen\Bundle\IbexaFormsBundle\Form\Type\InformationCollectionType; + use Netgen\Bundle\InformationCollectionBundle\Form\Type\InformationCollectionType; use Netgen\Bundle\EzPlatformSiteApiBundle\View\ContentValueView; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -63,4 +63,4 @@ How to override something on info collector form arguments: - '@request_stack' tags: - - { name: form.type_extension, extended_type: Netgen\Bundle\IbexaFormsBundle\Form\Type\InformationCollectionType } + - { name: form.type_extension, extended_type: Netgen\Bundle\InformationCollectionBundle\Form\Type\InformationCollectionType } diff --git a/lib/API/FieldHandler/CustomFieldHandlerInterface.php b/lib/API/FieldHandler/CustomFieldHandlerInterface.php index ac64ea8a..62c87614 100644 --- a/lib/API/FieldHandler/CustomFieldHandlerInterface.php +++ b/lib/API/FieldHandler/CustomFieldHandlerInterface.php @@ -4,8 +4,8 @@ namespace Netgen\InformationCollection\API\FieldHandler; +use Ibexa\Contracts\Core\FieldType\Value; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; -use Ibexa\Core\FieldType\Value; interface CustomFieldHandlerInterface { diff --git a/lib/API/FieldHandler/CustomLegacyFieldHandlerInterface.php b/lib/API/FieldHandler/CustomLegacyFieldHandlerInterface.php index 5db99768..4bf201b7 100644 --- a/lib/API/FieldHandler/CustomLegacyFieldHandlerInterface.php +++ b/lib/API/FieldHandler/CustomLegacyFieldHandlerInterface.php @@ -4,8 +4,9 @@ namespace Netgen\InformationCollection\API\FieldHandler; +use Ibexa\Contracts\Core\FieldType\Value; +use Ibexa\Contracts\Core\FieldType\Value as ValueInterface; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\Value\Legacy\FieldValue; interface CustomLegacyFieldHandlerInterface extends CustomFieldHandlerInterface @@ -15,4 +16,6 @@ interface CustomLegacyFieldHandlerInterface extends CustomFieldHandlerInterface * in legacy information collection database structure. */ public function getLegacyValue(Value $value, FieldDefinition $fieldDefinition): FieldValue; + + public function fromLegacyValue(FieldValue $legacyData): ?ValueInterface; } diff --git a/lib/Core/Factory/FieldDataFactory.php b/lib/Core/Factory/FieldDataFactory.php index b5474d58..38108d4e 100644 --- a/lib/Core/Factory/FieldDataFactory.php +++ b/lib/Core/Factory/FieldDataFactory.php @@ -4,8 +4,8 @@ namespace Netgen\InformationCollection\Core\Factory; +use Ibexa\Contracts\Core\FieldType\Value; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\Factory\FieldValueFactoryInterface; use Netgen\InformationCollection\API\FieldHandler\CustomFieldHandlerInterface; use Netgen\InformationCollection\API\FieldHandler\CustomLegacyFieldHandlerInterface; diff --git a/lib/Core/Persistence/FieldHandler/Custom/BirthdayFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/BirthdayFieldHandler.php new file mode 100644 index 00000000..940eb6d0 --- /dev/null +++ b/lib/Core/Persistence/FieldHandler/Custom/BirthdayFieldHandler.php @@ -0,0 +1,46 @@ +id, (string) $value, 0, 0); + } + + /** + * {@inheritDoc} + */ + public function fromLegacyValue(FieldValue $legacyData): ValueInterface + { + return new BirthdayValue($legacyData->getDataText()); + } +} diff --git a/lib/Core/Persistence/FieldHandler/Custom/CheckboxFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/CheckboxFieldHandler.php index 8d416daf..b5cba632 100644 --- a/lib/Core/Persistence/FieldHandler/Custom/CheckboxFieldHandler.php +++ b/lib/Core/Persistence/FieldHandler/Custom/CheckboxFieldHandler.php @@ -4,29 +4,35 @@ namespace Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom; +use Ibexa\Contracts\Core\FieldType\Value as ValueInterface; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; +use Ibexa\Core\FieldType\Checkbox\Value; use Ibexa\Core\FieldType\Checkbox\Value as CheckboxValue; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\FieldHandler\CustomLegacyFieldHandlerInterface; use Netgen\InformationCollection\API\Value\Legacy\FieldValue; class CheckboxFieldHandler implements CustomLegacyFieldHandlerInterface { - public function supports(Value $value): bool + public function supports(ValueInterface $value): bool { return $value instanceof CheckboxValue; } - public function toString(Value $value, FieldDefinition $fieldDefinition): string + public function toString(ValueInterface $value, FieldDefinition $fieldDefinition): string { return (string) $value; } /** - * @param \Ibexa\Core\FieldType\Checkbox\Value $value + * @param Value $value */ - public function getLegacyValue(Value $value, FieldDefinition $fieldDefinition): FieldValue + public function getLegacyValue(ValueInterface $value, FieldDefinition $fieldDefinition): FieldValue { return FieldValue::withIntValue($fieldDefinition->id, (int) $value->bool); } + + public function fromLegacyValue(FieldValue $legacyData): ValueInterface + { + return new CheckboxValue($legacyData->getDataInt() === 1); + } } diff --git a/lib/Core/Persistence/FieldHandler/Custom/CountryFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/CountryFieldHandler.php index 65f5ce8f..050c6f36 100644 --- a/lib/Core/Persistence/FieldHandler/Custom/CountryFieldHandler.php +++ b/lib/Core/Persistence/FieldHandler/Custom/CountryFieldHandler.php @@ -4,13 +4,19 @@ namespace Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom; +use Ibexa\Contracts\Core\FieldType\Value as ValueInterface; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; +use Ibexa\Core\FieldType\Country\Type as CountryType; +use Ibexa\Core\FieldType\Country\Value; use Ibexa\Core\FieldType\Country\Value as CountryValue; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\FieldHandler\CustomLegacyFieldHandlerInterface; use Netgen\InformationCollection\API\Value\Legacy\FieldValue; + use function array_column; +use function array_map; +use function explode; use function implode; +use function trim; /** * Overrides the original country handler to save country alpha2 code to collected info @@ -18,21 +24,35 @@ */ final class CountryFieldHandler implements CustomLegacyFieldHandlerInterface { - public function supports(Value $value): bool + private CountryType $countryType; + + public function __construct(CountryType $countryType) + { + $this->countryType = $countryType; + } + + public function supports(ValueInterface $value): bool { return $value instanceof CountryValue; } - public function toString(Value $value, FieldDefinition $fieldDefinition): string + public function toString(ValueInterface $value, FieldDefinition $fieldDefinition): string { return (string) $value; } /** - * @param \Ibexa\Core\FieldType\Country\Value $value + * @param Value $value */ - public function getLegacyValue(Value $value, FieldDefinition $fieldDefinition): FieldValue + public function getLegacyValue(ValueInterface $value, FieldDefinition $fieldDefinition): FieldValue { return FieldValue::withStringValue($fieldDefinition->id, implode(', ', array_column($value->countries, 'Alpha2'))); } + + public function fromLegacyValue(FieldValue $legacyData): ?ValueInterface + { + $countryCodes = explode(',', $legacyData->getDataText()); + + return $this->countryType->fromHash(array_map(static fn ($code) => trim($code), $countryCodes)); + } } diff --git a/lib/Core/Persistence/FieldHandler/Custom/DateAndTimeFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/DateAndTimeFieldHandler.php index 07db39f4..c4f99b26 100644 --- a/lib/Core/Persistence/FieldHandler/Custom/DateAndTimeFieldHandler.php +++ b/lib/Core/Persistence/FieldHandler/Custom/DateAndTimeFieldHandler.php @@ -4,20 +4,21 @@ namespace Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom; +use Ibexa\Contracts\Core\FieldType\Value as ValueInterface; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; +use Ibexa\Core\FieldType\DateAndTime\Value; use Ibexa\Core\FieldType\DateAndTime\Value as DateAndTimeValue; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\FieldHandler\CustomLegacyFieldHandlerInterface; use Netgen\InformationCollection\API\Value\Legacy\FieldValue; class DateAndTimeFieldHandler implements CustomLegacyFieldHandlerInterface { - public function supports(Value $value): bool + public function supports(ValueInterface $value): bool { return $value instanceof DateAndTimeValue; } - public function toString(Value $value, FieldDefinition $fieldDefinition): string + public function toString(ValueInterface $value, FieldDefinition $fieldDefinition): string { if ($value instanceof DateAndTimeValue) { return (string) $value; @@ -25,10 +26,12 @@ public function toString(Value $value, FieldDefinition $fieldDefinition): string } /** - * @param \Ibexa\Core\FieldType\DateAndTime\Value $value + * @param Value $value */ - public function getLegacyValue(Value $value, FieldDefinition $fieldDefinition): FieldValue + public function getLegacyValue(ValueInterface $value, FieldDefinition $fieldDefinition): FieldValue { return FieldValue::withIntValue($fieldDefinition->id, $value->value->getTimestamp()); } + + public function fromLegacyValue(FieldValue $legacyData): ?ValueInterface {} } diff --git a/lib/Core/Persistence/FieldHandler/Custom/DateFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/DateFieldHandler.php index be53e50b..6b75c873 100644 --- a/lib/Core/Persistence/FieldHandler/Custom/DateFieldHandler.php +++ b/lib/Core/Persistence/FieldHandler/Custom/DateFieldHandler.php @@ -4,29 +4,32 @@ namespace Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom; +use Ibexa\Contracts\Core\FieldType\Value as ValueInterface; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; +use Ibexa\Core\FieldType\Date\Value; use Ibexa\Core\FieldType\Date\Value as DateValue; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\FieldHandler\CustomLegacyFieldHandlerInterface; use Netgen\InformationCollection\API\Value\Legacy\FieldValue; class DateFieldHandler implements CustomLegacyFieldHandlerInterface { - public function supports(Value $value): bool + public function supports(ValueInterface $value): bool { return $value instanceof DateValue; } - public function toString(Value $value, FieldDefinition $fieldDefinition): string + public function toString(ValueInterface $value, FieldDefinition $fieldDefinition): string { return (string) $value; } /** - * @param \Ibexa\Core\FieldType\Date\Value $value + * @param Value $value */ - public function getLegacyValue(Value $value, FieldDefinition $fieldDefinition): FieldValue + public function getLegacyValue(ValueInterface $value, FieldDefinition $fieldDefinition): FieldValue { return FieldValue::withIntValue($fieldDefinition->id, $value->date->getTimestamp()); } + + public function fromLegacyValue(FieldValue $legacyData): ?ValueInterface {} } diff --git a/lib/Core/Persistence/FieldHandler/Custom/EmailAddressFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/EmailAddressFieldHandler.php new file mode 100644 index 00000000..ef792339 --- /dev/null +++ b/lib/Core/Persistence/FieldHandler/Custom/EmailAddressFieldHandler.php @@ -0,0 +1,43 @@ +id, $value->email, 0, 0); + } + + public function fromLegacyValue(FieldValue $legacyData): ValueInterface + { + return new EmailAddressValue($legacyData->getDataText()); + } +} diff --git a/lib/Core/Persistence/FieldHandler/Custom/EnhancedSelectionFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/EnhancedSelectionFieldHandler.php new file mode 100644 index 00000000..6bbb296d --- /dev/null +++ b/lib/Core/Persistence/FieldHandler/Custom/EnhancedSelectionFieldHandler.php @@ -0,0 +1,51 @@ +identifiers[0])) { + $identifier = $value->identifiers[0]; + } + + return new FieldValue($fieldDefinition->id, $identifier, 0, 0); + } + + /** + * {@inheritDoc} + */ + public function fromLegacyValue(FieldValue $legacyData): ValueInterface + { + return new EnhancedSelectionValue([$legacyData->getDataText()]); + } +} diff --git a/lib/Core/Persistence/FieldHandler/Custom/FloatFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/FloatFieldHandler.php index 566910aa..0076b025 100644 --- a/lib/Core/Persistence/FieldHandler/Custom/FloatFieldHandler.php +++ b/lib/Core/Persistence/FieldHandler/Custom/FloatFieldHandler.php @@ -4,29 +4,35 @@ namespace Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom; +use Ibexa\Contracts\Core\FieldType\Value as ValueInterface; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; +use Ibexa\Core\FieldType\Float\Value; use Ibexa\Core\FieldType\Float\Value as FloatValue; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\FieldHandler\CustomLegacyFieldHandlerInterface; use Netgen\InformationCollection\API\Value\Legacy\FieldValue; class FloatFieldHandler implements CustomLegacyFieldHandlerInterface { - public function supports(Value $value): bool + public function supports(ValueInterface $value): bool { return $value instanceof FloatValue; } - public function toString(Value $value, FieldDefinition $fieldDefinition): string + public function toString(ValueInterface $value, FieldDefinition $fieldDefinition): string { return (string) $value; } /** - * @param \Ibexa\Core\FieldType\Float\Value $value + * @param Value $value */ - public function getLegacyValue(Value $value, FieldDefinition $fieldDefinition): FieldValue + public function getLegacyValue(ValueInterface $value, FieldDefinition $fieldDefinition): FieldValue { return FieldValue::withFloatValue($fieldDefinition->id, $value->value); } + + public function fromLegacyValue(FieldValue $legacyData): FloatValue + { + return new FloatValue($legacyData->getDataFloat()); + } } diff --git a/lib/Core/Persistence/FieldHandler/Custom/IntegerFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/IntegerFieldHandler.php index 7f3e5631..fd146c98 100644 --- a/lib/Core/Persistence/FieldHandler/Custom/IntegerFieldHandler.php +++ b/lib/Core/Persistence/FieldHandler/Custom/IntegerFieldHandler.php @@ -4,29 +4,35 @@ namespace Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom; +use Ibexa\Contracts\Core\FieldType\Value as ValueInterface; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; +use Ibexa\Core\FieldType\Integer\Value; use Ibexa\Core\FieldType\Integer\Value as IntegerValue; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\FieldHandler\CustomLegacyFieldHandlerInterface; use Netgen\InformationCollection\API\Value\Legacy\FieldValue; class IntegerFieldHandler implements CustomLegacyFieldHandlerInterface { - public function supports(Value $value): bool + public function supports(ValueInterface $value): bool { return $value instanceof IntegerValue; } - public function toString(Value $value, FieldDefinition $fieldDefinition): string + public function toString(ValueInterface $value, FieldDefinition $fieldDefinition): string { return (string) $value; } /** - * @param \Ibexa\Core\FieldType\Integer\Value $value + * @param Value $value */ - public function getLegacyValue(Value $value, FieldDefinition $fieldDefinition): FieldValue + public function getLegacyValue(ValueInterface $value, FieldDefinition $fieldDefinition): FieldValue { return FieldValue::withIntValue($fieldDefinition->id, $value->value); } + + public function fromLegacyValue(FieldValue $legacyData): IntegerValue + { + return new IntegerValue($legacyData->getDataInt()); + } } diff --git a/lib/Core/Persistence/FieldHandler/Custom/StringFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/StringFieldHandler.php new file mode 100644 index 00000000..49bb0486 --- /dev/null +++ b/lib/Core/Persistence/FieldHandler/Custom/StringFieldHandler.php @@ -0,0 +1,43 @@ +id, $value->text, 0, 0); + } + + public function fromLegacyValue(FieldValue $legacyData): ValueInterface + { + return new TextLineValue($legacyData->getDataText()); + } +} diff --git a/lib/Core/Persistence/FieldHandler/Custom/TimeFieldHandler.php b/lib/Core/Persistence/FieldHandler/Custom/TimeFieldHandler.php index 22e97a27..68a5860d 100644 --- a/lib/Core/Persistence/FieldHandler/Custom/TimeFieldHandler.php +++ b/lib/Core/Persistence/FieldHandler/Custom/TimeFieldHandler.php @@ -4,29 +4,32 @@ namespace Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom; +use Ibexa\Contracts\Core\FieldType\Value as ValueInterface; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; +use Ibexa\Core\FieldType\Time\Value; use Ibexa\Core\FieldType\Time\Value as TimeValue; -use Ibexa\Core\FieldType\Value; use Netgen\InformationCollection\API\FieldHandler\CustomLegacyFieldHandlerInterface; use Netgen\InformationCollection\API\Value\Legacy\FieldValue; class TimeFieldHandler implements CustomLegacyFieldHandlerInterface { - public function supports(Value $value): bool + public function supports(ValueInterface $value): bool { return $value instanceof TimeValue; } - public function toString(Value $value, FieldDefinition $fieldDefinition): string + public function toString(ValueInterface $value, FieldDefinition $fieldDefinition): string { return (string) $value; } /** - * @param \Ibexa\Core\FieldType\Time\Value $value + * @param Value $value */ - public function getLegacyValue(Value $value, FieldDefinition $fieldDefinition): FieldValue + public function getLegacyValue(ValueInterface $value, FieldDefinition $fieldDefinition): FieldValue { return FieldValue::withIntValue($fieldDefinition->id, (int) $value->time); } + + public function fromLegacyValue(FieldValue $legacyData): ValueInterface {} } diff --git a/lib/Core/Persistence/FieldHandler/FieldHandlerRegistry.php b/lib/Core/Persistence/FieldHandler/FieldHandlerRegistry.php index b3c0e633..2ea2de2a 100644 --- a/lib/Core/Persistence/FieldHandler/FieldHandlerRegistry.php +++ b/lib/Core/Persistence/FieldHandler/FieldHandlerRegistry.php @@ -4,7 +4,7 @@ namespace Netgen\InformationCollection\Core\Persistence\FieldHandler; -use Ibexa\Core\FieldType\Value; +use Ibexa\Contracts\Core\FieldType\Value; use Netgen\InformationCollection\API\FieldHandler\CustomFieldHandlerInterface; final class FieldHandlerRegistry diff --git a/lib/Doctrine/Repository/EzInfoCollectionAttributeRepository.php b/lib/Doctrine/Repository/EzInfoCollectionAttributeRepository.php index f4069e79..30067b61 100644 --- a/lib/Doctrine/Repository/EzInfoCollectionAttributeRepository.php +++ b/lib/Doctrine/Repository/EzInfoCollectionAttributeRepository.php @@ -19,6 +19,7 @@ use Netgen\InformationCollection\API\Value\Legacy\FieldValue; use Netgen\InformationCollection\Doctrine\Entity\EzInfoCollection; use Netgen\InformationCollection\Doctrine\Entity\EzInfoCollectionAttribute; + use function array_column; use function mb_strtolower; @@ -90,14 +91,30 @@ public function findByCollectionIdAndFieldDefinitionIds(int $collectionId, array $qb = $this->createQueryBuilder('eica'); return $qb->select('eica') - ->where('eica.informationCollectionId = :collection-id') - ->setParameter('collection-id', $collectionId) + ->where('eica.informationCollectionId = :collectionId') + ->setParameter('collectionId', $collectionId) ->andWhere($qb->expr()->in('eica.contentClassAttributeId', ':fields')) ->setParameter('fields', $fieldDefinitionIds) ->getQuery() ->getResult(); } + /** + * @throws NonUniqueResultException + */ + public function findByCollectionIdAndFieldDefinitionId(int $collectionId, int $fieldDefinitionId): mixed + { + $qb = $this->createQueryBuilder('eica'); + + return $qb->select('eica') + ->where('eica.informationCollectionId = :collectionId') + ->setParameter('collectionId', $collectionId) + ->andWhere('eica.contentClassAttributeId = :fieldId') + ->setParameter('fieldId', $fieldDefinitionId) + ->getQuery() + ->getOneOrNullResult(); + } + public function updateByCollectionId(CollectionId $collectionId, Attribute $attribute): void { $entity = $this->findOneBy([ diff --git a/lib/Form/Type/FieldType/Birthday/Value.php b/lib/Form/Type/FieldType/Birthday/Value.php new file mode 100644 index 00000000..8de6c33c --- /dev/null +++ b/lib/Form/Type/FieldType/Birthday/Value.php @@ -0,0 +1,41 @@ +date = $date->setTime(0, 0); + } elseif (is_string($date)) { + $this->date = new DateTimeImmutable($date); + } + } + + public function __toString(): string + { + if (!$this->date instanceof DateTimeInterface) { + return ''; + } + + return $this->date->format($this->dateFormat); + } +} \ No newline at end of file diff --git a/lib/Form/Type/FieldType/EnhancedSelection/Value.php b/lib/Form/Type/FieldType/EnhancedSelection/Value.php new file mode 100644 index 00000000..832c2024 --- /dev/null +++ b/lib/Form/Type/FieldType/EnhancedSelection/Value.php @@ -0,0 +1,31 @@ +identifiers = $identifiers; + } + + public function __toString(): string + { + return implode(', ', $this->identifiers); + } +} + diff --git a/lib/Resources/config/admin.yml b/lib/Resources/config/admin.yml index 05388ff4..0164a911 100644 --- a/lib/Resources/config/admin.yml +++ b/lib/Resources/config/admin.yml @@ -53,8 +53,24 @@ services: - !tagged_iterator netgen_information_collection.export.formatter netgen_information_collection.form.type.export: - class: Netgen\Bundle\InformationCollectionBundle\Form\ExportType + class: Netgen\Bundle\InformationCollectionBundle\Form\Type\ExportType arguments: - "@netgen_information_collection.core.export.registry" tags: - { name: form.type } + + netgen_information_collection.form.data_mapper.info_collection_update: + class: Netgen\Bundle\InformationCollectionBundle\Form\DataMapper\InformationCollectionUpdateMapper + public: false + arguments: + - "@netgen_information_collection.form.fieldtype_handler_registry" + + netgen_information_collection.form.type.information_collection_update: + class: Netgen\Bundle\InformationCollectionBundle\Form\Type\InformationCollectionUpdateType + # public: false + arguments: + - "@netgen_information_collection.form.fieldtype_handler_registry" + - "@netgen_information_collection.form.data_mapper.info_collection_update" + - "@ibexa.config.resolver" + tags: + - { name: form.type, alias: ibexaforms_information_collection_update } diff --git a/lib/Resources/config/form_fieldtype_handlers.yaml b/lib/Resources/config/form_fieldtype_handlers.yaml new file mode 100644 index 00000000..94cf7bbc --- /dev/null +++ b/lib/Resources/config/form_fieldtype_handlers.yaml @@ -0,0 +1,109 @@ +services: + netgen_information_collection.form.fieldtype_handler.ezimage: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Image + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezimage } + + netgen_information_collection.form.fieldtype_handler.ezstring: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\TextLine + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezstring } + + netgen_information_collection.form.fieldtype_handler.eztext: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\TextBlock + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: eztext } + + netgen_information_collection.form.fieldtype_handler.ezemail: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Email + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezemail } + + netgen_information_collection.form.fieldtype_handler.ezselection: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Selection + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezselection } + + netgen_information_collection.form.fieldtype_handler.ezboolean: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Checkbox + arguments: [ "@Ibexa\\Core\\Helper\\FieldHelper" ] + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezboolean } + + netgen_information_collection.form.fieldtype_handler.ezdate: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Date + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezdate } + + netgen_information_collection.form.fieldtype_handler.ezdatetime: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\DateAndTime + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezdatetime } + + netgen_information_collection.form.fieldtype_handler.ezinteger: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\IntegerHandler + arguments: [ "@Ibexa\\Core\\Helper\\FieldHelper" ] + tags: + - {name: netgen_information_collection.form.fieldtype_handler, alias: ezinteger} + + netgen_information_collection.form.fieldtype_handler.ezfloat: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\FloatHandler + arguments: [ "@Ibexa\\Core\\Helper\\FieldHelper" ] + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezfloat } + + netgen_information_collection.form.fieldtype_handler.ezurl: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Url + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezurl } + + netgen_information_collection.form.fieldtype_handler.ezcountry: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Country + arguments: + - "%ibexa.field_type.country.data%" + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezcountry } + + netgen_information_collection.form.fieldtype_handler.eztime: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Time + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: eztime } + + netgen_information_collection.form.fieldtype_handler.ezisbn: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Isbn + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezisbn } + + netgen_information_collection.form.fieldtype_handler.ezbinaryfile: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\BinaryFile + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezbinaryfile } + + netgen_information_collection.form.fieldtype_handler.ezgmaplocation: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\MapLocation + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezgmaplocation } + + netgen_information_collection.form.fieldtype_handler.ezobjectrelation: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Relation + arguments: + - "@ibexa.api.repository" + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezobjectrelation } + + netgen_information_collection.form.fieldtype_handler.ezobjectrelationlist: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\RelationList + arguments: + - "@ibexa.api.repository" + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezobjectrelationlist } + + netgen_information_collection.form.fieldtype_handler.ezbirthday: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\Birthday + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: ezbirthday } + + netgen_information_collection.form.fieldtype_handler.sckenhancedselection: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandler\EnhancedSelection + tags: + - { name: netgen_information_collection.form.fieldtype_handler, alias: sckenhancedselection } diff --git a/lib/Resources/config/legacy_handlers.yml b/lib/Resources/config/legacy_handlers.yml index 019f4f40..0342b1db 100644 --- a/lib/Resources/config/legacy_handlers.yml +++ b/lib/Resources/config/legacy_handlers.yml @@ -28,3 +28,23 @@ services: class: Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom\DateFieldHandler tags: - { name: netgen_information_collection.field_handler.custom } + + netgen_information_collection.field_handler.string: + class: Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom\StringFieldHandler + tags: + - { name: netgen_information_collection.field_handler.custom } + + netgen_information_collection.field_handler.birthday: + class: Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom\BirthdayFieldHandler + tags: + - { name: netgen_information_collection.field_handler.custom } + + netgen_information_collection.field_handler.email_address: + class: Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom\EmailAddressFieldHandler + tags: + - { name: netgen_information_collection.field_handler.custom } + + netgen_information_collection.field_handler.enhanced_selection: + class: Netgen\InformationCollection\Core\Persistence\FieldHandler\Custom\EnhancedSelectionFieldHandler + tags: + - { name: netgen_information_collection.field_handler.custom } diff --git a/lib/Resources/config/services.yml b/lib/Resources/config/services.yml index cdbe316c..7e86aa27 100644 --- a/lib/Resources/config/services.yml +++ b/lib/Resources/config/services.yml @@ -7,6 +7,7 @@ imports: - { resource: admin.yml } - { resource: anonymizers.yml } - { resource: ibexa_admin.yml } + - { resource: form_fieldtype_handlers.yaml } parameters: @@ -99,7 +100,7 @@ services: - { name: netgen_information_collection.export.formatter } netgen_information_collection.form.captcha_type: - class: Netgen\Bundle\InformationCollectionBundle\Form\CaptchaType + class: Netgen\Bundle\InformationCollectionBundle\Form\Type\CaptchaType arguments: - '@netgen_information_collection.listener.captcha_validation' tags: @@ -116,3 +117,6 @@ services: - '@netgen_information_collection.captcha.service' tags: - { name: twig.runtime } + + netgen_information_collection.form.fieldtype_handler_registry: + class: Netgen\Bundle\InformationCollectionBundle\Form\FieldTypeHandlerRegistry diff --git a/tests/old/Controller/InformationCollectionControllerTest.php b/tests/old/Controller/InformationCollectionControllerTest.php index 9cf5b375..c832a699 100644 --- a/tests/old/Controller/InformationCollectionControllerTest.php +++ b/tests/old/Controller/InformationCollectionControllerTest.php @@ -4,8 +4,8 @@ use Ibexa\Core\MVC\Symfony\View\ContentView; use Ibexa\Core\Repository\Values\Content\Location; -use Netgen\Bundle\IbexaFormsBundle\Form\DataWrapper; -use Netgen\Bundle\IbexaFormsBundle\Form\Payload\InformationCollectionStruct; +use Netgen\Bundle\InformationCollectionBundle\Form\DataWrapper; +use Netgen\Bundle\InformationCollectionBundle\Form\Payload\InformationCollectionStruct; use Netgen\Bundle\InformationCollectionBundle\Controller\InformationCollectionController; use Netgen\Bundle\InformationCollectionBundle\Form\Builder\FormBuilder; use Netgen\Bundle\InformationCollectionBundle\Tests\ContentViewStub; diff --git a/tests/old/Factory/EmailDataFactoryTest.php b/tests/old/Factory/EmailDataFactoryTest.php index d54000c5..2b42d25b 100644 --- a/tests/old/Factory/EmailDataFactoryTest.php +++ b/tests/old/Factory/EmailDataFactoryTest.php @@ -13,8 +13,8 @@ use Ibexa\Core\Repository\Values\Content\Location; use Ibexa\Core\Repository\Values\Content\VersionInfo; use Ibexa\Core\Repository\Values\ContentType\ContentType; -use Netgen\Bundle\IbexaFormsBundle\Form\DataWrapper; -use Netgen\Bundle\IbexaFormsBundle\Form\Payload\InformationCollectionStruct; +use Netgen\Bundle\InformationCollectionBundle\Form\DataWrapper; +use Netgen\Bundle\InformationCollectionBundle\Form\Payload\InformationCollectionStruct; use Netgen\Bundle\InformationCollectionBundle\Event\InformationCollected; use Netgen\Bundle\InformationCollectionBundle\Factory\EmailDataFactory; use Netgen\Bundle\InformationCollectionBundle\Value\EmailData;