custom/plugins/WnsAvailableItemsFirst/src/Subscriber/ProductListingLoaderSubscriber.php line 78

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Wns\AvailableItemsFirst\Subscriber;
  3. use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
  4. use Shopware\Core\Content\Product\Events\ProductListingResultEvent;
  5. use Shopware\Core\Content\Product\Events\ProductSearchCriteriaEvent;
  6. use Shopware\Core\Content\Product\Events\ProductSearchResultEvent;
  7. use Shopware\Core\Content\Product\SalesChannel\Listing\ProductListingLoader;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Bucket\FilterAggregation;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\CountAggregation;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\CountResult;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\Filter;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
  18. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  19. class ProductListingLoaderSubscriber implements EventSubscriberInterface
  20. {
  21.     private const COUNT_PRODUCT_AVAILABLE_AGGREGATION 'count-product-available';
  22.     private const COUNT_PRODUCT_SOLD_OUT_AGGREGATION 'count-product-sold-out';
  23.     private const PRODUCT_AVAILABLE_FILTER_NAME 'productAvailableFilter';
  24.     private ProductListingLoader $listingLoader;
  25.     public function __construct(ProductListingLoader $listingLoader)
  26.     {
  27.         $this->listingLoader $listingLoader;
  28.     }
  29.     public static function getSubscribedEvents(): array
  30.     {
  31.         return [
  32.             ProductSearchCriteriaEvent::class => ['onProductListingCriteriaLoaded', -200],
  33.             ProductListingCriteriaEvent::class => ['onProductListingCriteriaLoaded', -200],
  34.             ProductSearchResultEvent::class => ['onProductListingLoaded', -200],
  35.             ProductListingResultEvent::class => ['onProductListingLoaded', -200],
  36.         ];
  37.     }
  38.     public function onProductListingCriteriaLoaded(ProductListingCriteriaEvent $event): void
  39.     {
  40.         $criteria $event->getCriteria();
  41.         $productAvailableFilter $this->getProductAvailableCriteria();
  42.         $soldOutFilterAgg = new FilterAggregation('product-sold-out',
  43.             new CountAggregation(self::COUNT_PRODUCT_SOLD_OUT_AGGREGATION'displayGroup'),
  44.             [
  45.                 $this->getProductSoldOutCriteria(),
  46.             ]
  47.         );
  48.         $availableFilterAgg = new FilterAggregation('product-available',
  49.             new CountAggregation(self::COUNT_PRODUCT_AVAILABLE_AGGREGATION'displayGroup'),
  50.             [
  51.                 $productAvailableFilter,
  52.             ]
  53.         );
  54.         
  55.         $criteria->addAggregation(
  56.             $soldOutFilterAgg,
  57.             $availableFilterAgg
  58.         );
  59.         $productAvailableFilter->assign([
  60.             'name' => self::PRODUCT_AVAILABLE_FILTER_NAME,
  61.         ]);
  62.         $criteria->addPostFilter($productAvailableFilter);
  63.     }
  64.     public function onProductListingLoaded(ProductListingResultEvent $event): void
  65.     {
  66.         $result $event->getResult();
  67.         /** @var CountResult|null $productSoldOutAgg */
  68.         $productSoldOutAgg $result->getAggregations()->get(self::COUNT_PRODUCT_SOLD_OUT_AGGREGATION);
  69.         if ($productSoldOutAgg === null || $productSoldOutAgg->getCount() === 0) {
  70.             return;
  71.         }
  72.         /** @var CountResult|null $productAvailableAgg */
  73.         $productAvailableAgg $result->getAggregations()->get(self::COUNT_PRODUCT_AVAILABLE_AGGREGATION);
  74.         if ($productAvailableAgg === null) {
  75.             return;
  76.         }
  77.         $totalProductAvailable $productAvailableAgg->getCount();
  78.         $result->assign([
  79.             'total' => $totalProductAvailable $productSoldOutAgg->getCount(),
  80.         ]);
  81.         $limitSoldOutThisPage $result->getLimit() - $result->count();
  82.         if ($limitSoldOutThisPage == 0) {
  83.             return;
  84.         }
  85.         /** @var Criteria $criteria */
  86.         $criteria Criteria::createFrom($result->getCriteria());
  87.         $criteria->setLimit($limitSoldOutThisPage);
  88.         $offset $result->getPage() * $result->getLimit() - $totalProductAvailable $limitSoldOutThisPage;
  89.         if ($offset 0) {
  90.             $offset 0;
  91.         }
  92.         $criteria->setOffset($offset);
  93.         $criteria->resetPostFilters();
  94.         $criteria->resetAggregations();
  95.         $criteria->addFilter($this->getProductSoldOutCriteria());
  96.         $outOfStock $this->listingLoader->load($criteria$event->getSalesChannelContext());
  97.         if ($outOfStock->count() === 0) {
  98.             return;
  99.         }
  100.         $result->getEntities()->merge($outOfStock->getEntities());
  101.         $result->merge($outOfStock->getEntities());
  102.     }
  103.     private function getProductSoldOutCriteria(): Filter
  104.     {
  105.         return new MultiFilter(MultiFilter::CONNECTION_OR, [
  106.             new RangeFilter('stock', [
  107.                 RangeFilter::LTE => 0
  108.             ]),
  109.             new AndFilter([
  110.                 new RangeFilter('availableStock', [
  111.                     RangeFilter::LTE => 0
  112.                 ]),
  113.                 new EqualsFilter('isCloseout'true),
  114.             ])
  115.         ]);
  116.     }
  117.     private function getProductAvailableCriteria(): Filter
  118.     {
  119.         return new NotFilter(MultiFilter::CONNECTION_OR, [
  120.             new RangeFilter('stock', [
  121.                 RangeFilter::LTE => 0
  122.             ]),
  123.             new AndFilter([
  124.                 new RangeFilter('availableStock', [
  125.                     RangeFilter::LTE => 0
  126.                 ]),
  127.                 new EqualsFilter('isCloseout'true),
  128.             ])
  129.         ]);
  130.     }
  131. }