<?php declare(strict_types=1);
namespace DvdwDirectory\Content\Product\Subscriber;
use Dvdw\Events\Content\DvdwEvent\Struct\DvdwEventContextExtension;
use DvdwDirectory\Content\Product\Service\ProductCountService;
use DvdwDirectory\Content\Product\Struct\FilterByParticipationReader;
use DvdwDirectory\Content\Product\Struct\FilterByHasPromotionStruct;
use DvdwDirectory\Content\Product\Struct\FilterByPaidStruct;
use DvdwDirectory\Content\Product\Struct\ProductListingLimitStruct;
use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
use Shopware\Core\Content\Product\Events\ProductSearchCriteriaEvent;
use Shopware\Core\Content\Product\SearchKeyword\ProductSearchTermInterpreter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Query\ScoreQuery;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Term\SearchPattern;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Page\Product\ProductPageCriteriaEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
class ProductCriteriaSubscriber implements EventSubscriberInterface
{
private ProductSearchTermInterpreter $interpreter;
private ProductCountService $productCountService;
private SystemConfigService $systemConfigService;
public function __construct(
ProductSearchTermInterpreter $interpreter,
ProductCountService $productCountService,
SystemConfigService $systemConfigService
)
{
$this->interpreter = $interpreter;
$this->productCountService = $productCountService;
$this->systemConfigService = $systemConfigService;
}
public static function getSubscribedEvents(): array
{
return [
ProductListingCriteriaEvent::class => 'extendListing',
ProductSearchCriteriaEvent::class => 'extendListing',
ProductPageCriteriaEvent::class => 'extendDetail'
];
}
public function extendListing(ProductListingCriteriaEvent $event): void
{
$request = $event->getRequest();
$criteria = $event->getCriteria();
$context = $event->getSalesChannelContext();
$dvdwEventId = $this->getDvdwEventId($context);
$this->addAssociations($criteria);
$this->handleSearch($request, $criteria, $context, $dvdwEventId);
$limit = $context->getExtension(ProductListingLimitStruct::KEY);
if ($limit && $limit->getProductListingLimit() > 0) {
$criteria->setLimit($limit->getProductListingLimit());
}
$this->applyRandomPage($request, $criteria, $context);
$filterByPaid = $context->getExtension(FilterByPaidStruct::KEY);
if ($filterByPaid && $filterByPaid->isFilterByPaid()) {
$criteria->addFilter(new OrFilter([
new EqualsFilter('product.dvdwParticipations.orderLineItem.dvdwTicket.type', 'gold'),
new EqualsFilter('product.dvdwParticipations.orderLineItem.dvdwTicket.type', 'silver')
]));
// Add the date filter for August, September, and October of the current year
$currentYear = date('Y');
$criteria->addFilter(new OrFilter([
new RangeFilter('createdAt', [
RangeFilter::GTE => $currentYear . '-08-01 00:00:00',
RangeFilter::LTE => $currentYear . '-10-31 23:59:59'
]),
new RangeFilter('updatedAt', [
RangeFilter::GTE => $currentYear . '-08-01 00:00:00',
RangeFilter::LTE => $currentYear . '-10-31 23:59:59'
])
]));
}
if ($dvdwEventId === null) {
return;
}
$filterByHasPromotion = $context->getExtension(FilterByHasPromotionStruct::KEY);
if ($filterByHasPromotion && $filterByHasPromotion->isFilterByHasPromotion()) {
$criteria = $this->addPromotionFilter($criteria, $dvdwEventId);
}
$this->filterParticipations($criteria, $dvdwEventId);
if (!FilterByParticipationReader::isOnlyParticipants($context)) {
return;
}
$criteria->addFilter(
new EqualsFilter(
'dvdwParticipations.dvdwEventId',
$dvdwEventId
)
);
}
public function extendDetail(ProductPageCriteriaEvent $event): void
{
$criteria = $event->getCriteria();
$this->addAssociations($criteria);
$criteria->addAssociations([
'dvdwPromotions.dvdwPromotionLimitations',
'dvdwPromotions.media',
'dvdwProducts.media'
]);
$context = $event->getSalesChannelContext();
$dvdwEventId = $this->getDvdwEventId($context);
if ($dvdwEventId === null) {
return;
}
$this->filterParticipations($criteria, $dvdwEventId);
}
private function handleSearch(Request $request, Criteria $criteria, SalesChannelContext $context, ?string $dvdwEventId): void
{
$search = $request->query->get('webshopSearch');
if ($search !== null) {
if (is_array($search)) {
$term = implode(' ', $search);
} else {
$term = (string) $search;
}
$term = trim($term);
$pattern = $this->interpreter->interpret($term, $context->getContext());
foreach ($pattern->getTerms() as $searchTerm) {
$criteria->addQuery(
new ScoreQuery(
new EqualsFilter('product.searchKeywords.keyword', $searchTerm->getTerm()),
$searchTerm->getScore(),
'product.searchKeywords.ranking'
)
);
}
$criteria->addQuery(
new ScoreQuery(
new ContainsFilter('product.searchKeywords.keyword', $pattern->getOriginal()->getTerm()),
$pattern->getOriginal()->getScore(),
'product.searchKeywords.ranking'
)
);
if ($pattern->getBooleanClause() !== SearchPattern::BOOLEAN_CLAUSE_AND) {
$criteria->addFilter(new AndFilter([
new EqualsAnyFilter('product.searchKeywords.keyword', array_values($pattern->getAllTerms())),
new EqualsFilter('product.searchKeywords.languageId', $context->getContext()->getLanguageId()),
]));
}
}
if (($categoryFilter = $request->query->get('categoryFilter')) !== null) {
$categoryIds = explode('|', $categoryFilter);
$queries = [];
foreach ($categoryIds as $categoryId) {
$queries[] = new ContainsFilter('categoryTree', $categoryId);
}
$criteria->addFilter(new OrFilter($queries));
}
if ($dvdwEventId && $request->query->get('promotionFilter') !== null) {
$criteria = $this->addPromotionFilter($criteria, $dvdwEventId);
}
}
private function applyRandomPage(Request $request, Criteria $criteria, SalesChannelContext $context): void
{
$limit = $criteria->getLimit() ? $criteria->getLimit() : $this->systemConfigService->getInt('core.listing.productsPerPage');
if ($limit <= 0) {
$limit = 24;
}
$page = (int) $request->query->get('p');
if ($page <= 0) {
$page = $this->getRandomFromTotalPages($limit, $request, $context);
}
$criteria->setLimit($limit);
$criteria->setOffset($page > 0 ? $limit * ($page - 1) : 0);
}
private function getRandomFromTotalPages(int $limit, Request $request, SalesChannelContext $context): int
{
$total = $this->productCountService->getTotal($request, $context);
if ($total <= 0) {
$total = 1;
}
return rand(1, (int) ceil($total / $limit));
}
private function addAssociations(Criteria $criteria): void
{
$criteria->addAssociations([
'dvdwParticipations.orderLineItem.dvdwTicket',
'categories.media',
'categories.children'
]);
}
private function getDvdwEventId(SalesChannelContext $context): ?string
{
/** @var DvdwEventContextExtension|null $contextExtension */
$contextExtension = $context->getExtension(DvdwEventContextExtension::KEY);
if ($contextExtension === null) {
return null;
}
return $contextExtension->getEvent()->getId();
}
private function filterParticipations(Criteria $criteria, string $dvdwEventId): void
{
$criteria->getAssociation('dvdwParticipations')->addFilter(new EqualsFilter('dvdwEventId', $dvdwEventId));
}
private function addPromotionFilter(Criteria $criteria, string $dvdwEventId): Criteria
{
$criteria->addFilter(new NotFilter(NotFilter::CONNECTION_OR, [
new EqualsFilter('product.dvdwParticipations.dvdwPromotion.type', 'placeholder'),
new EqualsFilter('product.dvdwParticipations.dvdwPromotion.id', null)
]));
$criteria->addFilter(
new EqualsFilter('product.dvdwParticipations.dvdwEvent.id', $dvdwEventId)
);
return $criteria;
}
}