custom/plugins/MoorlForms/src/Core/Service/FbService.php line 254

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace MoorlForms\Core\Service;
  3. use Doctrine\DBAL\Connection;
  4. use Monolog\Logger;
  5. use MoorlForms\Core\Content\Element\ElementAvailableFilter;
  6. use MoorlForms\Core\Content\Element\ElementCollection;
  7. use MoorlForms\Core\Content\Element\ElementDataStruct;
  8. use MoorlForms\Core\Content\Element\ElementEntity;
  9. use MoorlForms\Core\Content\Element\ElementPriceCalculator;
  10. use MoorlForms\Core\Content\Element\Selection\ElementSelectionInterface;
  11. use MoorlForms\Core\Content\Element\Tree\Tree;
  12. use MoorlForms\Core\Content\Element\Tree\TreeItem;
  13. use MoorlForms\Core\Content\Element\Type\ElementTypeInterface;
  14. use MoorlForms\Core\Content\Element\Type\Fields\ElementTypeUpload;
  15. use MoorlForms\Core\Content\Form\Action\FormActionInterface;
  16. use MoorlForms\Core\Content\Form\FormAvailableFilter;
  17. use MoorlForms\Core\Content\Form\FormCollection;
  18. use MoorlForms\Core\Content\Form\FormDefinition;
  19. use MoorlForms\Core\Content\Form\FormEntity;
  20. use MoorlForms\Core\Content\Form\FormSubmitResponse;
  21. use MoorlForms\Core\Content\Form\Type\FormTypeInterface;
  22. use MoorlForms\Core\Event\SubmitFormEntityEvent;
  23. use MoorlForms\Core\Event\SubmitFormErrorEvent;
  24. use MoorlForms\Core\Event\SubmitFormSuccessEvent;
  25. use MoorlForms\Core\Event\ValidateFormEvent;
  26. use MoorlForms\MoorlForms;
  27. use MoorlFoundation\Core\PluginHelpers;
  28. use Psr\Log\LoggerInterface;
  29. use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
  30. use Shopware\Core\Content\Category\CategoryDefinition;
  31. use Shopware\Core\Content\Category\SalesChannel\CachedCategoryRoute;
  32. use Shopware\Core\Content\Category\SalesChannel\CachedNavigationRoute;
  33. use Shopware\Core\Content\Media\MediaCollection;
  34. use Shopware\Core\Content\Media\MediaDefinition;
  35. use Shopware\Core\Content\Media\MediaEntity;
  36. use Shopware\Core\Content\Product\ProductDefinition;
  37. use Shopware\Core\Content\Product\ProductEntity;
  38. use Shopware\Core\Content\Product\SalesChannel\ProductAvailableFilter;
  39. use Shopware\Core\Framework\Adapter\Cache\CacheInvalidator;
  40. use Shopware\Core\Framework\Context;
  41. use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
  42. use Shopware\Core\Framework\DataAbstractionLayer\Entity;
  43. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  44. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  45. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\Filter;
  46. use Shopware\Core\Framework\Log\LoggerFactory;
  47. use Shopware\Core\Framework\Uuid\Uuid;
  48. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  49. use Shopware\Core\System\SystemConfig\SystemConfigService;
  50. use Shopware\Storefront\Framework\Media\StorefrontMediaUploader;
  51. use Symfony\Component\HttpFoundation\File\UploadedFile;
  52. use Symfony\Component\HttpFoundation\Request;
  53. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  54. class FbService
  55. {
  56.     private Context $context;
  57.     private DefinitionInstanceRegistry $definitionInstanceRegistry;
  58.     private SystemConfigService $systemConfigService;
  59.     private LoggerInterface $logger;
  60.     private Connection $connection;
  61.     private EventDispatcherInterface $eventDispatcher;
  62.     private ElementPriceCalculator $elementPriceCalculator;
  63.     private StorefrontMediaUploader $storefrontMediaUploader;
  64.     private CacheInvalidator $cacheInvalidator;
  65.     /**
  66.      * @var iterable<FormActionInterface>
  67.      */
  68.     private iterable $formActions;
  69.     /**
  70.      * @var iterable<FormTypeInterface>
  71.      */
  72.     private iterable $formTypes;
  73.     /**
  74.      * @var iterable<ElementTypeInterface>
  75.      */
  76.     private iterable $elementTypes;
  77.     /**
  78.      * @var iterable<ElementSelectionInterface>
  79.      */
  80.     private iterable $elementSelections;
  81.     private TreeItem $treeItem;
  82.     private array $messages = [];
  83.     public function __construct(
  84.         DefinitionInstanceRegistry $definitionInstanceRegistry,
  85.         Connection $connection,
  86.         LoggerFactory $loggerFactory,
  87.         SystemConfigService $systemConfigService,
  88.         EventDispatcherInterface $eventDispatcher,
  89.         ElementPriceCalculator $elementPriceCalculator,
  90.         StorefrontMediaUploader $storefrontMediaUploader,
  91.         CacheInvalidator $cacheInvalidator,
  92.         iterable $formActions,
  93.         iterable $formTypes,
  94.         iterable $elementTypes,
  95.         iterable $elementSelections
  96.     )
  97.     {
  98.         $this->definitionInstanceRegistry $definitionInstanceRegistry;
  99.         $this->connection $connection;
  100.         $this->systemConfigService $systemConfigService;
  101.         $this->eventDispatcher $eventDispatcher;
  102.         $this->elementPriceCalculator $elementPriceCalculator;
  103.         $this->storefrontMediaUploader $storefrontMediaUploader;
  104.         $this->cacheInvalidator $cacheInvalidator;
  105.         $this->formActions $formActions;
  106.         $this->formTypes $formTypes;
  107.         $this->elementTypes $elementTypes;
  108.         $this->elementSelections $elementSelections;
  109.         $this->logger $loggerFactory->createRotating(
  110.             'moorl_fb',
  111.             1,
  112.             getenv('APP_ENV') === 'prod' Logger::NOTICE Logger::DEBUG
  113.         );
  114.         $this->context Context::createDefaultContext();
  115.         $this->treeItem = new TreeItem(null, []);
  116.     }
  117.     public function getForm(string $formIdbool $withTranslation false): FormEntity
  118.     {
  119.         $criteria = new Criteria([$formId]);
  120.         $criteria->addAssociation('elements');
  121.         $criteria->setLimit(1);
  122.         if ($withTranslation) {
  123.             $criteria->addAssociation('translations');
  124.             $criteria->addAssociation('elements.translations');
  125.         }
  126.         $formRepository $this->definitionInstanceRegistry->getRepository(FormDefinition::ENTITY_NAME);
  127.         return $formRepository->search($criteria$this->context)->get($formId);
  128.     }
  129.     public function saveForm(array $payload): void
  130.     {
  131.         $formRepository $this->definitionInstanceRegistry->getRepository(FormDefinition::ENTITY_NAME);
  132.         $formRepository->upsert([$payload], $this->context);
  133.     }
  134.     public function initForm(?string $formIdSalesChannelContext $salesChannelContext): ?FormEntity
  135.     {
  136.         if (!$formId) {
  137.             return null;
  138.         }
  139.         $criteria $this->createFormCriteria($salesChannelContext);
  140.         $criteria->setLimit(1);
  141.         if (Uuid::isValid($formId)) {
  142.             $criteria->setIds([$formId]);
  143.         } else {
  144.             $criteria->addFilter(new EqualsFilter('technicalName'$formId));
  145.         }
  146.         $formRepository $this->definitionInstanceRegistry->getRepository(FormDefinition::ENTITY_NAME);
  147.         /** @var FormEntity $form */
  148.         $form $formRepository->search($criteria$salesChannelContext->getContext())->first();
  149.         if (!$form) {
  150.             return null;
  151.         }
  152.         $this->initFormComponents($form$salesChannelContext);
  153.         return $form;
  154.     }
  155.     public function initForms(array $formIdsSalesChannelContext $salesChannelContext): ?FormCollection
  156.     {
  157.         $criteria $this->createFormCriteria($salesChannelContext);
  158.         $criteria->setIds($formIds);
  159.         $formRepository $this->definitionInstanceRegistry->getRepository(FormDefinition::ENTITY_NAME);
  160.         /** @var FormCollection $forms */
  161.         $forms $formRepository->search($criteria$salesChannelContext->getContext())->getEntities();
  162.         foreach ($forms as $form) {
  163.             $this->initFormComponents($form$salesChannelContext);
  164.         }
  165.         return $forms;
  166.     }
  167.     public function initFormsByFilter(Filter $filterSalesChannelContext $salesChannelContext): ?FormCollection
  168.     {
  169.         $criteria $this->createFormCriteria($salesChannelContext);
  170.         $criteria->addFilter($filter);
  171.         $formRepository $this->definitionInstanceRegistry->getRepository(FormDefinition::ENTITY_NAME);
  172.         /** @var FormCollection $forms */
  173.         $forms $formRepository->search($criteria$salesChannelContext->getContext())->getEntities();
  174.         foreach ($forms as $form) {
  175.             $this->initFormComponents($form$salesChannelContext);
  176.         }
  177.         return $forms;
  178.     }
  179.     public function initFormsByType(string $formTypeSalesChannelContext $salesChannelContext): ?FormCollection
  180.     {
  181.         return $this->initFormsByFilter(
  182.             new EqualsFilter('type'$formType),
  183.             $salesChannelContext
  184.         );
  185.     }
  186.     public function initFormsByRelatedEntity(string $relatedEntitySalesChannelContext $salesChannelContext): ?FormCollection
  187.     {
  188.         return $this->initFormsByFilter(
  189.             new EqualsFilter('relatedEntity'$relatedEntity),
  190.             $salesChannelContext
  191.         );
  192.     }
  193.     public function initFormComponents(FormEntity $form$salesChannelContext): void
  194.     {
  195.         /* Add services to form actions */
  196.         foreach ($this->formActions as $formAction) {
  197.             $formAction->setSystemConfigService($this->systemConfigService);
  198.             $formAction->setSalesChannelContext($salesChannelContext);
  199.             $formAction->setEventDispatcher($this->eventDispatcher);
  200.         }
  201.         /* Init form actions (test connections etc.) */
  202.         foreach ($this->formActions as $formAction) {
  203.             try {
  204.                 $formAction->init($form);
  205.             } catch (\Exception $exception) {
  206.                 $this->addMessage($exception->getMessage(), $exception->getCode());
  207.             }
  208.         }
  209.         /* Init elements */
  210.         foreach ($form->getElements() as $element) {
  211.             $element->setTaxId($form->getTaxId());
  212.             $this->initElement($element$salesChannelContext);
  213.             $this->initElementSelections($element$form->getElements(), $salesChannelContext);
  214.         }
  215.         /* Build tree structure for the elements */
  216.         $form->setTree($this->getTree(null$form->getElements()));
  217.     }
  218.     public function initElement(ElementEntity $elementSalesChannelContext $salesChannelContext): void
  219.     {
  220.         foreach ($this->elementTypes as $elementType) {
  221.             if ($elementType->getName() !== $element->getType()) {
  222.                 continue;
  223.             }
  224.             $elementType->setSalesChannelContext($salesChannelContext);
  225.             $elementType->setSystemConfigService($this->systemConfigService);
  226.             $elementType->init($element);
  227.             break;
  228.         }
  229.     }
  230.     public function initElementSelections(
  231.         ElementEntity $element,
  232.         ElementCollection $elements,
  233.         SalesChannelContext $salesChannelContext
  234.     ): void
  235.     {
  236.         if (!$element->getSelection()) {
  237.             return;
  238.         }
  239.         foreach ($this->elementTypes as $elementType) {
  240.             if ($elementType->getName() !== $element->getType()) {
  241.                 continue;
  242.             }
  243.             if ($elementType->getGroup() !== 'multi-fields') {
  244.                 continue;
  245.             }
  246.             foreach ($this->elementSelections as $elementSelection) {
  247.                 if ($elementSelection->getName() !== $element->getSelection()) {
  248.                     continue;
  249.                 }
  250.                 $elementSelection->setSalesChannelContext($salesChannelContext);
  251.                 $elementSelection->setSystemConfigService($this->systemConfigService);
  252.                 $elementSelection->init($element$elements);
  253.                 break;
  254.             }
  255.             break;
  256.         }
  257.     }
  258.     public function validateForm(FormEntity $form): void
  259.     {
  260.         foreach ($this->formActions as $formAction) {
  261.             try {
  262.                 $formAction->validate($form);
  263.             } catch (\Exception $exception) {
  264.                 $this->addMessage(
  265.                     $exception->getMessage(),
  266.                     $exception->getCode()
  267.                 );
  268.             }
  269.         }
  270.         foreach ($form->getDataStruct()->getElements() as $elementData) {
  271.             $this->validateElementData($elementData);
  272.         }
  273.     }
  274.     public function validateElementData(ElementDataStruct $elementData): void
  275.     {
  276.         foreach ($this->elementTypes as $elementType) {
  277.             if ($elementType->getName() !== $elementData->getType()) {
  278.                 continue;
  279.             }
  280.             try {
  281.                 $elementType->validate($elementData);
  282.             } catch (\Exception $exception) {
  283.                 $this->handleElementException($exception$elementData);
  284.             }
  285.         }
  286.     }
  287.     public function getFormId(Request $request): string
  288.     {
  289.         return $request->get('_formId');
  290.     }
  291.     public function enrichForm(
  292.         FormEntity $form,
  293.         array $flattenElements,
  294.         SalesChannelContext $salesChannelContext
  295.     ): void
  296.     {
  297.         /* Convert flatten elements to ElementDataStruct[] */
  298.         DataStructFactory::convertFlattenElements($flattenElements);
  299.         /** @var ElementDataStruct $flattenElement */
  300.         foreach ($flattenElements as $path => $flattenElement) {
  301.             $element $form->getElements()->get($flattenElement->getElementId());
  302.             if (!$element) {
  303.                 continue;
  304.             }
  305.             $element->setValue($flattenElement->getValue());
  306.             /* Enrich media for upload field */
  307.             if ($flattenElement->getMediaId()) {
  308.                 $media $this->getMedia($flattenElement->getMediaId(), $salesChannelContext->getContext());
  309.                 $element->setMedia($media);
  310.                 //$element->setMediaId($flattenElement->getMediaId());
  311.                 $flattenElement->setMedia($media);
  312.             }
  313.         }
  314.         /* Rebuild data tree without struct branches, inherit conditions */
  315.         DataStructFactory::enrichFormDataStruct($form$this->elementTypes);
  316.         $form->getDataStruct()->setFlattenElements($flattenElements);
  317.     }
  318.     public function getEntity(string $entityNamestring $id): ?Entity
  319.     {
  320.         $repo $this->definitionInstanceRegistry->getRepository($entityName);
  321.         $criteria = new Criteria([$id]);
  322.         $criteria->setLimit(1);
  323.         return $repo->search($criteria$this->context)->get($id);
  324.     }
  325.     public function getEntityFromRequest(Request $request): ?Entity
  326.     {
  327.         if (
  328.             $request->request->get(MoorlForms::ENTITY_ID_KEY) &&
  329.             $request->request->get(MoorlForms::ENTITY_NAME_KEY)
  330.         ) {
  331.             return $this->getEntity(
  332.                 $request->request->get(MoorlForms::ENTITY_NAME_KEY),
  333.                 $request->request->get(MoorlForms::ENTITY_ID_KEY)
  334.             );
  335.         }
  336.         return null;
  337.     }
  338.     public function submitForm(
  339.         FormEntity $form,
  340.         Request $request,
  341.         SalesChannelContext $salesChannelContext
  342.     ): FormSubmitResponse
  343.     {
  344.         /* Since Shopware captchas need the request */
  345.         foreach ($this->elementTypes as $elementType) {
  346.             $elementType->setRequest($request);
  347.         }
  348.         foreach ($this->formActions as $formAction) {
  349.             $formAction->setRequest($request);
  350.         }
  351.         $entity $this->getEntityFromRequest($request);
  352.         if ($entity) {
  353.             if (method_exists($entity'getEmail')) {
  354.                 $entityMailDetection $this->systemConfigService->get('MoorlForms.config.entityMailDetection'$salesChannelContext->getSalesChannelId());
  355.                 if ($entityMailDetection === 'yes-replace') {
  356.                     $form->setConfigValue('mailReceiver'$entity->getEmail());
  357.                 } elseif ($entityMailDetection === 'yes-append') {
  358.                     $mailReceiver explode(";"$form->getConfig()['mailReceiver']);
  359.                     $mailReceiver[] = $entity->getEmail();
  360.                     $form->setConfigValue('mailReceiver'implode(";"$mailReceiver));
  361.                 }
  362.             }
  363.             $event = new SubmitFormEntityEvent($entity$salesChannelContext$form$request);
  364.             $this->eventDispatcher->dispatch($event);
  365.             /* Clear cache of category page */
  366.             if ($request->request->get(MoorlForms::ENTITY_NAME_KEY) === CategoryDefinition::ENTITY_NAME) {
  367.                 $this->cacheInvalidator->invalidate(
  368.                     array_map([CachedNavigationRoute::class, 'buildName'], [
  369.                         $request->request->get(MoorlForms::ENTITY_ID_KEY)
  370.                     ])
  371.                 );
  372.                 $this->cacheInvalidator->invalidate(
  373.                     array_map([CachedCategoryRoute::class, 'buildName'], [
  374.                         $request->request->get(MoorlForms::ENTITY_ID_KEY)
  375.                     ])
  376.                 );
  377.                 $this->cacheInvalidator->run();
  378.             }
  379.         }
  380.         /* Merge files and values */
  381.         $values array_replace_recursive(
  382.             $request->files->all(),
  383.             $request->request->all()
  384.         );
  385.         $this->logger->debug("Merge files and values"$values);
  386.         /* Rebuild data tree without struct branches, inherit conditions */
  387.         DataStructFactory::enrichFormDataStruct($form$this->elementTypes);
  388.         /* Set request values to form */
  389.         DataStructFactory::enrichValues($form$values);
  390.         /* Start recursive form validation */
  391.         $this->eventDispatcher->dispatch(new ValidateFormEvent($salesChannelContext$form));
  392.         $this->validateForm($form);
  393.         /* Early return if validation fails */
  394.         if ($this->hasErrorMessage()) {
  395.             $this->addMessage($form->getTranslation('errorMessage'), 0$form->getValues());
  396.             return new FormSubmitResponse($form$this->getMessages());
  397.         }
  398.         /* Set attachments to form */
  399.         DataStructFactory::enrichAttachments($form);
  400.         /* Process form actions (send mails etc.) */
  401.         foreach ($this->formActions as $formAction) {
  402.             if (!in_array($formAction->getName(), $form->getActions())) {
  403.                 continue;
  404.             }
  405.             /* Do not catch errors if form in test mode */
  406.             if ($form->getType() === 'test') {
  407.                 $formAction->process($form);
  408.             } else {
  409.                 try {
  410.                     $formAction->process($form);
  411.                 } catch (\Exception $exception) {
  412.                     $this->addMessage("processError");
  413.                     $this->logger->error($exception->getMessage(), $exception->getTrace());
  414.                 }
  415.             }
  416.         }
  417.         /* Add success message if no errors count */
  418.         if (empty($this->getMessages()) && $form->getTranslation('successMessage')) {
  419.             $this->addMessage(
  420.                 $form->getTranslation('successMessage'),
  421.                 0,
  422.                 $form->getValues(),
  423.                 MoorlForms::SUCCESS
  424.             );
  425.             $event = new SubmitFormSuccessEvent(
  426.                 $salesChannelContext,
  427.                 $form,
  428.                 $request
  429.             );
  430.             $this->eventDispatcher->dispatch($event);
  431.         } else {
  432.             $event = new SubmitFormErrorEvent(
  433.                 $salesChannelContext,
  434.                 $form,
  435.                 $request
  436.             );
  437.             $this->eventDispatcher->dispatch($event);
  438.         }
  439.         return new FormSubmitResponse($form$this->getMessages(), true);
  440.     }
  441.     public function submit(Request $requestSalesChannelContext $salesChannelContext): FormSubmitResponse
  442.     {
  443.         $form $this->initForm($this->getFormId($request), $salesChannelContext);
  444.         return $this->submitForm($form$request$salesChannelContext);
  445.     }
  446.     public function handleUploadedFiles(FormEntity $formSalesChannelContext $salesChannelContext): void
  447.     {
  448.         /* If the form have a related entity, the upload folder is bound to it */
  449.         $folder $form->getRelatedEntity() ?: 'moorl_fb';
  450.         /* Upload files to media system */
  451.         foreach ($form->getDataStruct()->getFlattenElements() as $flattenElement) {
  452.             if ($flattenElement->getValue() instanceof UploadedFile) {
  453.                 $mediaId $this->storefrontMediaUploader->upload(
  454.                     $flattenElement->getValue(),
  455.                     $folder,
  456.                     'moorl_fb',
  457.                     $salesChannelContext->getContext()
  458.                 );
  459.                 $flattenElement->setPlainValue($flattenElement->getValue()->getClientOriginalName());
  460.                 $flattenElement->setValue($flattenElement->getValue()->getClientOriginalName());
  461.                 $flattenElement->setMediaId($mediaId);
  462.                 $flattenElement->setMedia($this->getMedia($mediaId$salesChannelContext->getContext()));
  463.             }
  464.             /* Store media id for attachments */
  465.             if ($flattenElement->getMediaId()) {
  466.                 $form->getDataStruct()->addAttachmentMediaId($flattenElement->getMediaId());
  467.             }
  468.         }
  469.     }
  470.     public function enrichPrices(
  471.         FormEntity $form,
  472.         SalesChannelContext $salesChannelContext,
  473.         CalculatedPrice $price
  474.     ): void
  475.     {
  476.         $this->elementPriceCalculator->calculate($form->getElements(), $salesChannelContext$price);
  477.     }
  478.     public function enrichFromEntity(
  479.         FormEntity $form,
  480.         SalesChannelContext $salesChannelContext,
  481.         ?Entity $entity null
  482.     ): void
  483.     {
  484.         if (!$entity) {
  485.             return;
  486.         }
  487.         foreach ($form->getElements() as $element) {
  488.             if ($element->getEntityMapping()) {
  489.                 $mapping explode('.'$element->getEntityMapping());
  490.                 if ($mapping[0] === 'customFields') {
  491.                     if (property_exists($entity'customFields')) {
  492.                         if (isset($entity->getCustomFields()[$mapping[1]])) {
  493.                             $element->setValue($entity->getCustomFields()[$mapping[1]]);
  494.                         }
  495.                     }
  496.                 } else {
  497.                     if (isset($entity->getTranslated()[$mapping[0]])) {
  498.                         $element->setValue($entity->getTranslated()[$mapping[0]]);
  499.                     } else {
  500.                         $element->setValue($entity->get($mapping[0]));
  501.                     }
  502.                 }
  503.                 if ($element->getValue() && $element->getType() === ElementTypeUpload::NAME && Uuid::isValid($element->getValue())) {
  504.                     $media $this->getMedia($element->getValue(), $salesChannelContext->getContext());
  505.                     $element->setMedia($media);
  506.                     $element->setMediaId($element->getValue());
  507.                 }
  508.             }
  509.         }
  510.     }
  511.     /**
  512.      * @return array
  513.      */
  514.     public function getMessages(): array
  515.     {
  516.         return $this->messages;
  517.     }
  518.     public function unsetMessages(): void
  519.     {
  520.         $this->messages = [];
  521.     }
  522.     public function handleElementException(\Exception $exceptionElementDataStruct $element): void
  523.     {
  524.         /* Create translatable error message */
  525.         $data array_merge(
  526.             $element->getConfig(),
  527.             [
  528.                 'value' => $element->getValue(),
  529.                 'path' => $element->getPath(),
  530.                 'name' => $element->getName()
  531.             ]
  532.         );
  533.         $this->addMessage(
  534.             $exception->getMessage(),
  535.             $exception->getCode(),
  536.             $data
  537.         );
  538.     }
  539.     public function hasErrorMessage(): bool
  540.     {
  541.         foreach ($this->messages as $message) {
  542.             if ($message['type'] === MoorlForms::DANGER) {
  543.                 return true;
  544.             }
  545.         }
  546.         return false;
  547.     }
  548.     public function addMessage(
  549.         string $message,
  550.         int $code MoorlForms::MSG_CODE,
  551.         array $data = [],
  552.         string $type MoorlForms::DANGER
  553.     ): void
  554.     {
  555.         $this->messages[] = [
  556.             'type' => $type,
  557.             'code' => $code,
  558.             'message' => $message,
  559.             'data' => $this->getTransPlaceholder($data)
  560.         ];
  561.     }
  562.     /**
  563.      * @return FormActionInterface[]
  564.      */
  565.     public function getFormActions()
  566.     {
  567.         return $this->formActions;
  568.     }
  569.     /**
  570.      * @return FormTypeInterface[]
  571.      */
  572.     public function getFormTypes()
  573.     {
  574.         return $this->formTypes;
  575.     }
  576.     /**
  577.      * @return ElementTypeInterface[]
  578.      */
  579.     public function getElementTypes()
  580.     {
  581.         return $this->elementTypes;
  582.     }
  583.     /**
  584.      * @return ElementSelectionInterface[]
  585.      */
  586.     public function getElementSelections()
  587.     {
  588.         return $this->elementSelections;
  589.     }
  590.     private function getTransPlaceholder(array $datastring $wrap "%"): array
  591.     {
  592.         $wrapped = [];
  593.         foreach ($data as $key => $value) {
  594.             /* Not for multidimensional purposes */
  595.             if (is_array($value)) {
  596.                 continue;
  597.             }
  598.             $wrapped[$wrap.$key.$wrap] = $value;
  599.         }
  600.         return $wrapped;
  601.     }
  602.     private function getTree(?string $parentIdElementCollection $elements): Tree
  603.     {
  604.         $tree $this->buildTree($parentId$elements->getElements());
  605.         return new Tree($tree);
  606.     }
  607.     private function buildTree(?string $parentId, array $elements): array
  608.     {
  609.         $children = new ElementCollection();
  610.         /** @var ?ElementEntity $parent */
  611.         $parent null;
  612.         foreach ($elements as $key => $element) {
  613.             if ($element->getId() === $parentId) {
  614.                 $parent $element;
  615.             }
  616.             if ($element->getParentId() !== $parentId) {
  617.                 continue;
  618.             }
  619.             /* https://account.shopware.com/producer/support/228495 */
  620.             //unset($elements[$key]);
  621.             $children->add($element);
  622.         }
  623.         $children->sortByPosition();
  624.         /* https://account.shopware.com/producer/support/228495 */
  625.         if ($parent && !empty($parent->getConfig()['childrenSort'])) {
  626.             $children->sortBy($parent->getConfig()['childrenSort']);
  627.         }
  628.         $items = [];
  629.         foreach ($children as $child) {
  630.             if (!$child->getActive()) {
  631.                 continue;
  632.             }
  633.             $item = clone $this->treeItem;
  634.             $item->setElement($child);
  635.             $item->setChildren(
  636.                 $this->buildTree($child->getId(), $elements)
  637.             );
  638.             if ($child->has('inheritConfig')) {
  639.                 foreach ($item->getChildren() as $c) {
  640.                     $c->getElement()->setConfig(array_replace_recursive(
  641.                         $child->getConfig() ?: [],
  642.                         $c->getElement()->getConfig() ?: []
  643.                     ));
  644.                 }
  645.             }
  646.             $items[$child->getId()] = $item;
  647.         }
  648.         return $items;
  649.     }
  650.     public function createFormCriteria(SalesChannelContext $salesChannelContext): Criteria
  651.     {
  652.         $criteria = new Criteria();
  653.         $criteria->addFilter(new FormAvailableFilter($salesChannelContext));
  654.         $criteria->addAssociation('elements');
  655.         $criteria->getAssociation('elements')->addFilter(new ElementAvailableFilter($salesChannelContext));
  656.         return $criteria;
  657.     }
  658.     /**
  659.      * @param array|string $productIds
  660.      * @param SalesChannelContext $salesChannelContext
  661.      * @return array|null
  662.      * @throws \Shopware\Core\Framework\DataAbstractionLayer\Exception\EntityRepositoryNotFoundException
  663.      */
  664.     public function getProductStreamIds($productIdsSalesChannelContext $salesChannelContext): ?array
  665.     {
  666.         $criteria = new Criteria(is_array($productIds) ? $productIds : [$productIds]);
  667.         $criteria->setLimit(is_array($productIds) ? count($productIds) : 1);
  668.         $criteria->addFilter(new ProductAvailableFilter($salesChannelContext->getSalesChannelId()));
  669.         $productRepository $this->definitionInstanceRegistry->getRepository(ProductDefinition::ENTITY_NAME);
  670.         /** @var ProductEntity $product */
  671.         $products $productRepository->search($criteria$salesChannelContext->getContext())->getEntities();
  672.         if (!$products) {
  673.             return null;
  674.         }
  675.         $productStreamIds = [];
  676.         foreach ($products as $product) {
  677.             if ($product->getStreamIds()) {
  678.                 $productStreamIds array_merge($productStreamIds$product->getStreamIds());
  679.             }
  680.         }
  681.         return $productStreamIds;
  682.     }
  683.     public function getMedia(string $mediaIdContext $context): ?MediaEntity
  684.     {
  685.         $criteria = new Criteria([$mediaId]);
  686.         $criteria->setLimit(1);
  687.         $mediaRepository $this->definitionInstanceRegistry->getRepository(MediaDefinition::ENTITY_NAME);
  688.         return $mediaRepository->search($criteria$context)->first();
  689.     }
  690.     public function upsertEntity(
  691.         FormCollection $forms,
  692.         SalesChannelContext $salesChannelContext,
  693.         ?Entity $entity null,
  694.         ?string $relatedEntity null,
  695.         array $dataToMerge = []
  696.     ): void
  697.     {
  698.         if (!$form $forms->first()) {
  699.             return;
  700.         }
  701.         /* If an entity is manually defined - e.g. history table, then go on */
  702.         if (!$relatedEntity) {
  703.             if (!$relatedEntity $form->getRelatedEntity()) {
  704.                 return;
  705.             }
  706.         }
  707.         $data = [];
  708.         if ($entity) {
  709.             $data['id'] = $entity->getId();
  710.         } else {
  711.             $data['id'] = Uuid::randomHex();
  712.         }
  713.         $html       '<table class="table">';
  714.         $plain      '';
  715.         $mediaIds   = [];
  716.         foreach ($forms as $form) {
  717.             $this->handleUploadedFiles($form$salesChannelContext);
  718.             /* Values are technical here */
  719.             foreach ($form->getDataStruct()->getFlattenElements() as $flattenElement) {
  720.                 $element $form->getElements()->get($flattenElement->getElementId());
  721.                 if (!$element || !$element->getEntityMapping()) {
  722.                     continue;
  723.                 }
  724.                 if ($flattenElement->getMediaId()) {
  725.                     $data[$element->getEntityMapping()] = $flattenElement->getMediaId();
  726.                 } else {
  727.                     if ($flattenElement->getValue() === MoorlForms::CHECKED_INDICATOR) {
  728.                         $data[$element->getEntityMapping()] = true;
  729.                     } else {
  730.                         $data[$element->getEntityMapping()] = $flattenElement->getValue();
  731.                     }
  732.                 }
  733.             }
  734.             /* Values from here are readable and translated */
  735.             $html       .= $form->getDataStruct()->getPrettyInline();
  736.             $plain      .= $form->getDataStruct()->getPrettyInline('plain');
  737.             $mediaIds   array_merge($mediaIds$form->getDataStruct()->getAttachmentMediaIds());
  738.         }
  739.         $html .= '</table>';
  740.         $data["customFields." MoorlForms::CUSTOM_FIELD_HTML_KEY] = $html;
  741.         $data["customFields." MoorlForms::CUSTOM_FIELD_PLAIN_KEY] = $plain;
  742.         $data["customFields." MoorlForms::CUSTOM_FIELD_MEDIA_IDS_KEY] = $mediaIds;
  743.         PluginHelpers::getNestedVar($data);
  744.         $data array_merge($data$dataToMerge);
  745.         $relatedEntityRepository $this->definitionInstanceRegistry->getRepository($relatedEntity);
  746.         try {
  747.             $relatedEntityRepository->upsert([$data], $salesChannelContext->getContext());
  748.         } catch (\Exception $exception) {
  749.             $this->logger->critical(sprintf("Error while writing data to %s"$relatedEntity), $data);
  750.             return;
  751.         }
  752.         if (!$entity) {
  753.            return;
  754.         }
  755.         /* Enrich data for immediately use */
  756.         foreach ($data as $key => $value) {
  757.             if ($key !== 'id' && !str_contains($key".")) {
  758.                 $entity->__set($key$value);
  759.             }
  760.         }
  761.         if (method_exists($entity'getCustomFields')) {
  762.             $customFields array_merge($entity->getCustomFields() ?: [], $data['customFields']);
  763.             $entity->setCustomFields($customFields);
  764.         }
  765.     }
  766.     public function enrichAttachmentsFromEntity(MediaCollection $attachmentsEntity $entityContext $context): void
  767.     {
  768.         if (!method_exists($entity'getCustomFields')) {
  769.             return;
  770.         }
  771.         $customFields $entity->getCustomFields();
  772.         if (!$customFields || empty($customFields[MoorlForms::CUSTOM_FIELD_MEDIA_IDS_KEY])) {
  773.             return;
  774.         }
  775.         foreach ($customFields[MoorlForms::CUSTOM_FIELD_MEDIA_IDS_KEY] as $mediaId) {
  776.             $attachments->add($this->getMedia($mediaId$context));
  777.         }
  778.     }
  779. }