SeoUrlUpdater   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 139
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 57
dl 0
loc 139
rs 10
c 1
b 1
f 0
wmc 16

4 Methods

Rating   Name   Duplication   Size   Complexity  
B update() 0 40 8
A fetchLanguageChains() 0 15 2
A loadUrlTemplate() 0 47 5
A __construct() 0 8 1
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Content\Seo;
4
5
use Doctrine\DBAL\Connection;
6
use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlRouteRegistry;
7
use Shopware\Core\Defaults;
8
use Shopware\Core\Framework\Api\Context\SystemSource;
9
use Shopware\Core\Framework\Context;
10
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
11
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
12
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
13
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NandFilter;
14
use Shopware\Core\Framework\Feature;
15
use Shopware\Core\Framework\Log\Package;
16
use Shopware\Core\Framework\Uuid\Uuid;
17
use Shopware\Core\System\Language\LanguageCollection;
18
use Shopware\Core\System\SalesChannel\SalesChannelCollection;
19
20
/**
21
 * This class can be used to regenerate the seo urls for a route and an offset at ids.
22
 */
23
#[Package('buyers-experience')]
24
class SeoUrlUpdater
25
{
26
    /**
27
     * @internal
28
     *
29
     * @param EntityRepository<LanguageCollection> $languageRepository
30
     * @param EntityRepository<SalesChannelCollection> $salesChannelRepository
31
     */
32
    public function __construct(
33
        private readonly EntityRepository $languageRepository,
34
        private readonly SeoUrlRouteRegistry $seoUrlRouteRegistry,
35
        private readonly SeoUrlGenerator $seoUrlGenerator,
36
        private readonly SeoUrlPersister $seoUrlPersister,
37
        private readonly Connection $connection,
38
        private readonly EntityRepository $salesChannelRepository
39
    ) {
40
    }
41
42
    /**
43
     * @param list<string> $ids
44
     */
45
    public function update(string $routeName, array $ids): void
46
    {
47
        $templates = $routeName !== '' ? $this->loadUrlTemplate($routeName) : [];
48
        if (empty($templates)) {
49
            return;
50
        }
51
52
        $route = $this->seoUrlRouteRegistry->findByRouteName($routeName);
53
        if ($route === null) {
54
            throw new \RuntimeException(sprintf('Route by name %s not found', $routeName));
55
        }
56
57
        $context = Context::createDefaultContext();
58
59
        $languageChains = $this->fetchLanguageChains($context);
60
61
        $criteria = new Criteria();
62
63
        if (Feature::isActive('v6.6.0.0')) {
64
            $criteria->addFilter(new NandFilter([new EqualsFilter('typeId', Defaults::SALES_CHANNEL_TYPE_API)]));
65
        }
66
67
        $salesChannels = $this->salesChannelRepository->search($criteria, $context)->getEntities();
68
69
        foreach ($templates as $config) {
70
            $template = $config['template'];
71
            $salesChannel = $salesChannels->get($config['salesChannelId']);
72
            if ($template === '' || $salesChannel === null) {
73
                continue;
74
            }
75
76
            $chain = $languageChains[$config['languageId']];
77
            $languageContext = new Context(new SystemSource(), [], Defaults::CURRENCY, $chain);
78
            $languageContext->setConsiderInheritance(true);
79
80
            // generate new seo urls
81
            $urls = $this->seoUrlGenerator->generate($ids, $template, $route, $languageContext, $salesChannel);
82
83
            // persist seo urls to storage
84
            $this->seoUrlPersister->updateSeoUrls($languageContext, $routeName, $ids, $urls, $salesChannel);
85
        }
86
    }
87
88
    /**
89
     * Loads the SEO url templates for the given $routeName for all combinations of languages and sales channels
90
     *
91
     * @param non-empty-string $routeName
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
92
     *
93
     * @return list<array{salesChannelId: string, languageId: string, template: string}>
94
     */
95
    private function loadUrlTemplate(string $routeName): array
96
    {
97
        $query = 'SELECT DISTINCT
98
               LOWER(HEX(sales_channel.id)) as salesChannelId,
99
               LOWER(HEX(domains.language_id)) as languageId
100
             FROM sales_channel_domain as domains
101
             INNER JOIN sales_channel
102
               ON domains.sales_channel_id = sales_channel.id
103
               AND sales_channel.active = 1';
104
        $parameters = [];
105
106
        if (Feature::isActive('v6.6.0.0')) {
107
            $query .= ' AND sales_channel.type_id != :apiTypeId';
108
            $parameters['apiTypeId'] = Uuid::fromHexToBytes(Defaults::SALES_CHANNEL_TYPE_API);
109
        }
110
111
        $domains = $this->connection->fetchAllAssociative($query, $parameters);
112
113
        if ($domains === []) {
114
            return [];
115
        }
116
117
        $salesChannelTemplates = $this->connection->fetchAllKeyValue(
118
            'SELECT LOWER(HEX(sales_channel_id)) as sales_channel_id, template
119
             FROM seo_url_template
120
             WHERE route_name LIKE :route',
121
            ['route' => $routeName]
122
        );
123
124
        if (!\array_key_exists('', $salesChannelTemplates)) {
125
            throw new \RuntimeException('Default templates not configured');
126
        }
127
128
        $default = (string) $salesChannelTemplates[''];
129
130
        $result = [];
131
        foreach ($domains as $domain) {
132
            $salesChannelId = $domain['salesChannelId'];
133
134
            $result[] = [
135
                'salesChannelId' => $salesChannelId,
136
                'languageId' => $domain['languageId'],
137
                'template' => $salesChannelTemplates[$salesChannelId] ?? $default,
138
            ];
139
        }
140
141
        return $result;
142
    }
143
144
    /**
145
     * @return array<string, array<string>>
146
     */
147
    private function fetchLanguageChains(Context $context): array
148
    {
149
        $languages = $this->languageRepository->search(new Criteria(), $context)->getEntities()->getElements();
150
151
        $languageChains = [];
152
        foreach ($languages as $language) {
153
            $languageId = $language->getId();
154
            $languageChains[$languageId] = array_filter([
155
                $languageId,
156
                $language->getParentId(),
157
                Defaults::LANGUAGE_SYSTEM,
158
            ]);
159
        }
160
161
        return $languageChains;
162
    }
163
}
164