Reputation: 1391
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
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:
Category structure:
Upvotes: 2
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