<?php
namespace Cogi\CogiFreeProductQuantity\Subscriber;
use Shopware\Core\Checkout\Cart\Order\OrderConvertedEvent;
use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator;
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
use Shopware\Core\Checkout\Cart\Tax\Struct\CalculatedTaxCollection;
use Shopware\Core\Checkout\Cart\Tax\Struct\TaxRuleCollection;
use Shopware\Core\Content\Product\DataAbstractionLayer\CheapestPrice\CalculatedCheapestPrice;
use Shopware\Core\Content\Product\ProductCollection;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Content\Product\SalesChannel\Detail\AbstractProductDetailRoute;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
use Shopware\Core\Content\ProductStream\Service\ProductStreamBuilderInterface;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedEvent;
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
class FreeProductQuantitySubscriber implements EventSubscriberInterface
{
private ProductStreamBuilderInterface $productStreamBuilder;
/**
* @var EntityRepositoryInterface
*/
private $productRepository;
/**
* @var EntityRepositoryInterface
*/
private $productStreamRepository;
/**
* @var AbstractProductDetailRoute
*/
private $productDetailRoute;
/**
* @var QuantityPriceCalculator
*/
private $calculator;
/**
* @var SystemConfigService
*/
private $systemConfigService;
public function __construct(
ProductStreamBuilderInterface $productStreamBuilder,
EntityRepositoryInterface $productRepository,
EntityRepositoryInterface $productStreamRepository,
AbstractProductDetailRoute $productDetailRoute,
QuantityPriceCalculator $calculator,
SystemConfigService $systemConfigService
) {
$this->productStreamBuilder = $productStreamBuilder;
$this->productRepository = $productRepository;
$this->productStreamRepository = $productStreamRepository;
$this->productDetailRoute = $productDetailRoute;
$this->calculator = $calculator;
$this->systemConfigService = $systemConfigService;
}
public static function getSubscribedEvents(): array
{
return [
CheckoutConfirmPageLoadedEvent::class => 'onCheckoutConfirmPageLoadedEvent',
CheckoutCartPageLoadedEvent::class => 'onCheckoutCartPageLoadedEvent'
];
}
public function onCheckoutCartPageLoadedEvent(CheckoutCartPageLoadedEvent $event): void
{
$active = $this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityActive', $event->getSalesChannelContext()->getSalesChannelId());
$checkCustomerGroup = false;
if(empty($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityCustomerGroupIds', $event->getSalesChannelContext()->getSalesChannelId()))) {
$checkCustomerGroup = true;
}
if(!empty($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityCustomerGroupIds', $event->getSalesChannelContext()->getSalesChannelId()))) {
if(!empty($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityCustomerGroupIds', $event->getSalesChannelContext()->getSalesChannelId()) && !empty($event->getSalesChannelContext()->getCustomer()))) {
foreach ($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityCustomerGroupIds', $event->getSalesChannelContext()->getSalesChannelId()) as $customerGroupId) {
if ($event->getSalesChannelContext()->getCustomer()->getGroupId() == $customerGroupId) {
$checkCustomerGroup = true;
}
}
}
}
if($active && $checkCustomerGroup) {
if($event->getRequest()->get('page')) {
$page = $event->getRequest()->get('page');
} else {
$page = 1;
}
$freeProductRule = $this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantitySelect', $event->getSalesChannelContext()->getSalesChannelId());
if(!empty($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityStreamFree', $event->getSalesChannelContext()->getSalesChannelId()))) {
$productStreamId = $this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityStreamFree', $event->getSalesChannelContext()->getSalesChannelId());
} else {
return;
}
$total = $this->getFreeProductTotal($productStreamId, $event->getSalesChannelContext(), $event->getRequest());
$limit = 8;
$maxPages = ceil($total / $limit);
if($maxPages < 1) {
$maxPages = 1;
}
$prevPage = true;
$nextPage = true;
if($page < 2) {
$prevPage = false;
}
if($page >= $maxPages) {
$nextPage = false;
}
$products = $this->getProductStreamCollection($productStreamId, $event->getSalesChannelContext(), $page, $limit, $event->getRequest());
$freeProductCollection = [];
/** @var ProductEntity $product */
foreach($products as $product) {
/** @var SalesChannelProductEntity $salesChannelProduct */
$salesChannelProduct = $this->createFreeProduct($product, $event->getSalesChannelContext());
array_push($freeProductCollection, $salesChannelProduct);
}
$event->getPage()->addExtension('FreeProduct', new ArrayStruct([
'total' => $total,
'maxPages' => $maxPages,
'activePage' => $page,
'nextPage' => $nextPage,
'prevPage' => $prevPage,
]));
$event->getPage()->getCart()->setExtensions(["FreeProducts" => $freeProductCollection, "freeProductRules" => new ArrayStruct($freeProductRule)]);
}
}
public function onCheckoutConfirmPageLoadedEvent(CheckoutConfirmPageLoadedEvent $event): void
{
$active = $this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityActive', $event->getSalesChannelContext()->getSalesChannelId());
$checkCustomerGroup = false;
if(!$event->getSalesChannelContext()->getCustomer()) {
return;
}
if(empty($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityCustomerGroupIds', $event->getSalesChannelContext()->getSalesChannelId()))) {
$checkCustomerGroup = true;
}
if(!empty($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityCustomerGroupIds', $event->getSalesChannelContext()->getSalesChannelId()))) {
if(!empty($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityCustomerGroupIds', $event->getSalesChannelContext()->getSalesChannelId()) && !empty($event->getSalesChannelContext()->getCustomer()))) {
foreach ($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityCustomerGroupIds', $event->getSalesChannelContext()->getSalesChannelId()) as $customerGroupId) {
if ($event->getSalesChannelContext()->getCustomer()->getGroupId() == $customerGroupId) {
$checkCustomerGroup = true;
}
}
}
}
if($active && $checkCustomerGroup) {
if($event->getRequest()->get('page')) {
$page = $event->getRequest()->get('page');
} else {
$page = 1;
}
$freeProductRule = $this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantitySelect', $event->getSalesChannelContext()->getSalesChannelId());
if(!empty($this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityStreamFree', $event->getSalesChannelContext()->getSalesChannelId()))) {
$productStreamId = $this->systemConfigService->get('CogiFreeProductQuantity.config.cogiFreeProductQuantityStreamFree', $event->getSalesChannelContext()->getSalesChannelId());
} else {
return;
}
$total = $this->getFreeProductTotal($productStreamId, $event->getSalesChannelContext(), $event->getRequest());
$limit = 8;
$maxPages = ceil($total / $limit);
if($maxPages < 1) {
$maxPages = 1;
}
$prevPage = true;
$nextPage = true;
if($page < 2) {
$prevPage = false;
}
if($page >= $maxPages) {
$nextPage = false;
}
$products = $this->getProductStreamCollection($productStreamId, $event->getSalesChannelContext(), $page, $limit, $event->getRequest());
$freeProductCollection = [];
/** @var ProductEntity $product */
foreach($products as $product) {
/** @var SalesChannelProductEntity $salesChannelProduct */
$salesChannelProduct = $this->createFreeProduct($product, $event->getSalesChannelContext());
array_push($freeProductCollection, $salesChannelProduct);
}
$event->getPage()->addExtension('FreeProduct', new ArrayStruct([
'total' => $total,
'maxPages' => $maxPages,
'activePage' => $page,
'nextPage' => $nextPage,
'prevPage' => $prevPage,
]));
$event->getPage()->getCart()->setExtensions(["FreeProducts" => $freeProductCollection, "freeProductRules" => new ArrayStruct($freeProductRule)]);
}
}
private function getProductStreamCollection(String $productStreamId, SalesChannelContext $context, int $page, int $limit, Request $request): ProductCollection
{
$filters = $this->productStreamBuilder->buildFilters($productStreamId, $context->getContext());
if($request->get("search")) {
$searchWord = $request->get("search");
array_push($filters, new MultiFilter(MultiFilter::CONNECTION_OR, [
new ContainsFilter("productNumber", $searchWord),
new ContainsFilter("name", $searchWord)
]));
}
$criteria = new Criteria();
$criteria->addFilter(new MultiFilter(MultiFilter::CONNECTION_AND, $filters));
$criteria->setLimit($limit);
$criteria->addAssociation('visibilities');
$criteria->addFilter(new EqualsFilter('visibilities.salesChannelId', $context->getSalesChannelId()));
$criteria->addFilter(new EqualsFilter('active', true));
$newOffset = ($page - 1) * $limit;
$criteria->setOffset($newOffset);
/** @var ProductCollection $products */
$products = $this->productRepository->search($criteria, $context->getContext())->getEntities();
return $products;
}
private function getFreeProductTotal(String $productStreamId, SalesChannelContext $context, Request $request): int
{
$filters = $this->productStreamBuilder->buildFilters($productStreamId, $context->getContext());
if($request->get("search")) {
$searchWord = $request->get("search");
array_push($filters, new MultiFilter(MultiFilter::CONNECTION_OR, [
new ContainsFilter("productNumber", $searchWord),
new ContainsFilter("name", $searchWord)
]));
}
$criteria = new Criteria();
$criteria->setTotalCountMode(Criteria::TOTAL_COUNT_MODE_EXACT);
$criteria->addFilter(new MultiFilter(MultiFilter::CONNECTION_AND, $filters));
$criteria->addAssociation('visibilities');
$criteria->addFilter(new EqualsFilter('visibilities.salesChannelId', $context->getSalesChannelId()));
$criteria->addFilter(new EqualsFilter('active', true));
$totalFreeProducts = $this->productRepository->searchIds($criteria, $context->getContext())->getTotal();
return $totalFreeProducts;
}
public function createFreeProduct(ProductEntity $freeProduct, SalesChannelContext $context)
{
$criteria = new Criteria();
$criteria->addAssociation('options');
$criteria->addAssociation('options.group');
$criteria->addAssociation('media');
/** @var SalesChannelProductEntity $salesChannelProduktEntity */
$salesChannelProduktEntity = $this->productDetailRoute->load($freeProduct->getId(), new Request(), $context, $criteria)->getProduct();
$calcPrice = new CalculatedPrice(
0,
0,
new CalculatedTaxCollection(),
new TaxRuleCollection(),
1
);
$quantityPriceDefinition = new QuantityPriceDefinition(0.0, new TaxRuleCollection());
$cheapest = CalculatedCheapestPrice::createFrom(
$this->calculator->calculate($quantityPriceDefinition, $context)
);
$cheapest->setHasRange(false);
$salesChannelProduktEntity->setCalculatedCheapestPrice($cheapest);
$salesChannelProduktEntity->setCalculatedPrice($calcPrice);
$salesChannelProduktEntity->addExtension("FreeProductQuantity", new ArrayEntity(["isFreeProductQuantity" => true]));
return $salesChannelProduktEntity;
}
}