src/Core/Content/Category/Service/CategoryBreadcrumbBuilder.php line 73

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Category\Service;
  3. use Shopware\Core\Content\Category\CategoryDefinition;
  4. use Shopware\Core\Content\Category\CategoryEntity;
  5. use Shopware\Core\Content\Product\ProductEntity;
  6. use Shopware\Core\Content\Seo\MainCategory\MainCategoryEntity;
  7. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
  15. use Shopware\Core\Framework\Log\Package;
  16. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  17. use Shopware\Core\System\SalesChannel\SalesChannelEntity;
  18. #[Package('content')]
  19. class CategoryBreadcrumbBuilder
  20. {
  21.     /**
  22.      * @internal
  23.      */
  24.     public function __construct(private readonly EntityRepository $categoryRepository)
  25.     {
  26.     }
  27.     /**
  28.      * @return array<mixed>|null
  29.      */
  30.     public function build(CategoryEntity $category, ?SalesChannelEntity $salesChannel null, ?string $navigationCategoryId null): ?array
  31.     {
  32.         $categoryBreadcrumb $category->getPlainBreadcrumb();
  33.         // If the current SalesChannel is null ( which refers to the default template SalesChannel) or
  34.         // this category has no root, we return the full breadcrumb
  35.         if ($salesChannel === null && $navigationCategoryId === null) {
  36.             return $categoryBreadcrumb;
  37.         }
  38.         $entryPoints = [
  39.             $navigationCategoryId,
  40.         ];
  41.         if ($salesChannel !== null) {
  42.             $entryPoints[] = $salesChannel->getNavigationCategoryId();
  43.             $entryPoints[] = $salesChannel->getServiceCategoryId();
  44.             $entryPoints[] = $salesChannel->getFooterCategoryId();
  45.         }
  46.         $entryPoints array_filter($entryPoints);
  47.         $keys array_keys($categoryBreadcrumb);
  48.         foreach ($entryPoints as $entryPoint) {
  49.             // Check where this category is located in relation to the navigation entry point of the sales channel
  50.             $pos array_search($entryPoint$keystrue);
  51.             if ($pos !== false) {
  52.                 // Remove all breadcrumbs preceding the navigation category
  53.                 return \array_slice($categoryBreadcrumb$pos 1);
  54.             }
  55.         }
  56.         return $categoryBreadcrumb;
  57.     }
  58.     public function getProductSeoCategory(ProductEntity $productSalesChannelContext $context): ?CategoryEntity
  59.     {
  60.         $category $this->getMainCategory($product$context);
  61.         if ($category !== null) {
  62.             return $category;
  63.         }
  64.         $categoryIds $product->getCategoryIds() ?? [];
  65.         $productStreamIds $product->getStreamIds() ?? [];
  66.         if (empty($productStreamIds) && empty($categoryIds)) {
  67.             return null;
  68.         }
  69.         $criteria = new Criteria();
  70.         $criteria->setTitle('breadcrumb-builder');
  71.         $criteria->setLimit(1);
  72.         $criteria->addFilter(new EqualsFilter('active'true));
  73.         if (!empty($categoryIds)) {
  74.             $criteria->setIds($categoryIds);
  75.         } else {
  76.             $criteria->addFilter(new EqualsAnyFilter('productStream.id'$productStreamIds));
  77.             $criteria->addFilter(new EqualsFilter('productAssignmentType'CategoryDefinition::PRODUCT_ASSIGNMENT_TYPE_PRODUCT_STREAM));
  78.         }
  79.         $criteria->addFilter($this->getSalesChannelFilter($context));
  80.         $categories $this->categoryRepository->search($criteria$context->getContext());
  81.         if ($categories->count() > 0) {
  82.             return $categories->first();
  83.         }
  84.         return null;
  85.     }
  86.     private function getSalesChannelFilter(SalesChannelContext $context): MultiFilter
  87.     {
  88.         $ids array_filter([
  89.             $context->getSalesChannel()->getNavigationCategoryId(),
  90.             $context->getSalesChannel()->getServiceCategoryId(),
  91.             $context->getSalesChannel()->getFooterCategoryId(),
  92.         ]);
  93.         return new OrFilter(array_map(static fn (string $id) => new ContainsFilter('path''|' $id '|'), $ids));
  94.     }
  95.     private function getMainCategory(ProductEntity $productSalesChannelContext $context): ?CategoryEntity
  96.     {
  97.         $criteria = new Criteria();
  98.         $criteria->setLimit(1);
  99.         $criteria->setTitle('breadcrumb-builder::main-category');
  100.         if (($product->getMainCategories() === null || $product->getMainCategories()->count() <= 0) && $product->getParentId() !== null) {
  101.             $criteria->addFilter($this->getMainCategoryFilter($product->getParentId(), $context));
  102.         } else {
  103.             $criteria->addFilter($this->getMainCategoryFilter($product->getId(), $context));
  104.         }
  105.         $categories $this->categoryRepository->search($criteria$context->getContext())->getEntities();
  106.         if ($categories->count() <= 0) {
  107.             return null;
  108.         }
  109.         $firstCategory $categories->first();
  110.         $entity $firstCategory instanceof MainCategoryEntity $firstCategory->getCategory() : $firstCategory;
  111.         return $product->getCategoryIds() !== null && \in_array($entity->getId(), $product->getCategoryIds(), true) ? $entity null;
  112.     }
  113.     private function getMainCategoryFilter(string $productIdSalesChannelContext $context): AndFilter
  114.     {
  115.         return new AndFilter([
  116.             new EqualsFilter('mainCategories.productId'$productId),
  117.             new EqualsFilter('mainCategories.salesChannelId'$context->getSalesChannelId()),
  118.             $this->getSalesChannelFilter($context),
  119.         ]);
  120.     }
  121. }