Passed
Push — trunk ( 45c115...02b544 )
by Christian
11:39 queued 14s
created

SeoUrlUpdater::loadUrlTemplate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 41
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
eloc 26
c 1
b 1
f 0
nc 4
nop 1
dl 0
loc 41
rs 9.504
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\Log\Package;
13
use Shopware\Core\System\Language\LanguageCollection;
14
use Shopware\Core\System\SalesChannel\SalesChannelCollection;
15
16
/**
17
 * This class can be used to regenerate the seo urls for a route and an offset at ids.
18
 */
19
#[Package('sales-channel')]
20
class SeoUrlUpdater
21
{
22
    /**
23
     * @internal
24
     *
25
     * @param EntityRepository<LanguageCollection> $languageRepository
26
     * @param EntityRepository<SalesChannelCollection> $salesChannelRepository
27
     */
28
    public function __construct(
29
        private readonly EntityRepository $languageRepository,
30
        private readonly SeoUrlRouteRegistry $seoUrlRouteRegistry,
31
        private readonly SeoUrlGenerator $seoUrlGenerator,
32
        private readonly SeoUrlPersister $seoUrlPersister,
33
        private readonly Connection $connection,
34
        private readonly EntityRepository $salesChannelRepository
35
    ) {
36
    }
37
38
    /**
39
     * @param list<string> $ids
40
     */
41
    public function update(string $routeName, array $ids): void
42
    {
43
        $templates = $routeName !== '' ? $this->loadUrlTemplate($routeName) : [];
44
        if (empty($templates)) {
45
            return;
46
        }
47
48
        $route = $this->seoUrlRouteRegistry->findByRouteName($routeName);
49
        if ($route === null) {
50
            throw new \RuntimeException(sprintf('Route by name %s not found', $routeName));
51
        }
52
53
        $context = Context::createDefaultContext();
54
55
        $languageChains = $this->fetchLanguageChains($context);
56
        $salesChannels = $this->salesChannelRepository->search(new Criteria(), $context)->getEntities();
57
58
        foreach ($templates as $config) {
59
            $template = $config['template'];
60
            $salesChannel = $salesChannels->get($config['salesChannelId']);
61
            if ($template === '' || $salesChannel === null) {
62
                continue;
63
            }
64
65
            $chain = $languageChains[$config['languageId']];
66
            $languageContext = new Context(new SystemSource(), [], Defaults::CURRENCY, $chain);
67
            $languageContext->setConsiderInheritance(true);
68
69
            // generate new seo urls
70
            $urls = $this->seoUrlGenerator->generate($ids, $template, $route, $languageContext, $salesChannel);
71
72
            // persist seo urls to storage
73
            $this->seoUrlPersister->updateSeoUrls($languageContext, $routeName, $ids, $urls, $salesChannel);
74
        }
75
    }
76
77
    /**
78
     * Loads the SEO url templates for the given $routeName for all combinations of languages and sales channels
79
     *
80
     * @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...
81
     *
82
     * @return list<array{salesChannelId: string, languageId: string, template: string}>
83
     */
84
    private function loadUrlTemplate(string $routeName): array
85
    {
86
        $domains = $this->connection->fetchAllAssociative(
87
            'SELECT DISTINCT
88
               LOWER(HEX(sales_channel.id)) as salesChannelId,
89
               LOWER(HEX(domains.language_id)) as languageId
90
             FROM sales_channel_domain as domains
91
             INNER JOIN sales_channel
92
               ON domains.sales_channel_id = sales_channel.id
93
               AND sales_channel.active = 1'
94
        );
95
96
        if ($domains === []) {
97
            return [];
98
        }
99
100
        $salesChannelTemplates = $this->connection->fetchAllKeyValue(
101
            'SELECT LOWER(HEX(sales_channel_id)) as sales_channel_id, template
102
             FROM seo_url_template
103
             WHERE route_name LIKE :route',
104
            ['route' => $routeName]
105
        );
106
107
        if (!\array_key_exists('', $salesChannelTemplates)) {
108
            throw new \RuntimeException('Default templates not configured');
109
        }
110
111
        $default = (string) $salesChannelTemplates[''];
112
113
        $result = [];
114
        foreach ($domains as $domain) {
115
            $salesChannelId = $domain['salesChannelId'];
116
117
            $result[] = [
118
                'salesChannelId' => $salesChannelId,
119
                'languageId' => $domain['languageId'],
120
                'template' => $salesChannelTemplates[$salesChannelId] ?? $default,
121
            ];
122
        }
123
124
        return $result;
125
    }
126
127
    /**
128
     * @return array<string, array<string>>
129
     */
130
    private function fetchLanguageChains(Context $context): array
131
    {
132
        $languages = $this->languageRepository->search(new Criteria(), $context)->getEntities()->getElements();
133
134
        $languageChains = [];
135
        foreach ($languages as $language) {
136
            $languageId = $language->getId();
137
            $languageChains[$languageId] = array_filter([
138
                $languageId,
139
                $language->getParentId(),
140
                Defaults::LANGUAGE_SYSTEM,
141
            ]);
142
        }
143
144
        return $languageChains;
145
    }
146
}
147