custom/plugins/NetzpEvents6/src/Subscriber/FrontendSubscriber.php line 302

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace NetzpEvents6\Subscriber;
  3. use NetzpEvents6\Components\EventsHelper;
  4. use NetzpEvents6\Components\SlotStruct;
  5. use NetzpEvents6\Components\TicketDocumentGenerator;
  6. use NetzpEvents6\Core\SearchResult;
  7. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent;
  8. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemQuantityChangedEvent;
  9. use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
  10. use Shopware\Core\Checkout\Cart\Order\CartConvertedEvent;
  11. use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
  12. use Shopware\Core\Content\Product\Events\ProductSearchCriteriaEvent;
  13. use Shopware\Core\Content\Product\Events\ProductSuggestCriteriaEvent;
  14. use Shopware\Core\Framework\Api\Context\SalesChannelApiSource;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
  21. use Shopware\Core\Framework\Struct\ArrayStruct;
  22. use Shopware\Core\Framework\Uuid\Uuid;
  23. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  24. use Shopware\Core\System\SystemConfig\SystemConfigService;
  25. use Shopware\Storefront\Controller\CartLineItemController;
  26. use Shopware\Storefront\Page\Account\Order\AccountOrderPageLoadedEvent;
  27. use Shopware\Storefront\Page\Product\ProductPageCriteriaEvent;
  28. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  29. use Symfony\Component\DependencyInjection\ContainerInterface;
  30. use Symfony\Component\HttpFoundation\RequestStack;
  31. use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
  32. use Shopware\Core\Checkout\Order\Event\OrderStateMachineStateChangeEvent;
  33. use Shopware\Core\Framework\Struct;
  34. use Symfony\Contracts\EventDispatcher\Event;
  35. use NetzpEvents6\Core\Content\Participant\ParticipantEntity;
  36. class FrontendSubscriber implements EventSubscriberInterface
  37. {
  38.     const SEARCH_TYPE_EVENTS 11;
  39.     private $container;
  40.     private $config;
  41.     private $requestStack;
  42.     private $helper;
  43.     private $withoutParticipants;
  44.     private $salesChannelId;
  45.     public function __construct(
  46.         ContainerInterface $container,
  47.         SystemConfigService $config,
  48.         RequestStack $requestStack,
  49.         EventsHelper $helper
  50.     ) {
  51.         $this->container $container;
  52.         $this->config $config;
  53.         $this->requestStack $requestStack;
  54.         $this->helper $helper;
  55.         $context $this->requestStack->getCurrentRequest()->attributes->get('sw-context');
  56.         $this->salesChannelId null;
  57.         if ($context && $context->getSource() && $context->getSource() instanceof SalesChannelApiSource) {
  58.             $this->salesChannelId $context->getSource()->getSalesChannelId();
  59.         }
  60.         $pluginConfig $this->config->get('NetzpEvents6.config'$this->salesChannelId);
  61.         $this->withoutParticipants = (bool)$pluginConfig['anonymous'];
  62.     }
  63.     public static function getSubscribedEvents(): array
  64.     {
  65.         return [
  66.             ProductPageCriteriaEvent::class            => 'onProductCriteriaLoaded',
  67.             ProductListingCriteriaEvent::class         => 'onProductListingCriteria',
  68.             ProductSearchCriteriaEvent::class          => 'onSearchCriteria',
  69.             ProductSuggestCriteriaEvent::class         => 'onSuggestCriteria',
  70.             BeforeLineItemAddedEvent::class            => 'onLineItemAdded',
  71.             BeforeLineItemQuantityChangedEvent::class  => 'onLineItemUpdated',
  72.             CartConvertedEvent::class                  => 'onCartConverted',
  73.             CheckoutOrderPlacedEvent::class            => 'onOrderPlaced',
  74.             AccountOrderPageLoadedEvent::class         => 'onOrderLoaded',
  75.             ControllerArgumentsEvent::class            => 'onControllerEvent',
  76.             'state_enter.order.state.cancelled'        => 'orderStateCanceled',
  77.             'netzp.search.register'                    => 'registerSearchProvider'
  78.         ];
  79.     }
  80.     public function onOrderLoaded(AccountOrderPageLoadedEvent $event)
  81.     {
  82.         $repo $this->container->get('document.repository');
  83.         $repoDocumentType $this->container->get('document_type.repository');
  84.         $criteria = new Criteria();
  85.         $criteria->addFilter(new EqualsFilter('technicalName'TicketDocumentGenerator::EVENT_TICKET));
  86.         $documentType $repoDocumentType->search($criteria$event->getContext())->first();
  87.         if( ! $documentType) {
  88.             return;
  89.         }
  90.         foreach ($event->getPage()->getOrders() as $order)
  91.         {
  92.             $criteria = new Criteria();
  93.             $criteria->addFilter(new EqualsFilter('orderId'$order->getId()));
  94.             $criteria->addFilter(new EqualsFilter('documentTypeId'$documentType->getId()));
  95.             $document $repo->search($criteria$event->getContext())->first();
  96.             if($document) {
  97.                 $order->addExtension('ticket',
  98.                     new Struct\ArrayStruct(['id' => $document->getId(), 'deeplinkCode' => $document->getDeeplinkCode()])
  99.                 );
  100.             }
  101.         }
  102.     }
  103.     public function onProductCriteriaLoaded(ProductPageCriteriaEvent $event): void
  104.     {
  105.         $this->addCriteria($eventtrue);
  106.     }
  107.     public function onProductListingCriteria(ProductListingCriteriaEvent $event): void
  108.     {
  109.         $this->addCriteria($event);
  110.     }
  111.     public function onSearchCriteria(ProductSearchCriteriaEvent $event): void
  112.     {
  113.         $this->addCriteria($event);
  114.     }
  115.     public function onSuggestCriteria(ProductSuggestCriteriaEvent $event): void
  116.     {
  117.         $this->addCriteria($event);
  118.     }
  119.     private function addCriteria($eventbool $addFilter false)
  120.     {
  121.         $criteria $event->getCriteria();
  122.         $criteria->addAssociation('event');
  123.         $criteria->addAssociation('event.slots');
  124.         $criteria->addAssociation('eventparent');
  125.         $criteria->addAssociation('eventparent.slots');
  126.         if($addFilter) {
  127.             $bookingPeriod 'start';
  128.             $pluginConfig $this->config->get('NetzpEvents6.config'$this->salesChannelId);
  129.             if(array_key_exists('bookingperiod'$pluginConfig)) {
  130.                 $bookingPeriod $pluginConfig['bookingperiod'];
  131.             }
  132.             $now $this->helper->getCurrentDateTimeFormatted($this->requestStack);
  133.             $maxStartDate $now;
  134.             $slots $criteria->getAssociation('event')->getAssociation('slots');
  135.             if($bookingPeriod == 'end') {
  136.                 $slots->addFilter(new RangeFilter('until', ['gte' => $now]));
  137.             }
  138.             else {
  139.                 $maxStartDate $this->helper->getBookingStartDate($bookingPeriod)->format('Y-m-d H:i');
  140.                 $slots->addFilter(new RangeFilter('from', ['gt' => $maxStartDate]));
  141.             }
  142.             $slots->addFilter(new EqualsFilter('event.archived'false));
  143.             $slots->addFilter(new EqualsFilter('active'1));
  144.             $slots->addSorting(new FieldSorting('from''ASC'));
  145.             $slotsParent $criteria->getAssociation('eventparent')->getAssociation('slots');
  146.             if($bookingPeriod == 'end') {
  147.                 $slotsParent->addFilter(new RangeFilter('until', ['gte' => $now]));
  148.             }
  149.             else {
  150.                 $slotsParent->addFilter(new RangeFilter('from', ['gt' => $maxStartDate]));
  151.             }
  152.             $slotsParent->addFilter(new EqualsFilter('event.archived'false));
  153.             $slotsParent->addFilter(new EqualsFilter('active'1));
  154.             $slotsParent->addSorting(new FieldSorting('from''ASC'));
  155.         }
  156.     }
  157.     public function onLineItemAdded(BeforeLineItemAddedEvent $event)
  158.     {
  159.         $lineItem $event->getLineItem();
  160.         $request $this->requestStack->getCurrentRequest()->request;
  161.         if ($request->has('netzpEventId'))
  162.         {
  163.             $eventId $request->get('netzpEventId');
  164.             $repo $this->container->get('s_plugin_netzp_events_slots.repository');
  165.             $criteria = new Criteria([$eventId]);
  166.             $criteria->addAssociation('event');
  167.             $netzpEventSlot $repo->search($criteria$event->getContext())->getEntities()->first();
  168.             if($netzpEventSlot)
  169.             {
  170.                 $dateFrom $netzpEventSlot->getFrom() ?
  171.                     $netzpEventSlot->getFrom()->format(\DateTimeInterface::ATOM) :
  172.                     '';
  173.                 $dateUntil $netzpEventSlot->getUntil() ?
  174.                     $netzpEventSlot->getUntil()->format(\DateTimeInterface::ATOM) :
  175.                     '';
  176.                 $slot = new SlotStruct();
  177.                 $slot->setId($netzpEventSlot->getId());
  178.                 $slot->setTitle($netzpEventSlot->getEvent()->getTranslated()['title']);
  179.                 $slot->setLocation($netzpEventSlot->getTranslated()['location']);
  180.                 $slot->setAddress($netzpEventSlot->getTranslated()['address']);
  181.                 $slot->setFrom($dateFrom);
  182.                 $slot->setUntil($dateUntil);
  183.                 $slot->setTimezone($netzpEventSlot->getEvent()->getTimezone());
  184.                 $slot->setAvailable($netzpEventSlot->getTicketsAvailable() - $netzpEventSlot->getTicketsBooked());
  185.                 $additionalFields $this->mapAdditionalFields($netzpEventSlot->getEvent()->getAdditionalFields());
  186.                 $additionalFieldLabels $this->mapAdditionalFieldsLabels($netzpEventSlot->getEvent()->getAdditionalFields());
  187.                 $tmpAdditionalFields = [];
  188.                 for($i 1$i <= $lineItem->getQuantity(); $i++) {
  189.                     $tmpAdditionalFields[$i] = $additionalFields;
  190.                 }
  191.                 $slot->setAdditionalFields($tmpAdditionalFields);
  192.                 $slot->setAdditionalFieldsLabels($additionalFieldLabels);
  193.                 if($this->withoutParticipants)
  194.                 {
  195.                     $participants = [];
  196.                     $participantsIds = [];
  197.                     for($i 1$i <= $lineItem->getQuantity(); $i++) {
  198.                         $participants[$i] = '(' $i ')';
  199.                         $participantsIds[$i] = Uuid::randomHex();
  200.                     }
  201.                     $slot->setParticipants($participants);
  202.                     $slot->setParticipantsIds($participantsIds);
  203.                 }
  204.                 $lineItem->setPayloadValue('netzp_event'$slot->getVars());
  205.             }
  206.         }
  207.     }
  208.     public function onLineItemUpdated(BeforeLineItemQuantityChangedEvent $event)
  209.     {
  210.         $lineItem $event->getLineItem();
  211.         $request $this->requestStack->getCurrentRequest()->request;
  212.         if ($lineItem->hasPayloadValue('netzp_event'))
  213.         {
  214.             $payload $lineItem->getPayloadValue('netzp_event');
  215.             if($this->withoutParticipants)
  216.             {
  217.                 $participants = [];
  218.                 $participantsIds = [];
  219.                 for($i 1$i <= $lineItem->getQuantity(); $i++)
  220.                 {
  221.                     $participants[$i] = '(' $i ')';
  222.                     $participantsIds[$i] = Uuid::randomHex();
  223.                 }
  224.                 $payload['participants'] = $participants;
  225.                 $payload['participantsIds'] = $participantsIds;
  226.             }
  227.             else if($request->has('netzpEventParticipant'))
  228.             {
  229.                 $participants $request->get('netzpEventParticipant');
  230.                 $participantsIds = [];
  231.                 $n 1;
  232.                 foreach($participants as $participant)
  233.                 {
  234.                     $participantsIds[$n] = Uuid::randomHex();
  235.                     $n++;
  236.                 }
  237.                 $payload['participants'] = $participants;
  238.                 $payload['participantsIds'] = $participantsIds;
  239.             }
  240.             if($request->has('netzpEventAdditionalField'))
  241.             {
  242.                 $additionalFields $request->get('netzpEventAdditionalField');
  243.                 $payload['additionalFields'] = $additionalFields;
  244.             }
  245.             if ($lineItem->getQuantity() > count($payload['additionalFields'])) {
  246.                 $additionalFields $payload['additionalFields'];
  247.                 for($i count($payload['additionalFields']) + 1$i <= $lineItem->getQuantity(); $i++) {
  248.                     $firstEntry array_map(function($f) {
  249.                         return '';
  250.                     }, $additionalFields[1]);
  251.                     $additionalFields[$i] = $firstEntry// copy from first participant entry
  252.                 }
  253.                 $payload['additionalFields'] = $additionalFields;
  254.             }
  255.             $lineItem->setPayloadValue('netzp_event'$payload);
  256.         }
  257.     }
  258.     public function onControllerEvent(ControllerArgumentsEvent $event): void
  259.     {
  260.         $controller $event->getController();
  261.         $route $event->getRequest()->attributes->get('_route') ?? '';
  262.         if (is_array($controller) && count($controller) > 0) {
  263.             $controller $controller[0];
  264.         }
  265.         if ( ! $controller) {
  266.             return;
  267.         }
  268.         if($controller instanceof CartLineItemController && $route === 'frontend.checkout.line-item.add')
  269.         {
  270.             $args $event->getArguments();
  271.             // prevent article stacking only for events
  272.             if(count($args) >= && $event->getRequest()->request->get('netzpEventId''') != '') {
  273.                 $keys $args[1]->get('lineItems')->keys();
  274.                 if($keys && is_array($keys) && count($keys) > 0) {
  275.                     $lineItem $args[1]->get('lineItems')->get($keys[0]);
  276.                     if ($lineItem) {
  277.                         $lineItem->set('id'Uuid::randomHex());
  278.                         $event->setArguments($args);
  279.                     }
  280.                 }
  281.             }
  282.         }
  283.     }
  284.     public function onCartConverted(CartConvertedEvent $event)
  285.     {
  286.         $cart $event->getCart();
  287.         $lineItems $cart->getLineItems();
  288.         foreach($lineItems as $lineItem) {
  289.             if($lineItem->hasPayloadValue('netzp_event')) {
  290.                 $this->helper->processEvent($lineItem$event->getContext());
  291.             }
  292.         }
  293.     }
  294.     public function onOrderPlaced(CheckoutOrderPlacedEvent $event)
  295.     {
  296.         $this->helper->processOrder($event);
  297.     }
  298.     public function orderStateCanceled(OrderStateMachineStateChangeEvent $event)
  299.     {
  300.         $repoSlots $this->container->get('s_plugin_netzp_events_slots.repository');
  301.         $repoParticipants $this->container->get('s_plugin_netzp_events_participants.repository');
  302.         $context $event->getContext();
  303.         $lineItems $event->getOrder()->getLineItems();
  304.         foreach($lineItems as $lineItem)
  305.         {
  306.             $payload $lineItem->getPayload();
  307.             if(array_key_exists('netzp_event'$payload)) {
  308.                 $ticket $payload['netzp_event'];
  309.                 $criteria = new Criteria([$ticket['id']]);
  310.                 $criteria->addAssociations(['event''participants''event.product']);
  311.                 $slot $repoSlots->search($criteria$context)->getEntities()->first();
  312.                 $numberOfParticipants count($ticket['participants']);
  313.                 $newTicketsBooked $slot->getTicketsBooked() - $numberOfParticipants;
  314.                 $repoSlots->update([
  315.                     [
  316.                         'id'            => $slot->getId(),
  317.                         'ticketsBooked' => $newTicketsBooked
  318.                     ]
  319.                 ], $context);
  320.                 $this->helper->invalidateProductCache($slot->getEvent()->getProductid());
  321.                 $this->helper->invalidateEventListing();
  322.                 foreach($ticket['participants'] as $participantNo => $participant) {
  323.                     $participantId $ticket['participantsIds'][$participantNo];
  324.                     $repoParticipants->update([
  325.                         [
  326.                             'id'     => $participantId,
  327.                             'status' => ParticipantEntity::STATUS_CANCELED
  328.                         ]
  329.                     ], $context);
  330.                 }
  331.             }
  332.         }
  333.     }
  334.     public function registerSearchProvider(Event $event)
  335.     {
  336.         $pluginConfig $this->config->get('NetzpEvents6.config'$this->salesChannelId);
  337.         if((bool)$pluginConfig['searchEvents'] === false) {
  338.             return;
  339.         }
  340.         $event->setData([
  341.             'key'      => 'events',
  342.             'label'    => 'netzp.events.searchLabel',
  343.             'function' => [$this'doSearch']
  344.         ]);
  345.     }
  346.     public function doSearch(string $querySalesChannelContext $salesChannelContextbool $isSuggest false): array
  347.     {
  348.         $results = [];
  349.         $events $this->getEvents($query$salesChannelContext$isSuggest);
  350.         if($events == null) {
  351.             return [];
  352.         }
  353.         foreach ($events->getEntities() as $event) {
  354.             if( ! $event->getProduct()) continue;
  355.             $tmpResult = new SearchResult();
  356.             $tmpResult->setType(static::SEARCH_TYPE_EVENTS);
  357.             $tmpResult->setId($event->getId());
  358.             $tmpResult->setTitle($event->getTranslated()['title'] ?? '');
  359.             $tmpResult->setDescription($event->getTranslated()['teaser'] ?? '');
  360.             $tmpResult->setMedia($event->getImage());
  361.             $tmpResult->setTotal($events->getTotal());
  362.             $tmpResult->addExtension('productId', new ArrayStruct(['value' => $event->getProduct()->getId()]));
  363.             $tmpResult->addExtension('slots', new ArrayStruct(['value' => $event->getSlots()->count()]));
  364.             $results[] = $tmpResult;
  365.         }
  366.         return $results;
  367.     }
  368.     private function getEvents($querySalesChannelContext $salesChannelContextbool $isSuggest false)
  369.     {
  370.         $query trim($query);
  371.         $words explode(' '$query);
  372.         if (count($words) > 0) {
  373.             $now $this->helper->getCurrentDateTimeFormatted($this->requestStack);
  374.             $repo $this->container->get('s_plugin_netzp_events.repository');
  375.             $criteria = new Criteria();
  376.             $criteria->addAssociation('product');
  377.             $criteria->addAssociation('image');
  378.             $criteria->addAssociation('slots');
  379.             $slots $criteria->getAssociation('slots');
  380.             $slots->addSorting(new FieldSorting('from''ASC'));
  381.             $slots->addFilter(new RangeFilter('from', ['gt' => $now]));
  382.             $slots->addFilter(new EqualsFilter('active'true));
  383.             $criteria->addSorting(new FieldSorting('title'FieldSorting::ASCENDING));
  384.             $criteria->addFilter(new EqualsFilter('bookable'true));
  385.             $criteria->addFilter(new EqualsFilter('archived'false));
  386.             $filter = [];
  387.             foreach ($words as $word) {
  388.                 $filter[] = new ContainsFilter('title'$word);
  389.                 $filter[] = new ContainsFilter('teaser'$word);
  390.                 $filter[] = new ContainsFilter('description'$word);
  391.             }
  392.             $criteria->addFilter(new MultiFilter(MultiFilter::CONNECTION_OR$filter));
  393.             return $repo->search($criteria$salesChannelContext->getContext());
  394.         }
  395.         return null;
  396.     }
  397.     private function mapAdditionalFields(?string $additionalFields): array
  398.     {
  399.         if($additionalFields === null || $additionalFields == '') {
  400.             return [];
  401.         }
  402.         $fields array_map(function($field) {
  403.             return $this->sanitizeAdditionalField($field);
  404.         }, explode(';'$additionalFields));
  405.         $fields array_flip($fields);
  406.         $fields array_map(function($f) {
  407.             return '';
  408.         }, $fields);
  409.         return $fields;
  410.     }
  411.     private function mapAdditionalFieldsLabels(?string $additionalFields): array
  412.     {
  413.         if($additionalFields === null || $additionalFields == '') {
  414.             return [];
  415.         }
  416.         $fields explode(';'$additionalFields);
  417.         $tmp = [];
  418.         foreach($fields as $field) {
  419.             $f $this->sanitizeAdditionalField($field);
  420.             $tmp[$f] = $field;
  421.         }
  422.         return $tmp;
  423.     }
  424.     private function sanitizeAdditionalField(?string $f)
  425.     {
  426.         $f trim($f);
  427.         $f strtolower($f);
  428.         $f str_replace(' '''$f);
  429.         $f str_replace('/'''$f);
  430.         $f str_replace('_'''$f);
  431.         $f str_replace(','''$f);
  432.         $f str_replace(':'''$f);
  433.         $f str_replace(';'''$f);
  434.         $f str_replace('#'''$f);
  435.         $f str_replace('@'''$f);
  436.         $f str_replace('$'''$f);
  437.         $f str_replace('€'''$f);
  438.         $f str_replace('+'''$f);
  439.         $f str_replace('-'''$f);
  440.         $f str_replace('\''''$f);
  441.         $f str_replace('\"'''$f);
  442.         return $f;
  443.     }
  444. }