Modifying sitemap entries

Overview

You might have had a look at our guide about adding custom sitemap entries, e.g. for a custom entity. However, you might not want to add new URLs, but rather modify already existing ones. This guide will cover modifying e.g. the product URLs for the sitemap.

Prerequisites

This guide is built upon the plugin base guide, like most guides.
Modifying the sitemap entries is done via decoration, so should know how that's done as well. Also, knowing how the URL providers work, like it's explained in our guide about adding custom sitemap entries, will come in handy.

Modifying the sitemap

There's two ways of actually modifying the sitemap entries, but both ways are done by decorating the respective UrlProvider, e.g. the Shopware\Core\Content\Sitemap\Provider\ProductUrlProvider for products.
Hence, let's start with creating the basic decorated class for the ProductUrlProvider. We'll call this class DecoratedProductUrlProvider:
DecoratedProductUrlProvider.php
services.xml
<plugin root>/src/Core/Content/Sitemap/Provider/DecoratedProductUrlProvider.php
1
<?php declare(strict_types=1);
2
3
namespace Swag\BasicExample\Core\Content\Sitemap\Provider;
4
5
use Doctrine\DBAL\Connection;
6
use Shopware\Core\Content\Sitemap\Provider\AbstractUrlProvider;
7
use Shopware\Core\Content\Sitemap\Struct\UrlResult;
8
use Shopware\Core\Framework\Uuid\Uuid;
9
use Shopware\Core\System\SalesChannel\SalesChannelContext;
10
11
class DecoratedProductUrlProvider extends AbstractUrlProvider
12
{
13
private AbstractUrlProvider $decoratedUrlProvider;
14
15
public function __construct(AbstractUrlProvider $abstractUrlProvider)
16
{
17
$this->decoratedUrlProvider = $abstractUrlProvider;
18
}
19
20
public function getDecorated(): AbstractUrlProvider
21
{
22
return $this->decoratedUrlProvider;
23
}
24
25
public function getName(): string
26
{
27
return $this->getDecorated()->getName();
28
}
29
30
public function getUrls(SalesChannelContext $context, int $limit, ?int $offset = null): UrlResult
31
{
32
return $this->getDecorated()->getUrls($context, $limit, $offset);
33
}
34
}
Copied!
<plugin root>/src/Resources/config/services.xml
1
<?xml version="1.0" ?>
2
<container xmlns="http://symfony.com/schema/dic/services"
3
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
5
6
<services>
7
<service id="Swag\BasicExample\Core\Content\Sitemap\Provider\DecoratedProductUrlProvider" decorates="Shopware\Core\Content\Sitemap\Provider\ProductUrlProvider">
8
<argument type="service" id="Swag\BasicExample\Core\Content\Sitemap\Provider\DecoratedProductUrlProvider.inner" />
9
</service>
10
</services>
11
</container>
Copied!
Now let's get on to the two possible ways and its benefits.

Adjusting the getUrls method

By adjusting the getUrls method, you can execute the parent's getUrls method and modify its return value, which is an instance of the UrlResult. On this instance, you can use the method getUrls to actually get the Url instances and make adjustments to them - or even remove them.
<plugin root>/src/Core/Content/Sitemap/Provider/DecoratedProductUrlProvider.php
1
public function getUrls(SalesChannelContext $context, int $limit, ?int $offset = null): UrlResult
2
{
3
$urlResult = $this->getDecorated()->getUrls($context, $limit, $offset);
4
$urls = $urlResult->getUrls();
5
6
/* Change $urls, e.g. removing entries or updating them by iterating over them. */
7
8
return new UrlResult($urls, $urlResult->getNextOffset());
9
}
Copied!
You could iterate over the $urls array and modify each entry - or even create a new array with less entries, if you want to fully remove some.
There is one main downside to this way: You don't have access to a lot of information about the entity itself, that was used for this Url instance. E.g. if you'd like to filter all products with a given name, you can't do that here, since the name itself isn't available. The only reliable information you have here, is the ID of the entity by using the method getIdentifier on the Url instance.
Also, it's not the best way in terms of performance to read all SEO URLs from the database, only to filter them afterwards.

Overriding the getSeoUrls method

The available SEO URLs are read in the protected method getSeoUrls of the AbstractUrlProvider. Since it's a protected method, you can override it and create a custom SQL in order to only read the data you really want.
For this you'll most likely want to copy the original method's code and paste it into your overridden method. You can then add new lines to the SQL statement in order to do the necessary filtering or customising.
<plugin root>/src/Core/Content/Sitemap/Provider/DecoratedProductUrlProvider.php
1
protected function getSeoUrls(array $ids, string $routeName, SalesChannelContext $context, Connection $connection): array
2
{
3
/* Make adjustments to this SQL */
4
$sql = 'SELECT LOWER(HEX(foreign_key)) as foreign_key, seo_path_info
5
FROM seo_url WHERE foreign_key IN (:ids)
6
AND `seo_url`.`route_name` =:routeName
7
AND `seo_url`.`is_canonical` = 1
8
AND `seo_url`.`is_deleted` = 0
9
AND `seo_url`.`language_id` =:languageId
10
AND (`seo_url`.`sales_channel_id` =:salesChannelId OR seo_url.sales_channel_id IS NULL)';
11
12
return $connection->fetchAll(
13
$sql,
14
[
15
'routeName' => $routeName,
16
'languageId' => Uuid::fromHexToBytes($context->getSalesChannel()->getLanguageId()),
17
'salesChannelId' => Uuid::fromHexToBytes($context->getSalesChannelId()),
18
'ids' => Uuid::fromHexToBytesList(array_values($ids)),
19
],
20
[
21
'ids' => Connection::PARAM_STR_ARRAY,
22
]
23
);
24
}
Copied!
Now you could adjust the SQL statement to fit your needs, e.g. by adding a JOIN to the respective entities' table.
However, there is a downside here as well: Overriding the method like this is not really update-compatible. If the original method is changed in a future update, those changes will not apply for your modification, hence you might not receive a performance update or a bugfix for those few lines of code.