Passed
Push — trunk ( a9c8a1...b40967 )
by Christian
12:25 queued 12s
created

ThemeService::assignTheme()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 4
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Storefront\Theme;
4
5
use Doctrine\DBAL\Connection;
6
use Shopware\Core\Framework\Context;
7
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
8
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
9
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
10
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
11
use Shopware\Core\Framework\Uuid\Uuid;
12
use Shopware\Storefront\Theme\ConfigLoader\AbstractConfigLoader;
13
use Shopware\Storefront\Theme\Event\ThemeAssignedEvent;
14
use Shopware\Storefront\Theme\Event\ThemeConfigChangedEvent;
15
use Shopware\Storefront\Theme\Event\ThemeConfigResetEvent;
16
use Shopware\Storefront\Theme\Exception\InvalidThemeConfigException;
17
use Shopware\Storefront\Theme\Exception\InvalidThemeException;
18
use Shopware\Storefront\Theme\StorefrontPluginConfiguration\StorefrontPluginConfigurationCollection;
19
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
20
21
class ThemeService
22
{
23
    private StorefrontPluginRegistryInterface $extensionRegistery;
24
25
    private EntityRepositoryInterface $themeRepository;
26
27
    private EntityRepositoryInterface $themeSalesChannelRepository;
28
29
    private ThemeCompilerInterface $themeCompiler;
30
31
    private EventDispatcherInterface $dispatcher;
32
33
    private AbstractConfigLoader $configLoader;
34
35
    private Connection $connection;
36
37
    /**
38
     * @internal
39
     */
40
    public function __construct(
41
        StorefrontPluginRegistryInterface $extensionRegistry,
42
        EntityRepositoryInterface $themeRepository,
43
        EntityRepositoryInterface $themeSalesChannelRepository,
44
        ThemeCompilerInterface $themeCompiler,
45
        EventDispatcherInterface $dispatcher,
46
        AbstractConfigLoader $configLoader,
47
        Connection $connection
48
    ) {
49
        $this->extensionRegistery = $extensionRegistry;
50
        $this->themeRepository = $themeRepository;
51
        $this->themeSalesChannelRepository = $themeSalesChannelRepository;
52
        $this->themeCompiler = $themeCompiler;
53
        $this->dispatcher = $dispatcher;
54
        $this->configLoader = $configLoader;
55
        $this->connection = $connection;
56
    }
57
58
    /**
59
     * Only compiles a single theme/saleschannel combination.
60
     * Use `compileThemeById` to compile all dependend saleschannels
61
     */
62
    public function compileTheme(
63
        string $salesChannelId,
64
        string $themeId,
65
        Context $context,
66
        ?StorefrontPluginConfigurationCollection $configurationCollection = null,
67
        bool $withAssets = true
68
    ): void {
69
        /** @phpstan-ignore-next-line  */
70
        $this->themeCompiler->compileTheme(
71
            $salesChannelId,
72
            $themeId,
73
            $this->configLoader->load($themeId, $context),
74
            $configurationCollection ?? $this->extensionRegistery->getConfigurations(),
75
            $withAssets,
76
            $context
0 ignored issues
show
Unused Code introduced by
The call to Shopware\Storefront\Them...terface::compileTheme() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

76
        $this->themeCompiler->/** @scrutinizer ignore-call */ 
77
                              compileTheme(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
77
        );
78
    }
79
80
    /**
81
     * Compiles all dependend saleschannel/Theme combinations
82
     *
83
     * @return array<int, string>
84
     */
85
    public function compileThemeById(
86
        string $themeId,
87
        Context $context,
88
        ?StorefrontPluginConfigurationCollection $configurationCollection = null,
89
        bool $withAssets = true
90
    ): array {
91
        $mappings = $this->getThemeDependencyMapping($themeId);
92
93
        $compiledThemeIds = [];
94
        /** @var ThemeSalesChannel $mapping */
95
        foreach ($mappings as $mapping) {
96
            /** @phpstan-ignore-next-line  */
97
            $this->themeCompiler->compileTheme(
98
                $mapping->getSalesChannelId(),
99
                $mapping->getThemeId(),
100
                $this->configLoader->load($mapping->getThemeId(), $context),
101
                $configurationCollection ?? $this->extensionRegistery->getConfigurations(),
102
                $withAssets,
103
                $context
0 ignored issues
show
Unused Code introduced by
The call to Shopware\Storefront\Them...terface::compileTheme() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

103
            $this->themeCompiler->/** @scrutinizer ignore-call */ 
104
                                  compileTheme(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
104
            );
105
106
            $compiledThemeIds[] = $mapping->getThemeId();
107
        }
108
109
        return $compiledThemeIds;
110
    }
111
112
    /**
113
     * @param array<string, mixed>|null $config
114
     */
115
    public function updateTheme(string $themeId, ?array $config, ?string $parentThemeId, Context $context): void
116
    {
117
        $criteria = new Criteria([$themeId]);
118
        $criteria->addAssociation('salesChannels');
119
        /** @var ThemeEntity|null $theme */
120
        $theme = $this->themeRepository->search($criteria, $context)->get($themeId);
121
122
        if (!$theme) {
123
            throw new InvalidThemeException($themeId);
124
        }
125
126
        $data = ['id' => $themeId];
127
        if ($config) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $config of type array<string,mixed> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
128
            foreach ($config as $key => $value) {
129
                $data['configValues'][$key] = $value;
130
            }
131
        }
132
133
        if ($parentThemeId) {
134
            $data['parentThemeId'] = $parentThemeId;
135
        }
136
137
        if (\array_key_exists('configValues', $data)) {
138
            $this->dispatcher->dispatch(new ThemeConfigChangedEvent($themeId, $data['configValues']));
139
        }
140
141
        if (\array_key_exists('configValues', $data) && $theme->getConfigValues()) {
142
            $submittedChanges = $data['configValues'];
143
            $currentConfig = $theme->getConfigValues();
144
            $data['configValues'] = array_replace_recursive($currentConfig, $data['configValues']);
145
146
            foreach ($submittedChanges as $key => $changes) {
147
                if (isset($changes['value']) && \is_array($changes['value']) && isset($currentConfig[(string) $key]) && \is_array($currentConfig[(string) $key])) {
148
                    $data['configValues'][$key]['value'] = array_unique($changes['value']);
149
                }
150
            }
151
        }
152
153
        $this->themeRepository->update([$data], $context);
154
155
        if ($theme->getSalesChannels() === null) {
156
            return;
157
        }
158
159
        $this->compileThemeById($themeId, $context, null, false);
160
    }
161
162
    public function assignTheme(string $themeId, string $salesChannelId, Context $context, bool $skipCompile = false): bool
163
    {
164
        if (!$skipCompile) {
165
            $this->compileTheme($salesChannelId, $themeId, $context);
166
        }
167
168
        $this->themeSalesChannelRepository->upsert([[
169
            'themeId' => $themeId,
170
            'salesChannelId' => $salesChannelId,
171
        ]], $context);
172
173
        $this->dispatcher->dispatch(new ThemeAssignedEvent($themeId, $salesChannelId));
174
175
        return true;
176
    }
177
178
    public function resetTheme(string $themeId, Context $context): void
179
    {
180
        $criteria = new Criteria([$themeId]);
181
        $theme = $this->themeRepository->search($criteria, $context)->get($themeId);
182
183
        if (!$theme) {
184
            throw new InvalidThemeException($themeId);
185
        }
186
187
        $data = ['id' => $themeId];
188
        $data['configValues'] = null;
189
190
        $this->dispatcher->dispatch(new ThemeConfigResetEvent($themeId));
191
192
        $this->themeRepository->update([$data], $context);
193
    }
194
195
    /**
196
     * @throws InvalidThemeConfigException
197
     * @throws InvalidThemeException
198
     * @throws InconsistentCriteriaIdsException
199
     *
200
     * @return array<string, mixed>
201
     */
202
    public function getThemeConfiguration(string $themeId, bool $translate, Context $context): array
203
    {
204
        $criteria = new Criteria();
205
        $criteria->setTitle('theme-service::load-config');
206
207
        $themes = $this->themeRepository->search($criteria, $context);
208
209
        $theme = $themes->get($themeId);
210
211
        /** @var ThemeEntity|null $theme */
212
        if (!$theme) {
213
            throw new InvalidThemeException($themeId);
214
        }
215
216
        /** @var ThemeEntity $baseTheme */
217
        $baseTheme = $themes->filter(function (ThemeEntity $themeEntry) {
218
            return $themeEntry->getTechnicalName() === StorefrontPluginRegistry::BASE_THEME_NAME;
219
        })->first();
220
221
        $baseThemeConfig = $this->mergeStaticConfig($baseTheme);
222
223
        $themeConfigFieldFactory = new ThemeConfigFieldFactory();
224
        $configFields = [];
225
        $labels = array_replace_recursive($baseTheme->getLabels() ?? [], $theme->getLabels() ?? []);
226
227
        if ($theme->getParentThemeId()) {
228
            $parentThemes = $this->getParentThemeIds($themes, $theme);
229
230
            foreach ($parentThemes as $parentTheme) {
231
                $configuredParentTheme = $this->mergeStaticConfig($parentTheme);
232
                $baseThemeConfig = array_replace_recursive($baseThemeConfig, $configuredParentTheme);
233
                $labels = array_replace_recursive($labels, $parentTheme->getLabels() ?? []);
234
            }
235
        }
236
237
        $configuredTheme = $this->mergeStaticConfig($theme);
238
        $themeConfig = array_replace_recursive($baseThemeConfig, $configuredTheme);
239
240
        foreach ($themeConfig['fields'] ?? [] as $name => &$item) {
0 ignored issues
show
Bug introduced by
The expression $themeConfig['fields'] ?? array() cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
241
            $configFields[$name] = $themeConfigFieldFactory->create($name, $item);
242
            if (
243
                isset($item['value'])
244
                && isset($configuredTheme['fields'])
245
                && \is_array($item['value'])
246
                && \array_key_exists($name, $configuredTheme['fields'])
247
            ) {
248
                $configFields[$name]->setValue($configuredTheme['fields'][$name]['value']);
249
            }
250
        }
251
252
        $configFields = json_decode((string) json_encode($configFields), true);
253
254
        if ($translate && !empty($labels)) {
255
            $configFields = $this->translateLabels($configFields, $labels);
256
        }
257
258
        $helpTexts = array_replace_recursive($baseTheme->getHelpTexts() ?? [], $theme->getHelpTexts() ?? []);
259
        if ($translate && !empty($helpTexts)) {
260
            $configFields = $this->translateHelpTexts($configFields, $helpTexts);
261
        }
262
263
        $themeConfig['fields'] = $configFields;
264
        $themeConfig['currentFields'] = [];
265
        $themeConfig['baseThemeFields'] = [];
266
267
        foreach ($themeConfig['fields'] as $field => $fieldItem) {
268
            $isInherited = $this->fieldIsInherited($field, $configuredTheme);
269
            $themeConfig['currentFields'][$field]['isInherited'] = $isInherited;
270
271
            if ($isInherited) {
272
                $themeConfig['currentFields'][$field]['value'] = null;
273
            } elseif (\array_key_exists('value', $fieldItem)) {
274
                $themeConfig['currentFields'][$field]['value'] = $fieldItem['value'];
275
            }
276
        }
277
278
        foreach ($themeConfig['fields'] as $field => $fieldItem) {
279
            $isInherited = $this->fieldIsInherited($field, $baseThemeConfig);
280
            $themeConfig['baseThemeFields'][$field]['isInherited'] = $isInherited;
281
282
            if ($isInherited) {
283
                $themeConfig['baseThemeFields'][$field]['value'] = null;
284
            } elseif (\array_key_exists('value', $fieldItem) && isset($baseThemeConfig['fields'][$field]['value'])) {
285
                $themeConfig['baseThemeFields'][$field]['value'] = $baseThemeConfig['fields'][$field]['value'];
286
            }
287
        }
288
289
        return $themeConfig;
290
    }
291
292
    /**
293
     * @return array<string, mixed>
294
     */
295
    public function getThemeConfigurationStructuredFields(string $themeId, bool $translate, Context $context): array
296
    {
297
        $mergedConfig = $this->getThemeConfiguration($themeId, $translate, $context)['fields'];
298
299
        $translations = [];
300
        if ($translate) {
301
            $translations = $this->getTranslations($themeId, $context);
302
            $mergedConfig = $this->translateLabels($mergedConfig, $translations);
303
        }
304
305
        $outputStructure = [];
306
307
        foreach ($mergedConfig as $fieldName => $fieldConfig) {
308
            $tab = $this->getTab($fieldConfig);
309
            $tabLabel = $this->getTabLabel($tab, $translations);
310
            $block = $this->getBlock($fieldConfig);
311
            $blockLabel = $this->getBlockLabel($block, $translations);
312
            $section = $this->getSection($fieldConfig);
313
            $sectionLabel = $this->getSectionLabel($section, $translations);
314
315
            // set default tab
316
            $outputStructure['tabs']['default']['label'] = '';
317
318
            // set labels
319
            $outputStructure['tabs'][$tab]['label'] = $tabLabel;
320
            $outputStructure['tabs'][$tab]['blocks'][$block]['label'] = $blockLabel;
321
            $outputStructure['tabs'][$tab]['blocks'][$block]['sections'][$section]['label'] = $sectionLabel;
322
323
            // add fields to sections
324
            $outputStructure['tabs'][$tab]['blocks'][$block]['sections'][$section]['fields'][$fieldName] = [
325
                'label' => $fieldConfig['label'],
326
                'helpText' => $fieldConfig['helpText'] ?? null,
327
                'type' => $fieldConfig['type'],
328
                'custom' => $fieldConfig['custom'],
329
                'fullWidth' => $fieldConfig['fullWidth'],
330
            ];
331
        }
332
333
        return $outputStructure;
334
    }
335
336
    public function getThemeDependencyMapping(string $themeId): ThemeSalesChannelCollection
337
    {
338
        $mappings = new ThemeSalesChannelCollection();
339
        $themeData = $this->connection->fetchAllAssociative(
340
            'SELECT LOWER(HEX(theme.id)) as id, LOWER(HEX(childTheme.id)) as dependentId,
341
            LOWER(HEX(tsc.sales_channel_id)) as saleschannelId,
342
            LOWER(HEX(dtsc.sales_channel_id)) as dsaleschannelId
343
            FROM theme
344
            LEFT JOIN theme as childTheme ON childTheme.parent_theme_id = theme.id
345
            LEFT JOIN theme_sales_channel as tsc ON theme.id = tsc.theme_id
346
            LEFT JOIN theme_sales_channel as dtsc ON childTheme.id = dtsc.theme_id
347
            WHERE theme.id = :id',
348
            ['id' => Uuid::fromHexToBytes($themeId)]
349
        );
350
351
        foreach ($themeData as $data) {
352
            if (isset($data['id']) && isset($data['saleschannelId']) && $data['id'] === $themeId) {
353
                $mappings->add(new ThemeSalesChannel($data['id'], $data['saleschannelId']));
354
            }
355
            if (isset($data['dependentId']) && isset($data['dsaleschannelId'])) {
356
                $mappings->add(new ThemeSalesChannel($data['dependentId'], $data['dsaleschannelId']));
357
            }
358
        }
359
360
        return $mappings;
361
    }
362
363
    /**
364
     * @param array<string, mixed> $parentThemes
365
     *
366
     * @return array<string, mixed>
367
     */
368
    private function getParentThemeIds(EntitySearchResult $themes, ThemeEntity $mainTheme, array $parentThemes = []): array
369
    {
370
        foreach ($this->getConfigInheritance($mainTheme) as $parentThemeName) {
371
            $parentTheme = $themes->filter(function (ThemeEntity $themeEntry) use ($parentThemeName) {
372
                return $themeEntry->getTechnicalName() === str_replace('@', '', $parentThemeName);
373
            })->first();
374
375
            if ($parentTheme instanceof ThemeEntity && !\array_key_exists($parentTheme->getId(), $parentThemes)) {
376
                $parentThemes[$parentTheme->getId()] = $parentTheme;
377
378
                if ($parentTheme->getParentThemeId()) {
379
                    $parentThemes = $this->getParentThemeIds($themes, $mainTheme, $parentThemes);
380
                }
381
            }
382
        }
383
384
        if ($mainTheme->getParentThemeId()) {
385
            $parentTheme = $themes->filter(function (ThemeEntity $themeEntry) use ($mainTheme) {
386
                return $themeEntry->getId() === $mainTheme->getParentThemeId();
387
            })->first();
388
389
            if ($parentTheme instanceof ThemeEntity && !\array_key_exists($parentTheme->getId(), $parentThemes)) {
390
                $parentThemes[$parentTheme->getId()] = $parentTheme;
391
                if ($parentTheme->getParentThemeId()) {
392
                    $parentThemes = $this->getParentThemeIds($themes, $mainTheme, $parentThemes);
393
                }
394
            }
395
        }
396
397
        return $parentThemes;
398
    }
399
400
    /**
401
     * @return array<string, mixed>
402
     */
403
    private function getConfigInheritance(ThemeEntity $mainTheme): array
404
    {
405
        if (\is_array($mainTheme->getBaseConfig())
406
            && \array_key_exists('configInheritance', $mainTheme->getBaseConfig())
407
            && \is_array($mainTheme->getBaseConfig()['configInheritance'])
408
            && !empty($mainTheme->getBaseConfig()['configInheritance'])
409
        ) {
410
            return $mainTheme->getBaseConfig()['configInheritance'];
411
        }
412
413
        return [];
414
    }
415
416
    /**
417
     * @return array<string, mixed>
418
     */
419
    private function mergeStaticConfig(ThemeEntity $theme): array
420
    {
421
        $configuredTheme = [];
422
423
        $pluginConfig = null;
424
        if ($theme->getTechnicalName()) {
425
            $pluginConfig = $this->extensionRegistery->getConfigurations()->getByTechnicalName($theme->getTechnicalName());
426
        }
427
428
        if ($pluginConfig !== null) {
429
            $configuredTheme = $pluginConfig->getThemeConfig();
430
        }
431
432
        if ($theme->getBaseConfig() !== null) {
433
            $configuredTheme = array_replace_recursive($configuredTheme ?? [], $theme->getBaseConfig());
0 ignored issues
show
Bug introduced by
It seems like $configuredTheme ?? array() can also be of type null; however, parameter $array of array_replace_recursive() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

433
            $configuredTheme = array_replace_recursive(/** @scrutinizer ignore-type */ $configuredTheme ?? [], $theme->getBaseConfig());
Loading history...
434
        }
435
436
        if ($theme->getConfigValues() !== null) {
437
            foreach ($theme->getConfigValues() as $fieldName => $configValue) {
438
                if (\array_key_exists('value', $configValue)) {
439
                    $configuredTheme['fields'][$fieldName]['value'] = $configValue['value'];
440
                }
441
            }
442
        }
443
444
        return $configuredTheme ?: [];
445
    }
446
447
    /**
448
     * @param array<string, mixed> $fieldConfig
449
     */
450
    private function getTab(array $fieldConfig): string
451
    {
452
        $tab = 'default';
453
454
        if (isset($fieldConfig['tab'])) {
455
            $tab = $fieldConfig['tab'];
456
        }
457
458
        return $tab;
459
    }
460
461
    /**
462
     * @param array<string, mixed> $fieldConfig
463
     */
464
    private function getBlock(array $fieldConfig): string
465
    {
466
        $block = 'default';
467
468
        if (isset($fieldConfig['block'])) {
469
            $block = $fieldConfig['block'];
470
        }
471
472
        return $block;
473
    }
474
475
    /**
476
     * @param array<string, mixed> $fieldConfig
477
     */
478
    private function getSection(array $fieldConfig): string
479
    {
480
        $section = 'default';
481
482
        if (isset($fieldConfig['section'])) {
483
            $section = $fieldConfig['section'];
484
        }
485
486
        return $section;
487
    }
488
489
    /**
490
     * @param array<string, mixed> $translations
491
     */
492
    private function getTabLabel(string $tabName, array $translations): string
493
    {
494
        if ($tabName === 'default') {
495
            return '';
496
        }
497
498
        return $translations['tabs.' . $tabName] ?? $tabName;
499
    }
500
501
    /**
502
     * @param array<string, mixed> $translations
503
     */
504
    private function getBlockLabel(string $blockName, array $translations): string
505
    {
506
        if ($blockName === 'default') {
507
            return '';
508
        }
509
510
        return $translations['blocks.' . $blockName] ?? $blockName;
511
    }
512
513
    /**
514
     * @param array<string, mixed> $translations
515
     */
516
    private function getSectionLabel(string $sectionName, array $translations): string
517
    {
518
        if ($sectionName === 'default') {
519
            return '';
520
        }
521
522
        return $translations['sections.' . $sectionName] ?? $sectionName;
523
    }
524
525
    /**
526
     * @param array<string, mixed> $themeConfiguration
527
     * @param array<string, mixed> $translations
528
     *
529
     * @return array<string, mixed>
530
     */
531
    private function translateLabels(array $themeConfiguration, array $translations): array
532
    {
533
        foreach ($themeConfiguration as $key => &$value) {
534
            $value['label'] = $translations['fields.' . $key] ?? $key;
535
        }
536
537
        return $themeConfiguration;
538
    }
539
540
    /**
541
     * @param array<string, mixed> $themeConfiguration
542
     * @param array<string, mixed> $translations
543
     *
544
     * @return array<string, mixed>
545
     */
546
    private function translateHelpTexts(array $themeConfiguration, array $translations): array
547
    {
548
        foreach ($themeConfiguration as $key => &$value) {
549
            $value['helpText'] = $translations['fields.' . $key] ?? null;
550
        }
551
552
        return $themeConfiguration;
553
    }
554
555
    /**
556
     * @return array<string, mixed>
557
     */
558
    private function getTranslations(string $themeId, Context $context): array
559
    {
560
        /** @var ThemeEntity $theme */
561
        $theme = $this->themeRepository->search(new Criteria([$themeId]), $context)->get($themeId);
562
        $translations = $theme->getLabels() ?: [];
563
564
        if ($theme->getParentThemeId()) {
565
            $criteria = new Criteria();
566
            $criteria->setTitle('theme-service::load-translations');
567
568
            $themes = $this->themeRepository->search($criteria, $context);
569
            $parentThemes = $this->getParentThemeIds($themes, $theme);
570
571
            foreach ($parentThemes as $parentTheme) {
572
                $parentTranslations = $parentTheme->getLabels() ?: [];
573
                $translations = array_replace_recursive($parentTranslations, $translations);
574
            }
575
        }
576
577
        return $translations;
578
    }
579
580
    /**
581
     * @param array<string, mixed> $configuration
582
     */
583
    private function fieldIsInherited(string $fieldName, array $configuration): bool
584
    {
585
        if (!isset($configuration['fields'])) {
586
            return true;
587
        }
588
589
        if (!\is_array($configuration['fields'])) {
590
            return true;
591
        }
592
593
        if (!\array_key_exists($fieldName, $configuration['fields'])) {
594
            return true;
595
        }
596
597
        return false;
598
    }
599
}
600