marks
marks

Reputation: 1391

How to add category-slugs to a product URL in Shopware 6?

By default, the Shopware 6 URLs for products are set like this:

{{ product.translated.name }}/{{ product.productNumber}}
=> /Product-Name/Number123

Category URLs look like this:

{% for part in category.seoBreadcrumb%}{{ part|lower }}/{% endfor %}
=> /parent-category/child-category

I would like to add the product's main category's path to the product URL like it was possible in SW5 with {sCategoryPath articleID=$sArticle.id}/{$sArticle.name}
=> /parent-category/child-category/product-name

Is there a way to achieve this in SW6?

Upvotes: 0

Views: 1378

Answers (2)

SmashingJummy
SmashingJummy

Reputation: 448

You can do this out-of-the-box. Tested in Shopware 6.4.15.0:

{% for part in product.mainCategory.breadcrumb|slice(1)%}{{ part }}/{% endfor %}{{ product.translated.name }}/{{ product.productNumber }}

Will result in:

enter image description here

Category structure:

enter image description here

Upvotes: 2

dneustadt
dneustadt

Reputation: 13161

You'll have to write a plugin to achieve this. With your plugin you can then decorate ProductPageSeoUrlRoute.

<service id="Foo\MyPlugin\ProductPageSeoUrlRouteDecorator" decorates="Shopware\Storefront\Framework\Seo\SeoUrlRoute\ProductPageSeoUrlRoute">
    <argument type="service" id="Foo\MyPlugin\ProductPageSeoUrlRouteDecorator.inner"/>
    <argument type="service" id="Shopware\Core\Content\Category\Service\CategoryBreadcrumbBuilder"/>

    <tag name="shopware.seo_url.route"/>
</service>

In your decorator you then enhance the criteria to fetch the main categories, use the injected CategoryBreadcrumbBuilder to generate the breadcrumb and enrich the data for the route generation with the breadcrumb array.

class ProductPageSeoUrlRouteDecorator implements SeoUrlRouteInterface
{
    private SeoUrlRouteInterface $decorated;

    private CategoryBreadcrumbBuilder $breadcrumbBuilder;

    public function __construct(SeoUrlRouteInterface $decorated, CategoryBreadcrumbBuilder $breadcrumbBuilder)
    {
        $this->decorated = $decorated;
        $this->breadcrumbBuilder = $breadcrumbBuilder;
    }

    public function getConfig(): SeoUrlRouteConfig
    {
        return $this->decorated->getConfig();
    }

    /**
     * @internal (flag:FEATURE_NEXT_13410) make $salesChannel parameter required
     */
    public function prepareCriteria(Criteria $criteria/*, SalesChannelEntity $salesChannel */): void
    {
        /** @var SalesChannelEntity|null $salesChannel */
        $salesChannel = \func_num_args() === 2 ? func_get_arg(1) : null;

        $this->decorated->prepareCriteria($criteria, $salesChannel);

        $criteria->addAssociation('mainCategories.category');
    }

    public function getMapping(Entity $product, ?SalesChannelEntity $salesChannel): SeoUrlMapping
    {
        if (!$product instanceof ProductEntity) {
            throw new \InvalidArgumentException('Expected ProductEntity');
        }

        $seoMapping = $this->decorated->getMapping($product, $salesChannel);
        $mainCategories = $product->getMainCategories();

        if (!$mainCategories) {
            return $seoMapping;
        }

        /** @var MainCategoryEntity|null $mainCategory */
        $mainCategory = $salesChannel ? $mainCategories->filterBySalesChannelId($salesChannel->getId())->first() : $mainCategories->first();

        if (!$mainCategory) {
            return $seoMapping;
        }

        $rootId = $this->detectRootId($mainCategory->getCategory(), $salesChannel);

        $breadcrumbs = $this->breadcrumbBuilder->build($mainCategory->getCategory(), $salesChannel, $rootId);

        $seoPathInfoContext = $seoMapping->getSeoPathInfoContext();
        $seoPathInfoContext['product']['seoBreadcrumb'] = $breadcrumbs;

        return new SeoUrlMapping(
            $seoMapping->getEntity(),
            $seoMapping->getInfoPathContext(),
            $seoPathInfoContext
        );
    }

    private function detectRootId(CategoryEntity $category, ?SalesChannelEntity $salesChannel): ?string
    {
        if (!$salesChannel) {
            return null;
        }
        $path = array_filter(explode('|', (string) $category->getPath()));

        $navigationId = $salesChannel->getNavigationCategoryId();
        if ($navigationId === $category->getId() || \in_array($navigationId, $path, true)) {
            return $navigationId;
        }

        $footerId = $salesChannel->getFooterCategoryId();
        if ($footerId === $category->getId() || \in_array($footerId, $path, true)) {
            return $footerId;
        }

        $serviceId = $salesChannel->getServiceCategoryId();
        if ($serviceId === $category->getId() || \in_array($serviceId, $path, true)) {
            return $serviceId;
        }

        return null;
    }
}

Finally you will be able to use the breadcrumb within the template for your product seo urls:

{% if product.seoBreadcrumb is defined %}{% for part in product.seoBreadcrumb%}{{ part|lower  }}/{% endfor %}{% endif %}{{ product.translated.name }}/{{ product.productNumber }}

Upvotes: 0

Related Questions