<?php
namespace CioPodProducts\Subscriber;
use CioPodProducts\CioPodProducts;
use CioPodProducts\Error\PodMixedCartError;
use CioPodProducts\Event\PodCustomerApprovalEvent;
use CioPodProducts\Event\PodFinalApprovalEvent;
use CioPodProducts\Event\PodOrderCreatedEvent;
use CioPodProducts\Event\PodPhotographerGalleryUploadedEvent;
use CioPodProducts\Event\PodSupplierPreviewUploadedEvent;
use CioPodProducts\Helper\PodOrdersAssociationsHelper;
use CioPodProducts\Service\PodTypeConfigService;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\Error\Error;
use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Event\BusinessEventCollectorEvent;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Storefront\Event\StorefrontRenderEvent;
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPage;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Framework\Event\BusinessEventCollector;
class BusinessEventCollectorSubscriber implements EventSubscriberInterface
{
protected BusinessEventCollector $businessEventCollector;
protected EventDispatcherInterface $eventDispatcher;
protected EntityRepository $entityRepository;
protected EntityRepository $orderRepository;
protected PodTypeConfigService $podTypeConfigService;
public function __construct(BusinessEventCollector $businessEventCollector, EventDispatcherInterface $eventDispatcher, EntityRepository $productRepository, EntityRepository $orderRepository, PodTypeConfigService $podTypeConfigService)
{
$this->businessEventCollector = $businessEventCollector;
$this->eventDispatcher = $eventDispatcher;
$this->entityRepository = $productRepository;
$this->orderRepository = $orderRepository;
$this->podTypeConfigService = $podTypeConfigService;
}
public static function getSubscribedEvents()
{
return [
BusinessEventCollectorEvent::NAME => 'onBusinessEventCollectorEvent',
// Run after PodCheckoutFormSubscriber on the same event, so order customFields are enriched first.
CheckoutOrderPlacedEvent::class => ['onCheckoutOrderPlacedEvent', -1000],
StorefrontRenderEvent::class => 'onStorefrontRenderEvent',
];
}
public function onBusinessEventCollectorEvent(BusinessEventCollectorEvent $event)
{
$collection = $event->getCollection();
// add event PodOrderCreatedEvent to the collection
$definition = $this->businessEventCollector->define(PodOrderCreatedEvent::class);
if (!$definition) {
return;
}
$collection->set($definition->getName(), $definition);
// add event PodPhotographerGalleryUploadedEvent to the collection
$definition = $this->businessEventCollector->define(PodPhotographerGalleryUploadedEvent::class);
if (!$definition) {
return;
}
$collection->set($definition->getName(), $definition);
// add event PodSupplierPreviewUploadedEvent to the collection
$definition = $this->businessEventCollector->define(PodSupplierPreviewUploadedEvent::class);
if (!$definition) {
return;
}
$collection->set($definition->getName(), $definition);
// add event PodCustomerApprovalEvent to the collection
$definition = $this->businessEventCollector->define(PodCustomerApprovalEvent::class);
if (!$definition) {
return;
}
$collection->set($definition->getName(), $definition);
// add event PodFinalApprovalEvent to the collection
$definition = $this->businessEventCollector->define(PodFinalApprovalEvent::class);
if (!$definition) {
return;
}
$collection->set($definition->getName(), $definition);
}
public function onCheckoutOrderPlacedEvent(CheckoutOrderPlacedEvent $event): void
{
$lineItems = $event->getOrder()->getLineItems();
if ($lineItems === null || $lineItems->count() === 0) {
return;
}
// Sammle POD-LineItems und deren Typen
$podLineItems = [];
$podTypes = [];
foreach ($lineItems as $lineItem) {
$payload = $lineItem->getPayload() ?? [];
$cf = $payload['customFields'] ?? [];
$isPod = ($cf[CioPodProducts::POD_PRODUCTS_CUSTOM_FIELD_IS_POD] ?? false) === true;
$podTypeKey = $cf['custom_pod_products_podProductType'] ?? null;
if ($isPod && $podTypeKey) {
$podLineItems[] = $lineItem;
$podTypes[] = $podTypeKey;
}
}
// Keine POD-Positionen -> nichts tun
if (count($podLineItems) === 0) {
return;
}
$podTypes = array_unique($podTypes);
// Mehrere Typen gleichzeitig => derzeit nicht zulässig, abbrechen
if (count($podTypes) > 1) {
return;
}
$podTypeKey = $podTypes[0];
// Typ-Konfiguration laden
$typeConfig = $this->podTypeConfigService->getTypeConfig($podTypeKey, $event->getSalesChannelId());
$podUniqueItem = $typeConfig['podUniqueItem'] ?? false;
// Wenn unique: exakt eine Position erlaubt
if ($podUniqueItem && count($podLineItems) !== 1) {
return;
}
// Wenn nicht unique: alle POD-LineItems müssen denselben Typ haben (oben geprüft) → erlaubt auch mehrere
// Für jede POD-Position: Payload-CustomFields sicherstellen
$updates = [];
foreach ($podLineItems as $li) {
$payload = $li->getPayload() ?? [];
if (!isset($payload['customFields']) || !is_array($payload['customFields'])) {
$payload['customFields'] = [];
}
$updates[] = [
'id' => $li->getId(),
'payload' => $payload,
'productId' => $li->getProductId(),
'referencedId' => $li->getReferencedId()
];
}
if (!empty($updates)) {
$this->orderRepository->update([
[
'id' => $event->getOrder()->getId(),
'lineItems' => $updates,
]
], $event->getContext());
}
// Order mit Assoziationen nachladen und Event dispatchen
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $event->getOrder()->getId()));
PodOrdersAssociationsHelper::addAssociationsToCriteria($criteria);
$order = $this->orderRepository->search($criteria, $event->getContext())->first();
if (!$order instanceof OrderEntity) {
return;
}
$podOrderCreatedEvent = new PodOrderCreatedEvent($event->getContext(), $order, $event->getSalesChannelId());
$this->eventDispatcher->dispatch($podOrderCreatedEvent);
}
public function onStorefrontRenderEvent(StorefrontRenderEvent $event)
{
$parameters = $event->getParameters();
if (!\is_array($parameters) || !array_key_exists('page', $parameters)) {
return;
}
$page = $parameters['page'];
if (!$page instanceof CheckoutConfirmPage) {
return;
}
$lineItems = $page->getCart()->getLineItems();
$hasPodProduct = false;
$hasNonPodProduct = false;
$hasInvalidPodProduct = false;
$hasPodProductTypes = [];
$errors = [];
foreach ($lineItems as $lineItem) {
$payload = $lineItem->getPayload();
$customFields = (is_array($payload) && array_key_exists('customFields', $payload) && is_array($payload['customFields']))
? $payload['customFields']
: [];
$isPod = (array_key_exists(CioPodProducts::POD_PRODUCTS_CUSTOM_FIELD_IS_POD, $customFields)
&& $customFields[CioPodProducts::POD_PRODUCTS_CUSTOM_FIELD_IS_POD] === true);
if ($isPod) {
$hasPodProduct = true;
// CIO-AI-Driven: POD without type is considered invalid and must block (per requirement).
if (!array_key_exists('custom_pod_products_podProductType', $customFields) || empty($customFields['custom_pod_products_podProductType'])) {
$hasInvalidPodProduct = true;
}
} else {
// CIO-AI-Driven: Anything that is not POD must not be mixed with POD (incl. promotion/custom line items).
$hasNonPodProduct = true;
}
if (array_key_exists('custom_pod_products_podProductType', $customFields) &&
!empty($customFields['custom_pod_products_podProductType'])) {
$hasPodProductTypes[] = $customFields['custom_pod_products_podProductType'];
}
}
$podProductTypes = array_unique($hasPodProductTypes);
// POD darf niemals mit Nicht-POD gemischt werden (inkl. Promotion/Gutschein/Custom-LineItems).
// Zusätzlich: POD ohne Typ ist ungültig und blockiert.
if ($hasPodProduct && ($hasNonPodProduct || $hasInvalidPodProduct)) {
$this->addCartError($page->getCart(), new PodMixedCartError());
$this->addFlashErrors($event, [['type' => 'norm', 'message' => 'pod.checkout.noMixedBasketsError']]);
return;
}
// dd($podProductTypes, $this->podTypeConfigService->getTypeConfig($podProductTypes[0], $event->getSalesChannelContext()->getSalesChannelId()));
// mehrere pod product typen dürfen niemals gemischt bestellt werden
if ($hasPodProduct && count($podProductTypes) > 1) {
$this->addCartError($page->getCart(), new PodMixedCartError());
$this->addFlashErrors($event, [['type' => 'norm', 'message' => 'pod.checkout.noMixedBasketsError']]);
return;
}
if ($hasPodProduct && count($podProductTypes) === 1) {
$config = $this->podTypeConfigService->getTypeConfig($podProductTypes[array_key_first($podProductTypes)], $event->getSalesChannelContext()->getSalesChannelId());
$podUniqueItem = $config['podUniqueItem'];
// produkte mit einem pod produkt typen erlauben per default nur ein produkt im warenkorb
if ($podUniqueItem && $lineItems->count() > 1) {
$this->addCartError($page->getCart(), new PodMixedCartError());
$this->addFlashErrors($event, [['type' => 'norm', 'message' => 'pod.checkout.noMixedBasketsError']]);
}
}
}
protected function addFlashErrors(StorefrontRenderEvent $event, array $errors)
{
if (array_key_exists('errors', $event->getParameters())) {
$event->setParameter('errors', array_merge($event->getParameters()['errors'], $errors));
} else {
$event->setParameter('errors', $errors);
}
}
protected function addCartError(Cart $cart, Error $error)
{
$cart->addErrors(new PodMixedCartError());
}
}