custom/static-plugins/CioItpThemeFilters/src/Subscriber/NavigationFilterSubscriber.php line 47

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace CioItpThemeFilters\Subscriber;
  4. use Shopware\Core\Content\Category\Event\NavigationLoadedEvent;
  5. use Shopware\Core\Content\Category\Tree\Tree;
  6. use Shopware\Core\Content\Category\CategoryEntity;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  10. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  11. use Shopware\Core\Framework\Context;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\HttpFoundation\RequestStack;
  14. class NavigationFilterSubscriber implements EventSubscriberInterface
  15. {
  16.     private RequestStack $requestStack;
  17.     private EntityRepositoryInterface $productRepository;
  18.     private EntityRepositoryInterface $cmsPageRepository;
  19.     private EntityRepositoryInterface $categoryRepository;
  20.     /** @var array<string, string|null> */
  21.     private array $cmsPageTypeCache = [];
  22.     public function __construct(
  23.         RequestStack $requestStack,
  24.         EntityRepositoryInterface $productRepository,
  25.         EntityRepositoryInterface $cmsPageRepository,
  26.         EntityRepositoryInterface $categoryRepository
  27.     ) {
  28.         $this->requestStack = $requestStack;
  29.         $this->productRepository = $productRepository;
  30.         $this->cmsPageRepository = $cmsPageRepository;
  31.         $this->categoryRepository = $categoryRepository;
  32.     }
  33.     public static function getSubscribedEvents(): array
  34.     {
  35.         return [
  36.             NavigationLoadedEvent::class => 'onNavigationLoaded'
  37.         ];
  38.     }
  39.     public function onNavigationLoaded(NavigationLoadedEvent $event): void
  40.     {
  41.         $request = $this->requestStack->getCurrentRequest();
  42.         if (!$request) {
  43.             return;
  44.         }
  45.         // Hole die Property-ID(s) aus den Query-Parametern (einzeln, Array oder "a|b|c")
  46.         $propertiesParam = $request->query->get('properties');
  47.         $propertyIds = [];
  48.         if (is_array($propertiesParam)) {
  49.             $propertyIds = array_values(array_filter($propertiesParam, static fn ($v) => (string)$v !== ''));
  50.         } elseif (is_string($propertiesParam) && $propertiesParam !== '') {
  51.             // Unterstützt sowohl Pipe- als auch Komma-separierte Werte
  52.             $normalized = str_replace(',', '|', $propertiesParam);
  53.             $propertyIds = array_filter(explode('|', $normalized), static fn ($v) => (string)$v !== '');
  54.         }
  55.         $propertyIds = array_values(array_unique($propertyIds));
  56.         if (empty($propertyIds)) {
  57.             return;
  58.         }
  59.         // Hole die Navigation
  60.         $navigation = $event->getNavigation();
  61.         // Filtere die Navigation
  62.         $this->filterNavigation($navigation, $propertyIds, $event->getContext());
  63.     }
  64.     private function filterNavigation(Tree $tree, array $propertyIds, Context $context): void
  65.     {
  66.         $items = $tree->getTree();
  67.         $filteredItems = [];
  68.         foreach ($items as $key => $item) {
  69.             $criteria = new Criteria();
  70.             $criteria->addFilter(new EqualsFilter('categoryTree', $item->getCategory()->getId()));
  71.             if (\count($propertyIds) > 1) {
  72.                 $criteria->addFilter(new EqualsAnyFilter('properties.id', $propertyIds));
  73.             } else {
  74.                 $criteria->addFilter(new EqualsFilter('properties.id', $propertyIds[0]));
  75.             }
  76.             $productCount = $this->productRepository->search($criteria, $context)->getTotal();
  77.             $keepAlways = $this->shouldKeepCategory($item->getCategory(), $context);
  78.             // Kinder immer prüfen, damit Folder mit gültigen (z. B. CMS-)Kindern sichtbar bleiben
  79.             $children = [];
  80.             if ($item->getChildren()) {
  81.                 $children = $this->filterTreeItems($item->getChildren(), $propertyIds, $context);
  82.                 if (!empty($children)) {
  83.                     $item->setChildren($children);
  84.                 }
  85.             }
  86.             if (!empty($children) || $productCount || $keepAlways) {
  87.                 $filteredItems[] = $item;
  88.             }
  89.         }
  90.         $tree->setTree($filteredItems);
  91.     }
  92.     private function filterTreeItems(array $items, array $propertyIds, Context $context): array
  93.     {
  94.         $filteredItems = [];
  95.         foreach ($items as $key => $item) {
  96.             $criteria = new Criteria();
  97.             $criteria->addFilter(new EqualsFilter('categoryTree', $item->getCategory()->getId()));
  98.             if (\count($propertyIds) > 1) {
  99.                 $criteria->addFilter(new EqualsAnyFilter('properties.id', $propertyIds));
  100.             } else {
  101.                 $criteria->addFilter(new EqualsFilter('properties.id', $propertyIds[0]));
  102.             }
  103.             $productCount = $this->productRepository->search($criteria, $context)->getTotal();
  104.             $keepAlways = $this->shouldKeepCategory($item->getCategory(), $context);
  105.             // Kinder immer prüfen, um Ordner mit gültigen Nachkommen zu behalten
  106.             $children = [];
  107.             if ($item->getChildren()) {
  108.                 $children = $this->filterTreeItems($item->getChildren(), $propertyIds, $context);
  109.                 if (!empty($children)) {
  110.                     $item->setChildren($children);
  111.                 }
  112.             }
  113.             if (!empty($children) || $productCount || $keepAlways) {
  114.                 $filteredItems[] = $item;
  115.             }
  116.         }
  117.         return $filteredItems;
  118.     }
  119.     private function shouldKeepCategory(CategoryEntity $category, Context $context): bool
  120.     {
  121.         $type = $category->getType();
  122.         $external = method_exists($category, 'getExternalLink') ? (string) $category->getExternalLink() : '';
  123.         $hasCmsRef = (string) ($category->getCmsPageId() ?? '') !== '' || (bool) $category->getCmsPage();
  124.         // Wenn wesentliche Infos fehlen, einmal aus der DB nachladen
  125.         if (($type === '') || (!$hasCmsRef && $external === '')) {
  126.             try {
  127.                 $criteria = new Criteria([$category->getId()]);
  128.                 $criteria->addAssociation('cmsPage');
  129.                 $loaded = $this->categoryRepository->search($criteria, $context)->first();
  130.                 if ($loaded) {
  131.                     $category = $loaded; // lokal für die Berechnung ersetzen
  132.                     $type = $loaded->getType();
  133.                     $external = method_exists($loaded, 'getExternalLink') ? (string) $loaded->getExternalLink() : $external;
  134.                     $hasCmsRef = (string) ($loaded->getCmsPageId() ?? '') !== '' || (bool) $loaded->getCmsPage();
  135.                 }
  136.             } catch (\Throwable $e) {
  137.                 // defensiv: Ignorieren und mit bisherigen Daten weiterarbeiten
  138.             }
  139.         }
  140.         // Externe Links immer behalten
  141.         if ($external !== '') {
  142.             return true;
  143.         }
  144.         if ($type === 'link') {
  145.             return true;
  146.         }
  147.         if ($type === 'page') {
  148.             // Alle Nicht-Listing CMS-Seiten behalten (z. B. Landingpage, Formulare, Content)
  149.             $pageType = null;
  150.             $cmsPage = $category->getCmsPage();
  151.             if ($cmsPage && method_exists($cmsPage, 'getType')) {
  152.                 $pageType = $cmsPage->getType();
  153.             } else {
  154.                 $cmsPageId = $category->getCmsPageId();
  155.                 if ($cmsPageId) {
  156.                     $pageType = $this->getCmsPageType($cmsPageId, $context);
  157.                 }
  158.             }
  159.             if ($pageType !== null) {
  160.                 return $pageType !== 'product_list';
  161.             }
  162.             // Fallback: Wenn eine CMS-Seite referenziert ist, aber der Typ nicht ermittelt werden konnte,
  163.             // Kategorie trotzdem behalten (betrifft u. a. Form-/FAQ-Seiten).
  164.             if ($category->getCmsPageId()) {
  165.                 return true;
  166.             }
  167.         }
  168.         return false;
  169.     }
  170.     private function getCmsPageType(string $cmsPageId, Context $context): ?string
  171.     {
  172.         if (isset($this->cmsPageTypeCache[$cmsPageId])) {
  173.             return $this->cmsPageTypeCache[$cmsPageId];
  174.         }
  175.         $criteria = new Criteria([$cmsPageId]);
  176.         $criteria->setLimit(1);
  177.         $page = $this->cmsPageRepository->search($criteria, $context)->first();
  178.         $type = $page ? $page->getType() : null;
  179.         $this->cmsPageTypeCache[$cmsPageId] = $type;
  180.         return $type;
  181.     }
  182. }