<?php declare(strict_types=1);
namespace Wns\AvailableItemsFirst\Subscriber;
use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
use Shopware\Core\Content\Product\Events\ProductListingResultEvent;
use Shopware\Core\Content\Product\Events\ProductSearchCriteriaEvent;
use Shopware\Core\Content\Product\Events\ProductSearchResultEvent;
use Shopware\Core\Content\Product\SalesChannel\Listing\ProductListingLoader;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Bucket\FilterAggregation;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\CountAggregation;
use Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\CountResult;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\Filter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ProductListingLoaderSubscriber implements EventSubscriberInterface
{
private const COUNT_PRODUCT_AVAILABLE_AGGREGATION = 'count-product-available';
private const COUNT_PRODUCT_SOLD_OUT_AGGREGATION = 'count-product-sold-out';
private const PRODUCT_AVAILABLE_FILTER_NAME = 'productAvailableFilter';
private ProductListingLoader $listingLoader;
public function __construct(ProductListingLoader $listingLoader)
{
$this->listingLoader = $listingLoader;
}
public static function getSubscribedEvents(): array
{
return [
ProductSearchCriteriaEvent::class => ['onProductListingCriteriaLoaded', -200],
ProductListingCriteriaEvent::class => ['onProductListingCriteriaLoaded', -200],
ProductSearchResultEvent::class => ['onProductListingLoaded', -200],
ProductListingResultEvent::class => ['onProductListingLoaded', -200],
];
}
public function onProductListingCriteriaLoaded(ProductListingCriteriaEvent $event): void
{
$criteria = $event->getCriteria();
$productAvailableFilter = $this->getProductAvailableCriteria();
$soldOutFilterAgg = new FilterAggregation('product-sold-out',
new CountAggregation(self::COUNT_PRODUCT_SOLD_OUT_AGGREGATION, 'displayGroup'),
[
$this->getProductSoldOutCriteria(),
]
);
$availableFilterAgg = new FilterAggregation('product-available',
new CountAggregation(self::COUNT_PRODUCT_AVAILABLE_AGGREGATION, 'displayGroup'),
[
$productAvailableFilter,
]
);
$criteria->addAggregation(
$soldOutFilterAgg,
$availableFilterAgg
);
$productAvailableFilter->assign([
'name' => self::PRODUCT_AVAILABLE_FILTER_NAME,
]);
$criteria->addPostFilter($productAvailableFilter);
}
public function onProductListingLoaded(ProductListingResultEvent $event): void
{
$result = $event->getResult();
/** @var CountResult|null $productSoldOutAgg */
$productSoldOutAgg = $result->getAggregations()->get(self::COUNT_PRODUCT_SOLD_OUT_AGGREGATION);
if ($productSoldOutAgg === null || $productSoldOutAgg->getCount() === 0) {
return;
}
/** @var CountResult|null $productAvailableAgg */
$productAvailableAgg = $result->getAggregations()->get(self::COUNT_PRODUCT_AVAILABLE_AGGREGATION);
if ($productAvailableAgg === null) {
return;
}
$totalProductAvailable = $productAvailableAgg->getCount();
$result->assign([
'total' => $totalProductAvailable + $productSoldOutAgg->getCount(),
]);
$limitSoldOutThisPage = $result->getLimit() - $result->count();
if ($limitSoldOutThisPage == 0) {
return;
}
/** @var Criteria $criteria */
$criteria = Criteria::createFrom($result->getCriteria());
$criteria->setLimit($limitSoldOutThisPage);
$offset = $result->getPage() * $result->getLimit() - $totalProductAvailable - $limitSoldOutThisPage;
if ($offset < 0) {
$offset = 0;
}
$criteria->setOffset($offset);
$criteria->resetPostFilters();
$criteria->resetAggregations();
$criteria->addFilter($this->getProductSoldOutCriteria());
$outOfStock = $this->listingLoader->load($criteria, $event->getSalesChannelContext());
if ($outOfStock->count() === 0) {
return;
}
$result->getEntities()->merge($outOfStock->getEntities());
$result->merge($outOfStock->getEntities());
}
private function getProductSoldOutCriteria(): Filter
{
return new MultiFilter(MultiFilter::CONNECTION_OR, [
new RangeFilter('stock', [
RangeFilter::LTE => 0
]),
new AndFilter([
new RangeFilter('availableStock', [
RangeFilter::LTE => 0
]),
new EqualsFilter('isCloseout', true),
])
]);
}
private function getProductAvailableCriteria(): Filter
{
return new NotFilter(MultiFilter::CONNECTION_OR, [
new RangeFilter('stock', [
RangeFilter::LTE => 0
]),
new AndFilter([
new RangeFilter('availableStock', [
RangeFilter::LTE => 0
]),
new EqualsFilter('isCloseout', true),
])
]);
}
}