<?php
declare(strict_types=1);
namespace CioPodProducts\Subscriber;
use CioBudget\Definition\Budget\BudgetEntity;
use CioBudget\Service\BudgetLoaderService;
use CioBudget\Service\SessionService;
use CioFormBuilder\Definition\CioForm\CioFormEntity;
use CioFormBuilder\Definition\CioFormField\CioFormFieldEntity;
use CioFormBuilder\Model\CioFormBuilder;
use CioFormBuilder\Model\Field\AbstractField;
use CioFormBuilder\Model\Field\FileField;
use CioFormBuilder\Model\Field\SelectField;
use CioFormBuilder\Model\Field\TextField;
use CioPodProducts\CioPodProducts;
use CioPodProducts\Service\PodTypeConfigService;
use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Content\Media\MediaService;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Storefront\Event\StorefrontRenderEvent;
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPage;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class PodCheckoutFormSubscriber implements EventSubscriberInterface
{
private RequestStack $requestStack;
private EntityRepository $orderRepository;
private EntityRepository $cioFormRepository;
private PodTypeConfigService $podTypeConfigService;
private MediaService $mediaService;
private BudgetLoaderService $budgetLoaderService;
private SessionService $sessionService;
public function __construct(
RequestStack $requestStack,
EntityRepository $orderRepository,
EntityRepository $cioFormRepository,
PodTypeConfigService $podTypeConfigService,
MediaService $mediaService,
BudgetLoaderService $budgetLoaderService,
SessionService $sessionService
) {
$this->requestStack = $requestStack;
$this->orderRepository = $orderRepository;
$this->cioFormRepository = $cioFormRepository;
$this->podTypeConfigService = $podTypeConfigService;
$this->mediaService = $mediaService;
$this->budgetLoaderService = $budgetLoaderService;
$this->sessionService = $sessionService;
}
/**
* @return array<string, string>
*/
public static function getSubscribedEvents(): array
{
return [
StorefrontRenderEvent::class => 'onStorefrontRender',
CheckoutOrderPlacedEvent::class => 'onCheckoutOrderPlacedEvent',
];
}
public function onStorefrontRender(StorefrontRenderEvent $event): void
{
$parameters = $event->getParameters();
if (!\is_array($parameters) || !\array_key_exists('page', $parameters)) {
return;
}
$page = $parameters['page'];
if (!$page instanceof CheckoutConfirmPage) {
return;
}
$cart = $page->getCart();
$lineItems = $cart->getLineItems();
$typeInfo = $this->resolveSinglePodTypeForCart($lineItems, $event->getSalesChannelContext()->getSalesChannelId());
if ($typeInfo === null) {
return;
}
$podCheckoutFormId = $typeInfo['config']['podCheckoutFormId'];
if ($podCheckoutFormId === null) {
return;
}
$formEntity = $this->getFormById($podCheckoutFormId, $event->getContext());
if (!$formEntity instanceof CioFormEntity) {
return;
}
$formEntity->getFields()->forEach(function (CioFormFieldEntity $field) use ($event) {
if ($field->getType() === CioFormBuilder::FIELD_TYPE_BUDGET_SELECT) {
$budgets = $this->budgetLoaderService->getActiveBudgetsByCustomer($event->getSalesChannelContext()->getCustomer(), Context::createDefaultContext());
$currentBudgetId = $this->sessionService->getCurrentBudgetId();
if ($budgets instanceof EntitySearchResult && $budgets->getTotal() > 0) {
$budgets = $budgets->getElements();
if (is_string($currentBudgetId) && UUID::isValid($currentBudgetId)) {
// sort so that current budget is first
usort($budgets, function (BudgetEntity $a, BudgetEntity $b) use ($currentBudgetId) {
if ($a->getId() === $currentBudgetId) {
return -1;
}
if ($b->getId() === $currentBudgetId) {
return 1;
}
return 0;
});
}
$selectionValues = array_map(function (BudgetEntity $budget) {
return $budget->getName() . ' (' . $budget->getStore()->getExtid() . ')';
}, $budgets);
$field->setSelectionValues(implode(';', $selectionValues));
} else {
$field->setSelectionValues('');
}
}
});
$formBuilder = new CioFormBuilder($formEntity);
$formBuilder->formName = 'confirmOrderForm';
$event->setParameter('podCheckoutForm', $formBuilder);
$event->setParameter('podCheckoutFormId', $podCheckoutFormId);
}
public function onCheckoutOrderPlacedEvent(CheckoutOrderPlacedEvent $event): void
{
$request = $this->requestStack->getCurrentRequest();
if (!$request instanceof Request) {
return;
}
$postedFormId = $request->request->get('podCheckoutFormId');
if (!\is_string($postedFormId) || $postedFormId === '') {
return;
}
$order = $event->getOrder();
$typeInfo = $this->resolveSinglePodTypeForOrder($order, $event->getSalesChannelId());
if ($typeInfo === null) {
return;
}
if ($typeInfo['config']['podCheckoutFormId'] !== $postedFormId) {
return;
}
$formEntity = $this->getFormById($postedFormId, $event->getContext());
if (!$formEntity instanceof CioFormEntity) {
return;
}
$formBuilder = new CioFormBuilder($formEntity);
$formBuilder->formName = 'confirmOrderForm';
if ($formBuilder->isValid($request) === false) {
return;
}
[$formData, $tableFormData, $rowCount, $tablePrefix, $dynamicMode] = $this->collectFormData($formBuilder, $formEntity, $request, $event->getContext());
$customFields = $order->getCustomFields() ?? [];
$customFields['podCheckoutFormData'] = $formData;
$customFields['podCheckoutTableFormData'] = $tableFormData;
$customFields['podCheckoutTableFormPrefix'] = $tablePrefix;
$customFields['podCheckoutDynamicRowMode'] = $dynamicMode;
if ($dynamicMode) {
$customFields['podCheckoutDynamicRowCount'] = $rowCount;
}
$order->setCustomFields($customFields);
$this->orderRepository->update([
[
'id' => $order->getId(),
'customFields' => $customFields,
],
], $event->getContext());
}
private function resolveSinglePodTypeForCart(\Shopware\Core\Checkout\Cart\LineItem\LineItemCollection $lineItems, ?string $salesChannelId): ?array
{
$hasPod = false;
$podTypes = [];
foreach ($lineItems as $lineItem) {
if (!$lineItem instanceof LineItem) {
continue;
}
$payload = $lineItem->getPayload();
if (!\is_array($payload) || !isset($payload['customFields']) || !\is_array($payload['customFields'])) {
continue;
}
$customFields = $payload['customFields'];
if (!empty($customFields['custom_pod_products_isPodProduct'])) {
$hasPod = true;
}
if (!empty($customFields['custom_pod_products_podProductType']) && \is_string($customFields['custom_pod_products_podProductType'])) {
$podTypes[] = $customFields['custom_pod_products_podProductType'];
}
}
if (!$hasPod) {
return null;
}
$podTypes = \array_values(\array_unique($podTypes));
if (\count($podTypes) !== 1) {
return null;
}
$typeConfig = $this->podTypeConfigService->getTypeConfig($podTypes[0], $salesChannelId);
if (empty($typeConfig['podCheckoutFormId'])) {
return null;
}
if (!empty($typeConfig['podUniqueItem']) && $lineItems->count() > 1) {
return null;
}
return [
'typeKey' => $podTypes[0],
'config' => $typeConfig,
];
}
private function resolveSinglePodTypeForOrder(OrderEntity $order, ?string $salesChannelId): ?array
{
$lineItems = $order->getLineItems();
if ($lineItems === null) {
return null;
}
$hasPod = false;
$podTypes = [];
foreach ($lineItems as $lineItem) {
$payload = $lineItem->getPayload();
if (!\is_array($payload) || !isset($payload['customFields']) || !\is_array($payload['customFields'])) {
continue;
}
$customFields = $payload['customFields'];
if (!empty($customFields[CioPodProducts::POD_PRODUCTS_CUSTOM_FIELD_IS_POD])) {
$hasPod = true;
}
if (!empty($customFields['custom_pod_products_podProductType']) && \is_string($customFields['custom_pod_products_podProductType'])) {
$podTypes[] = $customFields['custom_pod_products_podProductType'];
}
}
if (!$hasPod) {
return null;
}
$podTypes = \array_values(\array_unique($podTypes));
if (\count($podTypes) !== 1) {
return null;
}
$typeConfig = $this->podTypeConfigService->getTypeConfig($podTypes[0], $salesChannelId);
if (empty($typeConfig['podCheckoutFormId'])) {
return null;
}
if (!empty($typeConfig['podUniqueItem']) && $lineItems->count() > 1) {
return null;
}
return [
'typeKey' => $podTypes[0],
'config' => $typeConfig,
];
}
private function getFormById(string $id, Context $context): ?CioFormEntity
{
$criteria = (new Criteria())
->addFilter(new EqualsFilter('id', $id))
->addAssociation('fields');
$formEntity = $this->cioFormRepository->search($criteria, $context)->first();
return $formEntity instanceof CioFormEntity ? $formEntity : null;
}
/**
* @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}
*/
private function collectFormData(CioFormBuilder $formBuilder, CioFormEntity $formEntity, Request $request, Context $context): array
{
$formData = [];
$tableFormData = [];
/** @var AbstractField $field */
foreach ($formBuilder->getFormFields() as $field) {
if ($field instanceof FileField) {
if ($request->files->has($field->getEntity()->getId()) && $request->files->get($field->getEntity()->getId()) instanceof UploadedFile) {
/** @var UploadedFile $uploadedFile */
$uploadedFile = $request->files->get($field->getEntity()->getId());
$mediaId = $this->mediaService->saveFile(
\file_get_contents($uploadedFile->getPathname()),
$uploadedFile->getClientOriginalExtension(),
$uploadedFile->getClientMimeType(),
$this->cleanFilename($uploadedFile->getClientOriginalName()) . '_' . Uuid::randomHex(),
$context,
CioPodProducts::MEDIA_UPLOAD_FOLDER_ENTITY,
null,
false
);
$formData[] = [
'id' => $field->getEntity()->getId(),
'technicalName' => $field->getEntity()->getTechnicalName(),
'label' => $field->getEntity()->getLabel(),
'type' => $field->getEntity()->getType(),
'value' => $mediaId,
];
} elseif ($request->request->has($field->getEntity()->getId() . '_media_id')) {
$formData[] = [
'id' => $field->getEntity()->getId(),
'technicalName' => $field->getEntity()->getTechnicalName(),
'label' => $field->getEntity()->getLabel(),
'type' => $field->getEntity()->getType(),
'value' => $request->request->get($field->getEntity()->getId() . '_media_id'),
];
}
} else {
$formData[] = [
'id' => $field->getEntity()->getId(),
'technicalName' => $field->getEntity()->getTechnicalName(),
'label' => $field->getEntity()->getLabel(),
'type' => $field->getEntity()->getType(),
'value' => $field->getData($request),
];
}
}
$rowCount = 0;
$dynamicMode = $formBuilder->hasDynamicRowMode();
if ($dynamicMode) {
$rowCount = $this->computeDynamicRowCount($formBuilder, $request);
$tableFormData = $this->buildTableFormDataDynamic($formEntity, $request, $rowCount);
} else {
if (\is_array($formBuilder->getTableRepresentationFormFields())
&& \count($formBuilder->getTableRepresentationFormFields()) > 0
&& \is_array($formBuilder->getTableRepresentationFormFields()[0])) {
foreach ($formBuilder->getTableRepresentationFormFields() as $row) {
$rowData = [];
/** @var AbstractField $field */
foreach ($row as $field) {
if ($field instanceof TextField || $field instanceof SelectField) {
$rowData[] = [
'id' => $field->getEntity()->getId(),
'technicalName' => $field->getEntity()->getTechnicalName(),
'label' => $field->getEntity()->getLabel(),
'type' => $field->getEntity()->getType(),
'value' => $field->getData($request),
];
}
}
$tableFormData[] = $rowData;
}
}
}
$tablePrefix = $formBuilder->getEntity()->getTableRowsPrefix();
return [$formData, $tableFormData, $rowCount, $tablePrefix, $dynamicMode];
}
private function cleanFilename(string $filename): string
{
$filenameWithoutExtension = \substr($filename, 0, \strrpos($filename, '.'));
$cleanFilename = \preg_replace('/[^A-Za-z0-9\-_]/', '-', $filenameWithoutExtension);
return \strtolower(\trim((string) $cleanFilename, '-'));
}
private function computeDynamicRowCount(CioFormBuilder $form, Request $request): int
{
$min = $form->getEntity()->getMinTableRowsNumber() ?? 0;
$max = $form->getEntity()->getMaxTableRowsNumber() ?? 0;
$count = 0;
if ($request->request->has('podCheckoutDynamicRowCount')) {
$val = (int) $request->request->get('podCheckoutDynamicRowCount');
if ($val > 0) {
$count = $val;
}
}
if ($count === 0) {
$maxIndex = -1;
$tableFieldIds = [];
foreach ($form->getEntity()->getFields() as $fieldEntity) {
if ($fieldEntity instanceof \CioFormBuilder\Definition\CioFormField\CioFormFieldEntity && $fieldEntity->getShowAsTable() === true) {
$type = $fieldEntity->getType();
if (\in_array($type, [CioFormBuilder::FIELD_TYPE_TEXT, CioFormBuilder::FIELD_TYPE_SELECT], true)) {
$tableFieldIds[] = $fieldEntity->getId();
}
}
}
foreach ($request->request->keys() as $name) {
foreach ($tableFieldIds as $id) {
if (\preg_match('/^' . \preg_quote($id, '/') . '_(\d+)$/', $name, $m)) {
$idx = (int) $m[1];
if ($idx > $maxIndex) {
$maxIndex = $idx;
}
}
}
}
if ($maxIndex >= 0) {
$count = $maxIndex + 1;
}
}
if ($min > 0 && $max >= $min) {
if ($count === 0) {
$count = $min;
}
if ($count < $min) {
$count = $min;
}
if ($count > $max) {
$count = $max;
}
}
return \max(1, (int) $count);
}
/**
* @return array<int, array<int, array{id:string,technicalName:string,label:string,type:string,value:mixed}>>
*/
private function buildTableFormDataDynamic(CioFormEntity $formEntity, Request $request, int $rowCount): array
{
$result = [];
$tableFields = [];
foreach ($formEntity->getFields() as $fieldEntity) {
if ($fieldEntity instanceof \CioFormBuilder\Definition\CioFormField\CioFormFieldEntity && $fieldEntity->getShowAsTable() === true) {
$type = $fieldEntity->getType();
if (\in_array($type, [CioFormBuilder::FIELD_TYPE_TEXT, CioFormBuilder::FIELD_TYPE_SELECT], true)) {
$tableFields[] = $fieldEntity;
}
}
}
for ($i = 0; $i < $rowCount; $i++) {
$rowData = [];
foreach ($tableFields as $fieldEntity) {
$name = $fieldEntity->getId() . '_' . $i;
$value = '';
if ($request->request->has($name)) {
$val = $request->request->get($name);
if (\is_string($val)) {
$value = $val;
}
}
$rowData[] = [
'id' => $fieldEntity->getId(),
'technicalName' => $fieldEntity->getTechnicalName(),
'label' => $fieldEntity->getLabel(),
'type' => $fieldEntity->getType(),
'value' => $value,
];
}
$result[] = $rowData;
}
return $result;
}
}