Passed
Push — master ( 32e1ee...d8f908 )
by Christian
10:26 queued 15s
created

ThemeService::getTabLabel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 7
rs 10
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Storefront\Theme;
4
5
use Shopware\Core\Framework\Context;
6
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
7
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
8
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
9
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
10
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
11
use Shopware\Core\Framework\Uuid\Uuid;
12
use Shopware\Storefront\Theme\Exception\InvalidThemeConfigException;
13
use Shopware\Storefront\Theme\Exception\InvalidThemeException;
14
use Shopware\Storefront\Theme\StorefrontPluginConfiguration\StorefrontPluginConfiguration;
15
use Shopware\Storefront\Theme\StorefrontPluginConfiguration\StorefrontPluginConfigurationCollection;
16
17
class ThemeService
18
{
19
    /**
20
     * @var StorefrontPluginRegistryInterface
21
     */
22
    private $pluginRegistry;
23
24
    /**
25
     * @var EntityRepositoryInterface
26
     */
27
    private $themeRepository;
28
29
    /**
30
     * @var EntityRepositoryInterface
31
     */
32
    private $themeSalesChannelRepository;
33
34
    /**
35
     * @var EntityRepositoryInterface
36
     */
37
    private $mediaRepository;
38
39
    /**
40
     * @var ThemeCompilerInterface
41
     */
42
    private $themeCompiler;
43
44
    public function __construct(
45
        StorefrontPluginRegistryInterface $pluginRegistry,
46
        EntityRepositoryInterface $themeRepository,
47
        EntityRepositoryInterface $themeSalesChannelRepository,
48
        EntityRepositoryInterface $mediaRepository,
49
        ThemeCompilerInterface $themeCompiler
50
    ) {
51
        $this->pluginRegistry = $pluginRegistry;
52
        $this->themeRepository = $themeRepository;
53
        $this->themeSalesChannelRepository = $themeSalesChannelRepository;
54
        $this->mediaRepository = $mediaRepository;
55
        $this->themeCompiler = $themeCompiler;
56
    }
57
58
    public function compileTheme(
59
        string $salesChannelId,
60
        string $themeId,
61
        Context $context,
62
        ?StorefrontPluginConfigurationCollection $configurationCollection = null,
63
        bool $withAssets = true
64
    ): void {
65
        $themePluginConfiguration = $this->getPluginConfiguration($themeId, $context);
66
67
        $this->themeCompiler->compileTheme(
68
            $salesChannelId,
69
            $themeId,
70
            $themePluginConfiguration,
71
            $configurationCollection ?? $this->pluginRegistry->getConfigurations(),
72
            $withAssets
73
        );
74
    }
75
76
    public function updateTheme(string $themeId, ?array $config, ?string $parentThemeId, Context $context): void
77
    {
78
        $criteria = new Criteria([$themeId]);
79
        $criteria->addAssociation('salesChannels');
80
        $theme = $this->themeRepository->search($criteria, $context)->get($themeId);
81
82
        if (!$theme) {
83
            throw new InvalidThemeException($themeId);
84
        }
85
86
        $data = ['id' => $themeId];
87
        if ($config) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $config of type array 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...
88
            foreach ($config as $key => $value) {
89
                $data['configValues'][$key] = $value;
90
            }
91
        }
92
93
        if ($parentThemeId) {
94
            $data['parentThemeId'] = $parentThemeId;
95
        }
96
97
        if (array_key_exists('configValues', $data) && $theme->getConfigValues()) {
98
            $data['configValues'] = array_replace_recursive($theme->getConfigValues(), $data['configValues']);
99
        }
100
101
        $this->themeRepository->update([$data], $context);
102
103
        foreach ($theme->getSalesChannels() as $salesChannel) {
104
            $this->compileTheme($salesChannel->getId(), $themeId, $context, null, false);
105
        }
106
    }
107
108
    public function assignTheme(string $themeId, string $salesChannelId, Context $context): bool
109
    {
110
        $this->compileTheme($salesChannelId, $themeId, $context);
111
112
        $this->themeSalesChannelRepository->upsert([[
113
            'themeId' => $themeId,
114
            'salesChannelId' => $salesChannelId,
115
        ]], $context);
116
117
        return true;
118
    }
119
120
    public function resetTheme(string $themeId, Context $context): void
121
    {
122
        $criteria = new Criteria([$themeId]);
123
        $theme = $this->themeRepository->search($criteria, $context)->get($themeId);
124
125
        if (!$theme) {
126
            throw new InvalidThemeException($themeId);
127
        }
128
129
        $data = ['id' => $themeId];
130
        $data['configValues'] = null;
131
132
        $this->themeRepository->update([$data], $context);
133
    }
134
135
    /**
136
     * @throws InvalidThemeConfigException
137
     * @throws InvalidThemeException
138
     * @throws InconsistentCriteriaIdsException
139
     */
140
    public function getThemeConfiguration(string $themeId, bool $translate, Context $context): array
141
    {
142
        $criteria = new Criteria();
143
        $criteria->setTitle('theme-service::load-config');
144
145
        $criteria->addFilter(new MultiFilter(
146
            MultiFilter::CONNECTION_OR,
147
            [
148
                new EqualsFilter('technicalName', StorefrontPluginRegistry::BASE_THEME_NAME),
149
                new EqualsFilter('id', $themeId),
150
            ]
151
        ));
152
153
        $themes = $this->themeRepository->search($criteria, $context);
154
155
        $theme = $themes->get($themeId);
156
157
        /** @var ThemeEntity|null $theme */
158
        if (!$theme) {
159
            throw new InvalidThemeException($themeId);
160
        }
161
162
        /** @var ThemeEntity $baseTheme */
163
        $baseTheme = $themes->filter(function (ThemeEntity $theme) {
164
            return $theme->getTechnicalName() === StorefrontPluginRegistry::BASE_THEME_NAME;
165
        })->first();
166
167
        $baseThemeConfig = $this->mergeStaticConfig($baseTheme);
168
169
        $themeConfigFieldFactory = new ThemeConfigFieldFactory();
170
        $configFields = [];
171
172
        $configuredTheme = $this->mergeStaticConfig($theme);
173
        $themeConfig = array_replace_recursive($baseThemeConfig, $configuredTheme);
174
175
        foreach ($themeConfig['fields'] as $name => $item) {
176
            $configFields[$name] = $themeConfigFieldFactory->create($name, $item);
177
        }
178
179
        $configFields = json_decode((string) json_encode($configFields), true);
180
181
        $labels = array_replace_recursive($baseTheme->getLabels() ?? [], $theme->getLabels() ?? []);
182
        if ($translate && !empty($labels)) {
183
            $configFields = $this->translateLabels($configFields, $labels);
184
        }
185
186
        $helpTexts = array_replace_recursive($baseTheme->getHelpTexts() ?? [], $theme->getHelpTexts() ?? []);
187
        if ($translate && !empty($helpTexts)) {
188
            $configFields = $this->translateHelpTexts($configFields, $helpTexts);
189
        }
190
191
        $themeConfig['fields'] = $configFields;
192
193
        return $themeConfig;
194
    }
195
196
    public function getResolvedThemeConfiguration(string $themeId, Context $context): array
197
    {
198
        $config = $this->getThemeConfiguration($themeId, false, $context);
199
        $resolvedConfig = [];
200
        $mediaItems = [];
201
        if (!array_key_exists('fields', $config)) {
202
            return [];
203
        }
204
205
        foreach ($config['fields'] as $key => $data) {
206
            if ($data['type'] === 'media' && $data['value'] && Uuid::isValid($data['value'])) {
207
                $mediaItems[$data['value']][] = $key;
208
            }
209
            $resolvedConfig[$key] = $data['value'];
210
        }
211
212
        /** @var string[] $mediaIds */
213
        $mediaIds = array_keys($mediaItems);
214
        $criteria = new Criteria($mediaIds);
215
        $criteria->setTitle('theme-service::resolve-media');
216
        $result = $this->mediaRepository->search($criteria, $context);
217
218
        foreach ($result as $media) {
219
            if (!\array_key_exists($media->getId(), $mediaItems)) {
220
                continue;
221
            }
222
223
            foreach ($mediaItems[$media->getId()] as $key) {
224
                $resolvedConfig[$key] = $media->getUrl();
225
            }
226
        }
227
228
        return $resolvedConfig;
229
    }
230
231
    public function getThemeConfigurationStructuredFields(string $themeId, bool $translate, Context $context): array
232
    {
233
        $mergedConfig = $this->getThemeConfiguration($themeId, $translate, $context)['fields'];
234
235
        $translations = [];
236
        if ($translate) {
237
            $translations = $this->getTranslations($themeId, $context);
238
            $mergedConfig = $this->translateLabels($mergedConfig, $translations);
239
        }
240
241
        $outputStructure = [];
242
243
        foreach ($mergedConfig as $fieldName => $fieldConfig) {
244
            $tab = $this->getTab($fieldConfig);
245
            $tabLabel = $this->getTabLabel($tab, $translations);
246
            $block = $this->getBlock($fieldConfig);
247
            $blockLabel = $this->getBlockLabel($block, $translations);
248
            $section = $this->getSection($fieldConfig);
249
            $sectionLabel = $this->getSectionLabel($section, $translations);
250
251
            // set default tab
252
            $outputStructure['tabs']['default']['label'] = '';
253
254
            // set labels
255
            $outputStructure['tabs'][$tab]['label'] = $tabLabel;
256
            $outputStructure['tabs'][$tab]['blocks'][$block]['label'] = $blockLabel;
257
            $outputStructure['tabs'][$tab]['blocks'][$block]['sections'][$section]['label'] = $sectionLabel;
258
259
            // add fields to sections
260
            $outputStructure['tabs'][$tab]['blocks'][$block]['sections'][$section]['fields'][$fieldName] = [
261
                'label' => $fieldConfig['label'],
262
                'helpText' => $fieldConfig['helpText'] ?? null,
263
                'type' => $fieldConfig['type'],
264
                'custom' => $fieldConfig['custom'],
265
                'fullWidth' => $fieldConfig['fullWidth'],
266
            ];
267
        }
268
269
        return $outputStructure;
270
    }
271
272
    /**
273
     * @deprecated tag:v6.4.0 use getThemeConfigurationStructuredFields instead
274
     *
275
     * @throws InconsistentCriteriaIdsException
276
     * @throws InvalidThemeConfigException
277
     * @throws InvalidThemeException
278
     */
279
    public function getThemeConfigurationFields(string $themeId, bool $translate, Context $context): array
280
    {
281
        $mergedConfig = $this->getThemeConfiguration($themeId, $translate, $context)['fields'];
282
283
        $translations = [];
284
        if ($translate) {
285
            $translations = $this->getTranslations($themeId, $context);
286
            $mergedConfig = $this->translateLabels($mergedConfig, $translations);
287
        }
288
289
        $blocks = [];
290
        $noblocks = [
291
            'label' => $this->getBlockLabel('unordered', $translations),
292
            'sections' => [],
293
        ];
294
295
        foreach ($mergedConfig as $fieldName => $fieldConfig) {
296
            $section = $this->getSection($fieldConfig);
297
298
            if (!isset($fieldConfig['block'])) {
299
                $noblocks['sections'][$section] = [
300
                    'label' => $this->getSectionLabel($section, $translations),
301
                    $fieldName => [
302
                        'label' => $fieldConfig['label'],
303
                        'helpText' => $fieldConfig['helpText'] ?? null,
304
                        'type' => $fieldConfig['type'],
305
                        'custom' => $fieldConfig['custom'],
306
                    ],
307
                ];
308
            } elseif (!isset($blocks[$fieldConfig['block']])) {
309
                $blocks[$fieldConfig['block']] = [
310
                    'label' => $this->getBlockLabel($fieldConfig['block'], $translations),
311
                    'sections' => [
312
                        $section => [
313
                            'label' => $this->getSectionLabel($section, $translations),
314
                            $fieldName => [
315
                                'label' => $fieldConfig['label'],
316
                                'helpText' => $fieldConfig['helpText'] ?? null,
317
                                'type' => $fieldConfig['type'],
318
                                'custom' => $fieldConfig['custom'],
319
                            ],
320
                        ],
321
                    ],
322
                ];
323
            } elseif (isset($blocks[$fieldConfig['block']]['sections'][$section])) {
324
                $blocks[$fieldConfig['block']]['sections'][$section][$fieldName] = [
325
                    'label' => $fieldConfig['label'],
326
                    'helpText' => $fieldConfig['helpText'] ?? null,
327
                    'type' => $fieldConfig['type'],
328
                    'custom' => $fieldConfig['custom'],
329
                ];
330
            } else {
331
                $blocks[$fieldConfig['block']]['sections'][$section] = [
332
                    'label' => $this->getSectionLabel($section, $translations),
333
                    $fieldName => [
334
                        'label' => $fieldConfig['label'],
335
                        'helpText' => $fieldConfig['helpText'] ?? null,
336
                        'type' => $fieldConfig['type'],
337
                        'custom' => $fieldConfig['custom'],
338
                    ],
339
                ];
340
            }
341
        }
342
343
        $blocks['unordered'] = $noblocks;
344
345
        return $blocks;
346
    }
347
348
    private function loadCompileConfig(string $themeId, Context $context): array
349
    {
350
        $config = $this->loadRecursiveConfig($themeId, $context);
351
352
        $field = new ThemeConfigField();
353
354
        foreach ($config['fields'] as $name => $item) {
355
            $clone = clone $field;
356
            $clone->setName($name);
357
            $clone->assign($item);
358
            $config[$name] = $clone;
359
        }
360
361
        return json_decode((string) json_encode($config), true);
362
    }
363
364
    private function loadRecursiveConfig(string $themeId, Context $context): array
365
    {
366
        $criteria = new Criteria([$themeId]);
367
368
        $theme = $this->themeRepository
369
            ->search($criteria, $context)
370
            ->first();
371
372
        if (!$theme instanceof ThemeEntity) {
373
            throw new InvalidThemeException($themeId);
374
        }
375
376
        $config = $this->mergeStaticConfig($theme);
377
378
        $parentId = $theme->getParentThemeId();
379
        if ($parentId) {
380
            $parent = $this->loadRecursiveConfig($parentId, $context);
381
382
            return array_replace_recursive($parent, $config);
383
        }
384
385
        $criteria = new Criteria();
386
        $criteria->addFilter(new EqualsFilter('technicalName', StorefrontPluginRegistry::BASE_THEME_NAME));
387
388
        $theme = $this->themeRepository
389
            ->search($criteria, $context)
390
            ->first();
391
392
        if (!$theme instanceof ThemeEntity) {
393
            throw new InvalidThemeException(StorefrontPluginRegistry::BASE_THEME_NAME);
394
        }
395
396
        $base = $this->mergeStaticConfig($theme);
397
398
        return array_replace_recursive($base, $config);
399
    }
400
401
    private function getPluginConfiguration(string $themeId, Context $context): StorefrontPluginConfiguration
402
    {
403
        $pluginConfig = $this->loadConfigByName($themeId, $context);
404
405
        if (!$pluginConfig) {
406
            throw new InvalidThemeException($themeId);
407
        }
408
409
        $pluginConfig = clone $pluginConfig;
410
411
        $config = $this->loadCompileConfig($themeId, $context);
412
413
        $pluginConfig->setThemeConfig($config);
414
415
        return $pluginConfig;
416
    }
417
418
    private function loadConfigByName(string $themeId, Context $context): ?StorefrontPluginConfiguration
419
    {
420
        /** @var ThemeEntity|null $theme */
421
        $theme = $this->themeRepository
422
            ->search(new Criteria([$themeId]), $context)
423
            ->get($themeId);
424
425
        if ($theme === null) {
426
            return $this->pluginRegistry
427
                ->getConfigurations()
428
                ->getByTechnicalName(StorefrontPluginRegistry::BASE_THEME_NAME);
429
        }
430
431
        $pluginConfig = null;
432
        if ($theme->getTechnicalName() !== null) {
433
            $pluginConfig = $this->pluginRegistry
434
                ->getConfigurations()
435
                ->getByTechnicalName($theme->getTechnicalName());
436
        }
437
438
        if ($pluginConfig !== null) {
439
            return $pluginConfig;
440
        }
441
442
        if ($theme->getParentThemeId() !== null) {
443
            $criteria = new Criteria();
444
            $criteria->addFilter(new EqualsFilter('id', $theme->getParentThemeId()));
445
446
            /** @var ThemeEntity $parentTheme */
447
            $parentTheme = $this->themeRepository
448
                ->search($criteria, $context)
449
                ->first();
450
451
            return $this->pluginRegistry
452
                ->getConfigurations()
453
                ->getByTechnicalName($parentTheme->getTechnicalName());
0 ignored issues
show
Bug introduced by
It seems like $parentTheme->getTechnicalName() can also be of type null; however, parameter $name of Shopware\Storefront\Them...n::getByTechnicalName() does only seem to accept string, 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

453
                ->getByTechnicalName(/** @scrutinizer ignore-type */ $parentTheme->getTechnicalName());
Loading history...
454
        }
455
456
        return $this->pluginRegistry
457
            ->getConfigurations()
458
            ->getByTechnicalName(StorefrontPluginRegistry::BASE_THEME_NAME);
459
    }
460
461
    private function mergeStaticConfig(ThemeEntity $theme): array
462
    {
463
        $configuredTheme = [];
464
465
        $pluginConfig = null;
466
        if ($theme->getTechnicalName()) {
467
            $pluginConfig = $this->pluginRegistry->getConfigurations()->getByTechnicalName($theme->getTechnicalName());
468
        }
469
470
        if ($pluginConfig !== null) {
471
            $configuredTheme = $pluginConfig->getThemeConfig();
472
        }
473
474
        if ($theme !== null && $theme->getBaseConfig() !== null) {
475
            $configuredTheme = array_replace_recursive($configuredTheme, $theme->getBaseConfig());
476
        }
477
478
        if ($theme !== null && $theme->getConfigValues() !== null) {
479
            $configuredThemeFields = [];
480
            if (array_key_exists('fields', $configuredTheme)) {
481
                $configuredThemeFields = $configuredTheme['fields'];
482
            }
483
            $configuredTheme['fields'] = array_replace_recursive($configuredThemeFields, $theme->getConfigValues());
484
        }
485
486
        return $configuredTheme;
487
    }
488
489
    private function getTab($fieldConfig): string
490
    {
491
        $tab = 'default';
492
493
        if (isset($fieldConfig['tab'])) {
494
            $tab = $fieldConfig['tab'];
495
        }
496
497
        return $tab;
498
    }
499
500
    private function getBlock($fieldConfig): string
501
    {
502
        $block = 'default';
503
504
        if (isset($fieldConfig['block'])) {
505
            $block = $fieldConfig['block'];
506
        }
507
508
        return $block;
509
    }
510
511
    private function getSection($fieldConfig): string
512
    {
513
        $section = 'default';
514
515
        if (isset($fieldConfig['section'])) {
516
            $section = $fieldConfig['section'];
517
        }
518
519
        return $section;
520
    }
521
522
    private function getTabLabel(string $tabName, array $translations)
523
    {
524
        if ($tabName === 'default') {
525
            return '';
526
        }
527
528
        return $translations['tabs.' . $tabName] ?? $tabName;
529
    }
530
531
    private function getBlockLabel(string $blockName, array $translations)
532
    {
533
        if ($blockName === 'default') {
534
            return '';
535
        }
536
537
        return $translations['blocks.' . $blockName] ?? $blockName;
538
    }
539
540
    private function getSectionLabel(string $sectionName, array $translations)
541
    {
542
        if ($sectionName === 'default') {
543
            return '';
544
        }
545
546
        return $translations['sections.' . $sectionName] ?? $sectionName;
547
    }
548
549
    private function translateLabels(array $themeConfiguration, array $translations): array
550
    {
551
        foreach ($themeConfiguration as $key => &$value) {
552
            $value['label'] = $translations['fields.' . $key] ?? $key;
553
        }
554
555
        return $themeConfiguration;
556
    }
557
558
    private function translateHelpTexts(array $themeConfiguration, array $translations): array
559
    {
560
        foreach ($themeConfiguration as $key => &$value) {
561
            $value['helpText'] = $translations['fields.' . $key] ?? null;
562
        }
563
564
        return $themeConfiguration;
565
    }
566
567
    /**
568
     * @throws InconsistentCriteriaIdsException
569
     */
570
    private function getTranslations(string $themeId, Context $context): array
571
    {
572
        /** @var ThemeEntity $theme */
573
        $theme = $this->themeRepository->search(new Criteria([$themeId]), $context)->get($themeId);
574
        $translations = $theme->getLabels() ?: [];
575
        if ($theme->getParentThemeId() !== null) {
576
            $parentTheme = $this->themeRepository->search(new Criteria([$theme->getParentThemeId()]), $context)
577
                ->get($theme->getParentThemeId());
578
            $parentTranslations = $parentTheme->getLabels() ?: [];
579
            $translations = array_replace_recursive($parentTranslations, $translations);
580
        }
581
        $criteria = new Criteria();
582
        $criteria->addFilter(new EqualsFilter('technicalName', StorefrontPluginRegistry::BASE_THEME_NAME));
583
        $baseTheme = $this->themeRepository->search($criteria, $context)->first();
584
        $baseTranslations = $baseTheme->getLabels() ?: [];
585
        $translations = array_replace_recursive($baseTranslations, $translations);
586
587
        return $translations;
588
    }
589
}
590