custom/plugins/IntediaDoofinderSW6/src/Storefront/Subscriber/SearchSubscriber.php line 85

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Intedia\Doofinder\Storefront\Subscriber;
  3. use Intedia\Doofinder\Doofinder\Api\Search;
  4. use Psr\Log\LoggerInterface;
  5. use Shopware\Core\Content\Product\Events\ProductSearchCriteriaEvent;
  6. use Shopware\Core\Content\Product\Events\ProductSuggestCriteriaEvent;
  7. use Shopware\Core\Content\Product\ProductEntity;
  8. use Shopware\Core\Content\Product\SalesChannel\Listing\ProductListingResult;
  9. use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
  15. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  16. use Shopware\Core\System\SystemConfig\SystemConfigService;
  17. use Shopware\Storefront\Page\Search\SearchPageLoadedEvent;
  18. use Shopware\Storefront\Page\Suggest\SuggestPageLoadedEvent;
  19. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  20. use Symfony\Component\HttpFoundation\Request;
  21. class SearchSubscriber implements EventSubscriberInterface
  22. {
  23.     /** @var SystemConfigService */
  24.     protected $systemConfigService;
  25.     /** @var LoggerInterface */
  26.     protected $logger;
  27.     /** @var Search */
  28.     protected $searchApi;
  29.     /** @var array */
  30.     protected $doofinderIds;
  31.     /** @var integer */
  32.     protected $doofinderTotal;
  33.     /** @var integer */
  34.     protected $doofinderOffset;
  35.     /** @var integer */
  36.     protected $shopwareLimit;
  37.     /** @var integer */
  38.     protected $shopwareOffset;
  39.     /** @var bool */
  40.     protected $isScoreSorting;
  41.     /** @var bool */
  42.     protected $isSuggestCall false;
  43.     /**
  44.      * SearchSubscriber constructor.
  45.      * @param SystemConfigService $systemConfigService
  46.      * @param LoggerInterface $logger
  47.      * @param Search $searchApi
  48.      */
  49.     public function __construct(SystemConfigService $systemConfigServiceLoggerInterface $loggerSearch $searchApi)
  50.     {
  51.         $this->systemConfigService $systemConfigService;
  52.         $this->logger              $logger;
  53.         $this->searchApi           $searchApi;
  54.     }
  55.     /**
  56.      * {@inheritdoc}
  57.      */
  58.     public static function getSubscribedEvents(): array
  59.     {
  60.         return [
  61.             ProductSearchCriteriaEvent::class  => 'onSearchCriteriaEvent',
  62.             SearchPageLoadedEvent::class       => 'onSearchPageLoadedEvent',
  63.             ProductSuggestCriteriaEvent::class => 'onSuggestCriteriaEvent',
  64.             SuggestPageLoadedEvent::class      => 'onSuggestPageLoadedEvent'
  65.         ];
  66.     }
  67.     /**
  68.      * @param ProductSearchCriteriaEvent $event
  69.      */
  70.     public function onSearchCriteriaEvent(ProductSearchCriteriaEvent $event): void
  71.     {
  72.         $criteria $event->getCriteria();
  73.         $request  $event->getRequest();
  74.         $context  $event->getSalesChannelContext();
  75.         $this->handleWithDoofinder($context$request$criteria);
  76.     }
  77.     /**
  78.      * @param ProductSuggestCriteriaEvent $event
  79.      */
  80.     public function onSuggestCriteriaEvent(ProductSuggestCriteriaEvent $event): void
  81.     {
  82.         $criteria $event->getCriteria();
  83.         $request  $event->getRequest();
  84.         $context  $event->getSalesChannelContext();
  85.         $this->isSuggestCall true;
  86.         $this->handleWithDoofinder($context$request$criteria);
  87.     }
  88.     /**
  89.      * @param SalesChannelContext $context
  90.      * @param Request $request
  91.      * @param Criteria $criteria
  92.      */
  93.     protected function handleWithDoofinder(SalesChannelContext $contextRequest $requestCriteria $criteria): void
  94.     {
  95.         if ($this->systemConfigService->get('IntediaDoofinderSW6.config.doofinderEnabled'$context $context->getSalesChannel()->getId() : null)) {
  96.             $term $request->query->get('search');
  97.             if ($term) {
  98.                 $this->doofinderIds $this->searchApi->queryIds($term$context);
  99.                 $this->storeShopwareLimitAndOffset($criteria);
  100.                 $this->manipulateCriteriaLimitAndOffset($criteria);
  101.                 if (!empty($this->doofinderIds)) {
  102.                     $this->resetCriteriaFiltersQueriesAndSorting($criteria);
  103.                     $this->addProductNumbersToCriteria($criteria);
  104.                 }
  105.             }
  106.         }
  107.     }
  108.     /**
  109.      * @param Criteria $criteria
  110.      */
  111.     protected function resetCriteriaFiltersQueriesAndSorting(Criteria $criteria): void
  112.     {
  113.         $criteria->resetFilters();
  114.         $criteria->resetQueries();
  115.         if ($this->isSuggestCall || $this->checkIfScoreSorting($criteria)) {
  116.             $criteria->resetSorting();
  117.         }
  118.     }
  119.     /**
  120.      * @param Criteria $criteria
  121.      * @return bool
  122.      */
  123.     protected function checkIfScoreSorting(Criteria $criteria)
  124.     {
  125.         /** @var FieldSorting */
  126.         $sorting = !empty($criteria->getSorting()) ? $criteria->getSorting()[0] : null;
  127.         if ($sorting) {
  128.             $this->isScoreSorting $sorting->getField() === '_score';
  129.         }
  130.         return $this->isScoreSorting;
  131.     }
  132.     /**
  133.      * @param Criteria $criteria
  134.      */
  135.     protected function addProductNumbersToCriteria(Criteria $criteria): void
  136.     {
  137.         $criteria->addFilter(
  138.             new OrFilter([
  139.                 new EqualsAnyFilter('productNumber'array_values($this->doofinderIds)),
  140.                 new EqualsAnyFilter('parentId'array_keys($this->doofinderIds)),
  141.                 new EqualsAnyFilter('id'array_keys($this->doofinderIds))
  142.             ])
  143.         );
  144.     }
  145.     /**
  146.      * @param SearchPageLoadedEvent $event
  147.      */
  148.     public function onSearchPageLoadedEvent(SearchPageLoadedEvent $event): void
  149.     {
  150.         $event->getPage()->setListing($this->modifyListing($event->getPage()->getListing()));
  151.     }
  152.     /**
  153.      * @param SuggestPageLoadedEvent $event
  154.      */
  155.     public function onSuggestPageLoadedEvent(SuggestPageLoadedEvent $event): void
  156.     {
  157.         $event->getPage()->setSearchResult($this->modifyListing($event->getPage()->getSearchResult()));
  158.     }
  159.     /**
  160.      * @param EntitySearchResult $listing
  161.      * @return object|ProductListingResult
  162.      */
  163.     protected function modifyListing(EntitySearchResult $listing)
  164.     {
  165.         if ($listing && !empty($this->doofinderIds)) {
  166.             // reorder entities if doofinder score sorting
  167.             if ($this->isSuggestCall || $this->isScoreSorting) {
  168.                 $this->orderByProductNumberArray($listing->getEntities());
  169.             }
  170.             $newListing ProductListingResult::createFrom(new EntitySearchResult(
  171.                 $listing->getEntity(),
  172.                 $listing->getTotal(),
  173.                 $this->sliceEntityCollection($listing->getEntities(), $this->shopwareOffset$this->shopwareLimit),
  174.                 $listing->getAggregations(),
  175.                 $listing->getCriteria(),
  176.                 $listing->getContext()
  177.             ));
  178.             $this->reintroduceShopwareLimitAndOffset($newListing);
  179.             if ($this->isSuggestCall == false && $listing instanceof ProductListingResult) {
  180.                 $newListing->setSorting($listing->getSorting());
  181.                 if (method_exists($listing"getAvailableSortings") && method_exists($newListing"setAvailableSortings")) {
  182.                     $newListing->setAvailableSortings($listing->getAvailableSortings());
  183.                 }
  184.                 else if (method_exists($listing"getSortings") && method_exists($newListing"setSortings")) {
  185.                     $newListing->setSortings($listing->getSortings());
  186.                 }
  187.             }
  188.             return $newListing;
  189.         }
  190.         return $listing;
  191.     }
  192.     /**
  193.      * @param EntityCollection $collection
  194.      * @return EntityCollection
  195.      */
  196.     protected function orderByProductNumberArray(EntityCollection $collection): EntityCollection
  197.     {
  198.         if ($collection) {
  199.             $sortingNumbers  array_keys($this->doofinderIds);
  200.             $fallbackNumbers array_values($this->doofinderIds);
  201.             $collection->sort(
  202.                 function (ProductEntity $aProductEntity $b) use ($sortingNumbers$fallbackNumbers) {
  203.                     $aIndex false;
  204.                     $bIndex false;
  205.                     if ($a->getParentId() || $b->getParentId()) {
  206.                         $aIndex array_search($a->getParentId(), $sortingNumbers);
  207.                         $bIndex array_search($b->getParentId(), $sortingNumbers);
  208.                     }
  209.                     if ($aIndex === false || $bIndex === false) {
  210.                         $aIndex $aIndex !== false $aIndex array_search($a->getId(), $sortingNumbers);
  211.                         $bIndex $bIndex !== false $bIndex array_search($b->getId(), $sortingNumbers);
  212.                     }
  213.                     if ($aIndex === false || $bIndex === false) {
  214.                         $aIndex $aIndex !== false $aIndex array_search($a->getProductNumber(), $fallbackNumbers);
  215.                         $bIndex $bIndex !== false $bIndex array_search($b->getProductNumber(), $fallbackNumbers);
  216.                     }
  217.                     return ($aIndex !== false $aIndex PHP_INT_MAX) - ($bIndex !== false $bIndex PHP_INT_MAX); }
  218.             );
  219.         }
  220.         return $collection;
  221.     }
  222.     /**
  223.      * @param Criteria $criteria
  224.      */
  225.     protected function storeShopwareLimitAndOffset(Criteria $criteria): void
  226.     {
  227.         $this->shopwareLimit  $criteria->getLimit();
  228.         $this->shopwareOffset $criteria->getOffset();
  229.     }
  230.     /**
  231.      * @param Criteria $criteria
  232.      */
  233.     protected function manipulateCriteriaLimitAndOffset(Criteria $criteria): void
  234.     {
  235.         $criteria->setLimit(count($this->doofinderIds));
  236.         $criteria->setOffset(0);
  237.     }
  238.     /**
  239.      * @param ProductListingResult $newListing
  240.      */
  241.     protected function reintroduceShopwareLimitAndOffset(ProductListingResult $newListing): void
  242.     {
  243.         $newListing->setLimit($this->shopwareLimit);
  244.         $newListing->getCriteria()->setLimit($this->shopwareLimit);
  245.         $newListing->getCriteria()->setOffset($this->shopwareOffset);
  246.     }
  247.     /**
  248.      * @param EntityCollection $collection
  249.      * @param $offset
  250.      * @param $limit
  251.      * @return EntityCollection
  252.      */
  253.     protected function sliceEntityCollection(EntityCollection $collection$offset$limit): EntityCollection
  254.     {
  255.         $iterator    $collection->getIterator();
  256.         $newEntities = [];
  257.         $i 0;
  258.         for ($iterator->rewind(); $iterator->valid(); $iterator->next()) {
  259.             if ($i >= $offset && $i $offset $limit) {
  260.                 $newEntities[] = $iterator->current();
  261.             }
  262.             $i++;
  263.         }
  264.         return new EntityCollection($newEntities);
  265.     }
  266. }