custom/plugins/CioPodProducts/src/Subscriber/PodCheckoutFormSubscriber.php line 75

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace CioPodProducts\Subscriber;
  4. use CioBudget\Definition\Budget\BudgetEntity;
  5. use CioBudget\Service\BudgetLoaderService;
  6. use CioBudget\Service\SessionService;
  7. use CioFormBuilder\Definition\CioForm\CioFormEntity;
  8. use CioFormBuilder\Definition\CioFormField\CioFormFieldEntity;
  9. use CioFormBuilder\Model\CioFormBuilder;
  10. use CioFormBuilder\Model\Field\AbstractField;
  11. use CioFormBuilder\Model\Field\FileField;
  12. use CioFormBuilder\Model\Field\SelectField;
  13. use CioFormBuilder\Model\Field\TextField;
  14. use CioPodProducts\CioPodProducts;
  15. use CioPodProducts\Service\PodTypeConfigService;
  16. use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
  17. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  18. use Shopware\Core\Checkout\Order\OrderEntity;
  19. use Shopware\Core\Content\Media\MediaService;
  20. use Shopware\Core\Framework\Context;
  21. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  22. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  23. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  24. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  25. use Shopware\Core\Framework\Uuid\Uuid;
  26. use Shopware\Storefront\Event\StorefrontRenderEvent;
  27. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPage;
  28. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  29. use Symfony\Component\HttpFoundation\File\UploadedFile;
  30. use Symfony\Component\HttpFoundation\Request;
  31. use Symfony\Component\HttpFoundation\RequestStack;
  32. class PodCheckoutFormSubscriber implements EventSubscriberInterface
  33. {
  34.     private RequestStack $requestStack;
  35.     private EntityRepository $orderRepository;
  36.     private EntityRepository $cioFormRepository;
  37.     private PodTypeConfigService $podTypeConfigService;
  38.     private MediaService $mediaService;
  39.     private BudgetLoaderService $budgetLoaderService;
  40.     private SessionService $sessionService;
  41.     public function __construct(
  42.         RequestStack $requestStack,
  43.         EntityRepository $orderRepository,
  44.         EntityRepository $cioFormRepository,
  45.         PodTypeConfigService $podTypeConfigService,
  46.         MediaService $mediaService,
  47.         BudgetLoaderService $budgetLoaderService,
  48.         SessionService $sessionService
  49.     ) {
  50.         $this->requestStack $requestStack;
  51.         $this->orderRepository $orderRepository;
  52.         $this->cioFormRepository $cioFormRepository;
  53.         $this->podTypeConfigService $podTypeConfigService;
  54.         $this->mediaService $mediaService;
  55.         $this->budgetLoaderService $budgetLoaderService;
  56.         $this->sessionService $sessionService;
  57.     }
  58.     /**
  59.      * @return array<string, string>
  60.      */
  61.     public static function getSubscribedEvents(): array
  62.     {
  63.         return [
  64.             StorefrontRenderEvent::class => 'onStorefrontRender',
  65.             CheckoutOrderPlacedEvent::class => 'onCheckoutOrderPlacedEvent',
  66.         ];
  67.     }
  68.     public function onStorefrontRender(StorefrontRenderEvent $event): void
  69.     {
  70.         $parameters $event->getParameters();
  71.         if (!\is_array($parameters) || !\array_key_exists('page'$parameters)) {
  72.             return;
  73.         }
  74.         $page $parameters['page'];
  75.         if (!$page instanceof CheckoutConfirmPage) {
  76.             return;
  77.         }
  78.         $cart $page->getCart();
  79.         $lineItems $cart->getLineItems();
  80.         $typeInfo $this->resolveSinglePodTypeForCart($lineItems$event->getSalesChannelContext()->getSalesChannelId());
  81.         if ($typeInfo === null) {
  82.             return;
  83.         }
  84.         $podCheckoutFormId $typeInfo['config']['podCheckoutFormId'];
  85.         if ($podCheckoutFormId === null) {
  86.             return;
  87.         }
  88.         $formEntity $this->getFormById($podCheckoutFormId$event->getContext());
  89.         if (!$formEntity instanceof CioFormEntity) {
  90.             return;
  91.         }
  92.         $formEntity->getFields()->forEach(function (CioFormFieldEntity $field) use ($event) {
  93.             if ($field->getType() === CioFormBuilder::FIELD_TYPE_BUDGET_SELECT) {
  94.                 $budgets $this->budgetLoaderService->getActiveBudgetsByCustomer($event->getSalesChannelContext()->getCustomer(), Context::createDefaultContext());
  95.                 $currentBudgetId $this->sessionService->getCurrentBudgetId();
  96.                 if ($budgets instanceof EntitySearchResult && $budgets->getTotal() > 0) {
  97.                     $budgets $budgets->getElements();
  98.                     if (is_string($currentBudgetId) && UUID::isValid($currentBudgetId)) {
  99.                         // sort so that current budget is first
  100.                         usort($budgets, function (BudgetEntity $aBudgetEntity $b) use ($currentBudgetId) {
  101.                             if ($a->getId() === $currentBudgetId) {
  102.                                 return -1;
  103.                             }
  104.                             if ($b->getId() === $currentBudgetId) {
  105.                                 return 1;
  106.                             }
  107.                             return 0;
  108.                         });
  109.                     }
  110.                     $selectionValues array_map(function (BudgetEntity $budget) {
  111.                         return $budget->getName() . ' (' $budget->getStore()->getExtid() . ')';
  112.                     }, $budgets);
  113.                     $field->setSelectionValues(implode(';'$selectionValues));
  114.                 } else {
  115.                     $field->setSelectionValues('');
  116.                 }
  117.             }
  118.         });
  119.         $formBuilder = new CioFormBuilder($formEntity);
  120.         $formBuilder->formName 'confirmOrderForm';
  121.         $event->setParameter('podCheckoutForm'$formBuilder);
  122.         $event->setParameter('podCheckoutFormId'$podCheckoutFormId);
  123.     }
  124.     public function onCheckoutOrderPlacedEvent(CheckoutOrderPlacedEvent $event): void
  125.     {
  126.         $request $this->requestStack->getCurrentRequest();
  127.         if (!$request instanceof Request) {
  128.             return;
  129.         }
  130.         $postedFormId $request->request->get('podCheckoutFormId');
  131.         if (!\is_string($postedFormId) || $postedFormId === '') {
  132.             return;
  133.         }
  134.         $order $event->getOrder();
  135.         $typeInfo $this->resolveSinglePodTypeForOrder($order$event->getSalesChannelId());
  136.         if ($typeInfo === null) {
  137.             return;
  138.         }
  139.         if ($typeInfo['config']['podCheckoutFormId'] !== $postedFormId) {
  140.             return;
  141.         }
  142.         $formEntity $this->getFormById($postedFormId$event->getContext());
  143.         if (!$formEntity instanceof CioFormEntity) {
  144.             return;
  145.         }
  146.         $formBuilder = new CioFormBuilder($formEntity);
  147.         $formBuilder->formName 'confirmOrderForm';
  148.         if ($formBuilder->isValid($request) === false) {
  149.             return;
  150.         }
  151.         [$formData$tableFormData$rowCount$tablePrefix$dynamicMode] = $this->collectFormData($formBuilder$formEntity$request$event->getContext());
  152.         $customFields $order->getCustomFields() ?? [];
  153.         $customFields['podCheckoutFormData'] = $formData;
  154.         $customFields['podCheckoutTableFormData'] = $tableFormData;
  155.         $customFields['podCheckoutTableFormPrefix'] = $tablePrefix;
  156.         $customFields['podCheckoutDynamicRowMode'] = $dynamicMode;
  157.         if ($dynamicMode) {
  158.             $customFields['podCheckoutDynamicRowCount'] = $rowCount;
  159.         }
  160.         $order->setCustomFields($customFields);
  161.         $this->orderRepository->update([
  162.             [
  163.                 'id' => $order->getId(),
  164.                 'customFields' => $customFields,
  165.             ],
  166.         ], $event->getContext());
  167.     }
  168.     private function resolveSinglePodTypeForCart(\Shopware\Core\Checkout\Cart\LineItem\LineItemCollection $lineItems, ?string $salesChannelId): ?array
  169.     {
  170.         $hasPod false;
  171.         $podTypes = [];
  172.         foreach ($lineItems as $lineItem) {
  173.             if (!$lineItem instanceof LineItem) {
  174.                 continue;
  175.             }
  176.             $payload $lineItem->getPayload();
  177.             if (!\is_array($payload) || !isset($payload['customFields']) || !\is_array($payload['customFields'])) {
  178.                 continue;
  179.             }
  180.             $customFields $payload['customFields'];
  181.             if (!empty($customFields['custom_pod_products_isPodProduct'])) {
  182.                 $hasPod true;
  183.             }
  184.             if (!empty($customFields['custom_pod_products_podProductType']) && \is_string($customFields['custom_pod_products_podProductType'])) {
  185.                 $podTypes[] = $customFields['custom_pod_products_podProductType'];
  186.             }
  187.         }
  188.         if (!$hasPod) {
  189.             return null;
  190.         }
  191.         $podTypes \array_values(\array_unique($podTypes));
  192.         if (\count($podTypes) !== 1) {
  193.             return null;
  194.         }
  195.         $typeConfig $this->podTypeConfigService->getTypeConfig($podTypes[0], $salesChannelId);
  196.         if (empty($typeConfig['podCheckoutFormId'])) {
  197.             return null;
  198.         }
  199.         if (!empty($typeConfig['podUniqueItem']) && $lineItems->count() > 1) {
  200.             return null;
  201.         }
  202.         return [
  203.             'typeKey' => $podTypes[0],
  204.             'config' => $typeConfig,
  205.         ];
  206.     }
  207.     private function resolveSinglePodTypeForOrder(OrderEntity $order, ?string $salesChannelId): ?array
  208.     {
  209.         $lineItems $order->getLineItems();
  210.         if ($lineItems === null) {
  211.             return null;
  212.         }
  213.         $hasPod false;
  214.         $podTypes = [];
  215.         foreach ($lineItems as $lineItem) {
  216.             $payload $lineItem->getPayload();
  217.             if (!\is_array($payload) || !isset($payload['customFields']) || !\is_array($payload['customFields'])) {
  218.                 continue;
  219.             }
  220.             $customFields $payload['customFields'];
  221.             if (!empty($customFields[CioPodProducts::POD_PRODUCTS_CUSTOM_FIELD_IS_POD])) {
  222.                 $hasPod true;
  223.             }
  224.             if (!empty($customFields['custom_pod_products_podProductType']) && \is_string($customFields['custom_pod_products_podProductType'])) {
  225.                 $podTypes[] = $customFields['custom_pod_products_podProductType'];
  226.             }
  227.         }
  228.         if (!$hasPod) {
  229.             return null;
  230.         }
  231.         $podTypes \array_values(\array_unique($podTypes));
  232.         if (\count($podTypes) !== 1) {
  233.             return null;
  234.         }
  235.         $typeConfig $this->podTypeConfigService->getTypeConfig($podTypes[0], $salesChannelId);
  236.         if (empty($typeConfig['podCheckoutFormId'])) {
  237.             return null;
  238.         }
  239.         if (!empty($typeConfig['podUniqueItem']) && $lineItems->count() > 1) {
  240.             return null;
  241.         }
  242.         return [
  243.             'typeKey' => $podTypes[0],
  244.             'config' => $typeConfig,
  245.         ];
  246.     }
  247.     private function getFormById(string $idContext $context): ?CioFormEntity
  248.     {
  249.         $criteria = (new Criteria())
  250.             ->addFilter(new EqualsFilter('id'$id))
  251.             ->addAssociation('fields');
  252.         $formEntity $this->cioFormRepository->search($criteria$context)->first();
  253.         return $formEntity instanceof CioFormEntity $formEntity null;
  254.     }
  255.     /**
  256.      * @return array{0: array<int, array{id:string,technicalName:string,label:string,type:string,value:mixed}>, 1: array<int, array<int, array{id:string,technicalName:string,label:string,type:string,value:mixed}>>, 2: int, 3: ?string, 4: bool}
  257.      */
  258.     private function collectFormData(CioFormBuilder $formBuilderCioFormEntity $formEntityRequest $requestContext $context): array
  259.     {
  260.         $formData = [];
  261.         $tableFormData = [];
  262.         /** @var AbstractField $field */
  263.         foreach ($formBuilder->getFormFields() as $field) {
  264.             if ($field instanceof FileField) {
  265.                 if ($request->files->has($field->getEntity()->getId()) && $request->files->get($field->getEntity()->getId()) instanceof UploadedFile) {
  266.                     /** @var UploadedFile $uploadedFile */
  267.                     $uploadedFile $request->files->get($field->getEntity()->getId());
  268.                     $mediaId $this->mediaService->saveFile(
  269.                         \file_get_contents($uploadedFile->getPathname()),
  270.                         $uploadedFile->getClientOriginalExtension(),
  271.                         $uploadedFile->getClientMimeType(),
  272.                         $this->cleanFilename($uploadedFile->getClientOriginalName()) . '_' Uuid::randomHex(),
  273.                         $context,
  274.                         CioPodProducts::MEDIA_UPLOAD_FOLDER_ENTITY,
  275.                         null,
  276.                         false
  277.                     );
  278.                     $formData[] = [
  279.                         'id' => $field->getEntity()->getId(),
  280.                         'technicalName' => $field->getEntity()->getTechnicalName(),
  281.                         'label' => $field->getEntity()->getLabel(),
  282.                         'type' => $field->getEntity()->getType(),
  283.                         'value' => $mediaId,
  284.                     ];
  285.                 } elseif ($request->request->has($field->getEntity()->getId() . '_media_id')) {
  286.                     $formData[] = [
  287.                         'id' => $field->getEntity()->getId(),
  288.                         'technicalName' => $field->getEntity()->getTechnicalName(),
  289.                         'label' => $field->getEntity()->getLabel(),
  290.                         'type' => $field->getEntity()->getType(),
  291.                         'value' => $request->request->get($field->getEntity()->getId() . '_media_id'),
  292.                     ];
  293.                 }
  294.             } else {
  295.                 $formData[] = [
  296.                     'id' => $field->getEntity()->getId(),
  297.                     'technicalName' => $field->getEntity()->getTechnicalName(),
  298.                     'label' => $field->getEntity()->getLabel(),
  299.                     'type' => $field->getEntity()->getType(),
  300.                     'value' => $field->getData($request),
  301.                 ];
  302.             }
  303.         }
  304.         $rowCount 0;
  305.         $dynamicMode $formBuilder->hasDynamicRowMode();
  306.         if ($dynamicMode) {
  307.             $rowCount $this->computeDynamicRowCount($formBuilder$request);
  308.             $tableFormData $this->buildTableFormDataDynamic($formEntity$request$rowCount);
  309.         } else {
  310.             if (\is_array($formBuilder->getTableRepresentationFormFields())
  311.                 && \count($formBuilder->getTableRepresentationFormFields()) > 0
  312.                 && \is_array($formBuilder->getTableRepresentationFormFields()[0])) {
  313.                 foreach ($formBuilder->getTableRepresentationFormFields() as $row) {
  314.                     $rowData = [];
  315.                     /** @var AbstractField $field */
  316.                     foreach ($row as $field) {
  317.                         if ($field instanceof TextField || $field instanceof SelectField) {
  318.                             $rowData[] = [
  319.                                 'id' => $field->getEntity()->getId(),
  320.                                 'technicalName' => $field->getEntity()->getTechnicalName(),
  321.                                 'label' => $field->getEntity()->getLabel(),
  322.                                 'type' => $field->getEntity()->getType(),
  323.                                 'value' => $field->getData($request),
  324.                             ];
  325.                         }
  326.                     }
  327.                     $tableFormData[] = $rowData;
  328.                 }
  329.             }
  330.         }
  331.         $tablePrefix $formBuilder->getEntity()->getTableRowsPrefix();
  332.         return [$formData$tableFormData$rowCount$tablePrefix$dynamicMode];
  333.     }
  334.     private function cleanFilename(string $filename): string
  335.     {
  336.         $filenameWithoutExtension \substr($filename0\strrpos($filename'.'));
  337.         $cleanFilename \preg_replace('/[^A-Za-z0-9\-_]/''-'$filenameWithoutExtension);
  338.         return \strtolower(\trim((string) $cleanFilename'-'));
  339.     }
  340.     private function computeDynamicRowCount(CioFormBuilder $formRequest $request): int
  341.     {
  342.         $min $form->getEntity()->getMinTableRowsNumber() ?? 0;
  343.         $max $form->getEntity()->getMaxTableRowsNumber() ?? 0;
  344.         $count 0;
  345.         if ($request->request->has('podCheckoutDynamicRowCount')) {
  346.             $val = (int) $request->request->get('podCheckoutDynamicRowCount');
  347.             if ($val 0) {
  348.                 $count $val;
  349.             }
  350.         }
  351.         if ($count === 0) {
  352.             $maxIndex = -1;
  353.             $tableFieldIds = [];
  354.             foreach ($form->getEntity()->getFields() as $fieldEntity) {
  355.                 if ($fieldEntity instanceof \CioFormBuilder\Definition\CioFormField\CioFormFieldEntity && $fieldEntity->getShowAsTable() === true) {
  356.                     $type $fieldEntity->getType();
  357.                     if (\in_array($type, [CioFormBuilder::FIELD_TYPE_TEXTCioFormBuilder::FIELD_TYPE_SELECT], true)) {
  358.                         $tableFieldIds[] = $fieldEntity->getId();
  359.                     }
  360.                 }
  361.             }
  362.             foreach ($request->request->keys() as $name) {
  363.                 foreach ($tableFieldIds as $id) {
  364.                     if (\preg_match('/^' \preg_quote($id'/') . '_(\d+)$/'$name$m)) {
  365.                         $idx = (int) $m[1];
  366.                         if ($idx $maxIndex) {
  367.                             $maxIndex $idx;
  368.                         }
  369.                     }
  370.                 }
  371.             }
  372.             if ($maxIndex >= 0) {
  373.                 $count $maxIndex 1;
  374.             }
  375.         }
  376.         if ($min && $max >= $min) {
  377.             if ($count === 0) {
  378.                 $count $min;
  379.             }
  380.             if ($count $min) {
  381.                 $count $min;
  382.             }
  383.             if ($count $max) {
  384.                 $count $max;
  385.             }
  386.         }
  387.         return \max(1, (int) $count);
  388.     }
  389.     /**
  390.      * @return array<int, array<int, array{id:string,technicalName:string,label:string,type:string,value:mixed}>>
  391.      */
  392.     private function buildTableFormDataDynamic(CioFormEntity $formEntityRequest $requestint $rowCount): array
  393.     {
  394.         $result = [];
  395.         $tableFields = [];
  396.         foreach ($formEntity->getFields() as $fieldEntity) {
  397.             if ($fieldEntity instanceof \CioFormBuilder\Definition\CioFormField\CioFormFieldEntity && $fieldEntity->getShowAsTable() === true) {
  398.                 $type $fieldEntity->getType();
  399.                 if (\in_array($type, [CioFormBuilder::FIELD_TYPE_TEXTCioFormBuilder::FIELD_TYPE_SELECT], true)) {
  400.                     $tableFields[] = $fieldEntity;
  401.                 }
  402.             }
  403.         }
  404.         for ($i 0$i $rowCount$i++) {
  405.             $rowData = [];
  406.             foreach ($tableFields as $fieldEntity) {
  407.                 $name $fieldEntity->getId() . '_' $i;
  408.                 $value '';
  409.                 if ($request->request->has($name)) {
  410.                     $val $request->request->get($name);
  411.                     if (\is_string($val)) {
  412.                         $value $val;
  413.                     }
  414.                 }
  415.                 $rowData[] = [
  416.                     'id' => $fieldEntity->getId(),
  417.                     'technicalName' => $fieldEntity->getTechnicalName(),
  418.                     'label' => $fieldEntity->getLabel(),
  419.                     'type' => $fieldEntity->getType(),
  420.                     'value' => $value,
  421.                 ];
  422.             }
  423.             $result[] = $rowData;
  424.         }
  425.         return $result;
  426.     }
  427. }