Passed
Push — trunk ( 37af04...c71cc1 )
by Christian
13:15 queued 12s
created

ThemeCompiler::concatenateStyles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 3
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Storefront\Theme;
4
5
use League\Flysystem\FilesystemInterface;
6
use Padaliyajay\PHPAutoprefixer\Autoprefixer;
7
use ScssPhp\ScssPhp\OutputStyle;
8
use Shopware\Core\Framework\Adapter\Cache\CacheInvalidator;
9
use Shopware\Core\Framework\Context;
10
use Shopware\Core\Framework\Feature;
11
use Shopware\Storefront\Event\ThemeCompilerConcatenatedScriptsEvent;
12
use Shopware\Storefront\Event\ThemeCompilerConcatenatedStylesEvent;
13
use Shopware\Storefront\Event\ThemeCompilerEnrichScssVariablesEvent;
14
use Shopware\Storefront\Theme\Event\ThemeCompilerEnrichScssVariablesEvent as ThemeCompilerEnrichScssVariablesEventNew;
15
use Shopware\Storefront\Theme\Event\ThemeCopyToLiveEvent;
16
use Shopware\Storefront\Theme\Exception\InvalidThemeException;
17
use Shopware\Storefront\Theme\Exception\ThemeCompileException;
18
use Shopware\Storefront\Theme\Exception\ThemeFileCopyException;
19
use Shopware\Storefront\Theme\StorefrontPluginConfiguration\FileCollection;
20
use Shopware\Storefront\Theme\StorefrontPluginConfiguration\StorefrontPluginConfiguration;
21
use Shopware\Storefront\Theme\StorefrontPluginConfiguration\StorefrontPluginConfigurationCollection;
22
use Symfony\Component\Asset\Package;
23
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
24
25
class ThemeCompiler implements ThemeCompilerInterface
0 ignored issues
show
Deprecated Code introduced by
The interface Shopware\Storefront\Theme\ThemeCompilerInterface has been deprecated. ( Ignorable by Annotation )

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

25
class ThemeCompiler implements /** @scrutinizer ignore-deprecated */ ThemeCompilerInterface

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.

Loading history...
26
{
27
    private FilesystemInterface $filesystem;
28
29
    private AbstractScssCompiler $scssCompiler;
30
31
    private ThemeFileResolver $themeFileResolver;
32
33
    private ThemeFileImporterInterface $themeFileImporter;
34
35
    private EventDispatcherInterface $eventDispatcher;
36
37
    private FilesystemInterface $tempFilesystem;
38
39
    /**
40
     * @var Package[]
41
     */
42
    private iterable $packages;
43
44
    private CacheInvalidator $logger;
45
46
    private AbstractThemePathBuilder $themePathBuilder;
47
48
    private bool $debug;
49
50
    private string $projectDir;
51
52
    /**
53
     * @internal
54
     *
55
     * @param Package[] $packages
56
     */
57
    public function __construct(
58
        FilesystemInterface $filesystem,
59
        FilesystemInterface $tempFilesystem,
60
        ThemeFileResolver $themeFileResolver,
61
        bool $debug,
62
        EventDispatcherInterface $eventDispatcher,
63
        ThemeFileImporterInterface $themeFileImporter,
64
        iterable $packages,
65
        CacheInvalidator $logger,
66
        AbstractThemePathBuilder $themePathBuilder,
67
        string $projectDir,
68
        AbstractScssCompiler $scssCompiler
69
    ) {
70
        $this->filesystem = $filesystem;
71
        $this->tempFilesystem = $tempFilesystem;
72
        $this->themeFileResolver = $themeFileResolver;
73
        $this->themeFileImporter = $themeFileImporter;
74
75
        $this->scssCompiler = $scssCompiler;
76
77
        $this->eventDispatcher = $eventDispatcher;
78
        $this->packages = $packages;
0 ignored issues
show
Documentation Bug introduced by
It seems like $packages of type iterable is incompatible with the declared type Symfony\Component\Asset\Package[] of property $packages.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
79
        $this->logger = $logger;
80
        $this->themePathBuilder = $themePathBuilder;
81
        $this->debug = $debug;
82
        $this->projectDir = $projectDir;
83
    }
84
85
    /**
86
     * @param Context $context - @deprecated tag:v6.5.0 parameter $context will be required in v6.5.0.0
87
     */
88
    public function compileTheme(
89
        string $salesChannelId,
90
        string $themeId,
91
        StorefrontPluginConfiguration $themeConfig,
92
        StorefrontPluginConfigurationCollection $configurationCollection,
93
        bool $withAssets = true/*,
94
        Context $context = null */
95
    ): void {
96
        if (\func_num_args() === 6) {
97
            $context = func_get_arg(5);
98
        } else {
99
            $context = null;
100
            Feature::triggerDeprecationOrThrow(
101
                'v6.5.0.0',
102
                sprintf('The parameter context in method compileTheme of class %s is mandatory.', static::class)
103
            );
104
        }
105
106
        /**
107
         * @feature-deprecated (flag:FEATURE_NEXT_15381) keep if branch remove complete following on feature release
108
         */
109
        if (Feature::isActive('FEATURE_NEXT_15381')) {
110
            $themePrefix = $this->themePathBuilder->assemblePath($salesChannelId, $themeId);
111
112
            $resolvedFiles = $this->themeFileResolver->resolveFiles($themeConfig, $configurationCollection, false);
113
114
            $styleFiles = $resolvedFiles[ThemeFileResolver::STYLE_FILES];
115
116
            $concatenatedStyles = $this->concatenateStyles(
117
                $styleFiles,
118
                $themeConfig,
119
                $salesChannelId
120
            );
121
122
            $compiled = $this->compileStyles(
0 ignored issues
show
Deprecated Code introduced by
The function Shopware\Storefront\Them...mpiler::compileStyles() has been deprecated: tag:v6.5.0 - $context will be mandatory ( Ignorable by Annotation )

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

122
            $compiled = /** @scrutinizer ignore-deprecated */ $this->compileStyles(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
123
                $concatenatedStyles,
124
                $themeConfig,
125
                $styleFiles->getResolveMappings(),
126
                $salesChannelId,
127
                $context
128
            );
129
130
            if ($this->filesystem->has($this->getTmpOutputPath($themePrefix))) {
131
                $this->filesystem->deleteDir($this->getTmpOutputPath($themePrefix));
132
            }
133
134
            if ($this->scssCompiler->filesHandledInternal() === false) {
135
                $this->filesystem->put($this->getTmpCssFilepath($themePrefix), $compiled);
136
            }
137
138
            $concatenatedScripts = $this->getConcatenatedScripts($resolvedFiles[ThemeFileResolver::SCRIPT_FILES], $themeConfig, $salesChannelId);
139
140
            $this->writeScriptFiles($this->getTmpOutputPath($themePrefix), $concatenatedScripts);
141
142
            // assets
143
            if ($withAssets) {
144
                $this->copyAssets($themeConfig, $configurationCollection, $this->getTmpOutputPath($themePrefix));
145
            }
146
147
            $this->copyToLiveLocation($themePrefix, $themeId);
148
149
            // Reset cache buster state for improving performance in getMetadata
150
            $this->logger->invalidate(['theme-metaData'], true);
151
            /**
152
             * @feature-deprecated (flag:FEATURE_NEXT_15381) remove return statement on feature release
153
             */
154
            return;
155
        }
156
157
        $themePrefix = $this->themePathBuilder->assemblePath($salesChannelId, $themeId);
158
        $outputPath = 'theme' . \DIRECTORY_SEPARATOR . $themePrefix;
159
160
        if ($withAssets && $this->filesystem->has($outputPath)) {
161
            $this->filesystem->deleteDir($outputPath);
162
        }
163
164
        $resolvedFiles = $this->themeFileResolver->resolveFiles($themeConfig, $configurationCollection, false);
165
        /** @var FileCollection $styleFiles */
166
        $styleFiles = $resolvedFiles[ThemeFileResolver::STYLE_FILES];
167
168
        $concatenatedStyles = '';
169
        foreach ($styleFiles as $file) {
170
            $concatenatedStyles .= $this->themeFileImporter->getConcatenableStylePath($file, $themeConfig);
0 ignored issues
show
Bug introduced by
$file of type array is incompatible with the type Shopware\Storefront\Them...luginConfiguration\File expected by parameter $file of Shopware\Storefront\Them...ConcatenableStylePath(). ( Ignorable by Annotation )

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

170
            $concatenatedStyles .= $this->themeFileImporter->getConcatenableStylePath(/** @scrutinizer ignore-type */ $file, $themeConfig);
Loading history...
171
        }
172
        $concatenatedStylesEvent = new ThemeCompilerConcatenatedStylesEvent($concatenatedStyles, $salesChannelId);
173
        $this->eventDispatcher->dispatch($concatenatedStylesEvent);
174
        $compiled = $this->compileStyles(
0 ignored issues
show
Deprecated Code introduced by
The function Shopware\Storefront\Them...mpiler::compileStyles() has been deprecated: tag:v6.5.0 - $context will be mandatory ( Ignorable by Annotation )

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

174
        $compiled = /** @scrutinizer ignore-deprecated */ $this->compileStyles(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
175
            $concatenatedStylesEvent->getConcatenatedStyles(),
176
            $themeConfig,
177
            $styleFiles->getResolveMappings(),
178
            $salesChannelId,
179
            $context
180
        );
181
        $cssFilepath = $outputPath . \DIRECTORY_SEPARATOR . 'css' . \DIRECTORY_SEPARATOR . 'all.css';
182
        $this->filesystem->put($cssFilepath, $compiled);
183
184
        /** @var FileCollection $scriptFiles */
185
        $scriptFiles = $resolvedFiles[ThemeFileResolver::SCRIPT_FILES];
186
        $concatenatedScripts = '';
187
        foreach ($scriptFiles as $file) {
188
            $concatenatedScripts .= $this->themeFileImporter->getConcatenableScriptPath($file, $themeConfig);
0 ignored issues
show
Bug introduced by
$file of type array is incompatible with the type Shopware\Storefront\Them...luginConfiguration\File expected by parameter $file of Shopware\Storefront\Them...oncatenableScriptPath(). ( Ignorable by Annotation )

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

188
            $concatenatedScripts .= $this->themeFileImporter->getConcatenableScriptPath(/** @scrutinizer ignore-type */ $file, $themeConfig);
Loading history...
189
        }
190
        $concatenatedScriptsEvent = new ThemeCompilerConcatenatedScriptsEvent($concatenatedScripts, $salesChannelId);
191
        $this->eventDispatcher->dispatch($concatenatedScriptsEvent);
192
193
        $scriptFilepath = $outputPath . \DIRECTORY_SEPARATOR . 'js' . \DIRECTORY_SEPARATOR . 'all.js';
194
        $this->filesystem->put($scriptFilepath, $concatenatedScriptsEvent->getConcatenatedScripts());
195
196
        // assets
197
        if ($withAssets) {
198
            $this->copyAssets($themeConfig, $configurationCollection, $outputPath);
199
        }
200
201
        // Reset cache buster state for improving performance in getMetadata
202
        $this->logger->invalidate(['theme-metaData'], true);
203
    }
204
205
    /**
206
     * @deprecated tag:v6.5.0 - Use AbstractThemePathBuilder instead
207
     */
208
    public static function getThemePrefix(string $salesChannelId, string $themeId): string
209
    {
210
        Feature::triggerDeprecationOrThrow(
211
            'v6.5.0.0',
212
            Feature::deprecatedMethodMessage(__CLASS__, __METHOD__, 'v6.5.0.0', 'AbstractThemePathBuilder')
213
        );
214
215
        return md5($themeId . $salesChannelId);
216
    }
217
218
    /**
219
     * @param array<string, string> $resolveMappings
220
     */
221
    public function getResolveImportPathsCallback(array $resolveMappings): \Closure
222
    {
223
        return function ($originalPath) use ($resolveMappings) {
224
            foreach ($resolveMappings as $resolve => $resolvePath) {
225
                $resolve = '~' . $resolve;
226
                if (mb_strpos($originalPath, $resolve) === 0) {
227
                    /**
228
                     * @deprecated tag:v6.5.0 - Alias `vendorBootstrap` will be removed.
229
                     *
230
                     * Alias is used to import Bootstrap v5 instead of Bootstrap v4 if feature flag v6.5.0.0 is active.
231
                     * Package `bootstrap5` will be renamed to `bootstrap` and replace Bootstrap v4.
232
                     * Remove this if completely.
233
                     */
234
                    if (mb_strpos($originalPath, '~vendorBootstrap/') === 0) {
235
                        $originalPath = Feature::isActive('v6.5.0.0')
236
                            ? str_replace('~vendorBootstrap/', '~vendor/bootstrap5/', $originalPath)
237
                            : str_replace('~vendorBootstrap/', '~vendor/bootstrap/', $originalPath);
238
                    }
239
240
                    $dirname = $resolvePath . \dirname(mb_substr($originalPath, mb_strlen($resolve)));
241
242
                    $filename = basename($originalPath);
243
                    $extension = $this->getImportFileExtension(pathinfo($filename, \PATHINFO_EXTENSION));
0 ignored issues
show
Bug introduced by
It seems like pathinfo($filename, PATHINFO_EXTENSION) can also be of type array; however, parameter $extension of Shopware\Storefront\Them...etImportFileExtension() 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

243
                    $extension = $this->getImportFileExtension(/** @scrutinizer ignore-type */ pathinfo($filename, \PATHINFO_EXTENSION));
Loading history...
244
                    $path = $dirname . \DIRECTORY_SEPARATOR . $filename . $extension;
245
                    if (file_exists($path)) {
246
                        return $path;
247
                    }
248
249
                    $path = $dirname . \DIRECTORY_SEPARATOR . '_' . $filename . $extension;
250
                    if (file_exists($path)) {
251
                        return $path;
252
                    }
253
                }
254
            }
255
256
            return null;
257
        };
258
    }
259
260
    private function copyAssets(
261
        StorefrontPluginConfiguration $configuration,
262
        StorefrontPluginConfigurationCollection $configurationCollection,
263
        string $outputPath
264
    ): void {
265
        if (!$configuration->getAssetPaths()) {
266
            return;
267
        }
268
269
        foreach ($configuration->getAssetPaths() as $asset) {
270
            if (mb_strpos($asset, '@') === 0) {
271
                $name = mb_substr($asset, 1);
272
                $config = $configurationCollection->getByTechnicalName($name);
273
                if (!$config) {
274
                    throw new InvalidThemeException($name);
275
                }
276
277
                $this->copyAssets($config, $configurationCollection, $outputPath);
278
279
                continue;
280
            }
281
282
            if ($asset[0] !== '/' && file_exists($this->projectDir . '/' . $asset)) {
283
                $asset = $this->projectDir . '/' . $asset;
284
            }
285
286
            $assets = $this->themeFileImporter->getCopyBatchInputsForAssets($asset, $outputPath, $configuration);
287
288
            // method copyBatch is provided by copyBatch filesystem plugin
289
            $this->filesystem->copyBatch(...$assets);
0 ignored issues
show
Bug introduced by
The method copyBatch() does not exist on League\Flysystem\FilesystemInterface. It seems like you code against a sub-type of League\Flysystem\FilesystemInterface such as League\Flysystem\MountManager or League\Flysystem\Filesystem. ( Ignorable by Annotation )

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

289
            $this->filesystem->/** @scrutinizer ignore-call */ 
290
                               copyBatch(...$assets);
Loading history...
290
        }
291
    }
292
293
    /**
294
     * @deprecated tag:v6.5.0 - $context will be mandatory
295
     *
296
     * @param array<string, string> $resolveMappings
297
     */
298
    private function compileStyles(
299
        string $concatenatedStyles,
300
        StorefrontPluginConfiguration $configuration,
301
        array $resolveMappings,
302
        string $salesChannelId,
303
        ?Context $context = null
304
    ): string {
305
        if ($context === null) {
306
            $context = Context::createDefaultContext();
307
        }
308
309
        $variables = $this->dumpVariables($configuration->getThemeConfig() ?? [], $salesChannelId, $context);
310
        $features = $this->getFeatureConfigScssMap();
311
312
        $resolveImportPath = $this->getResolveImportPathsCallback($resolveMappings);
313
314
        $importPaths = [];
315
316
        $cwd = \getcwd();
317
        if ($cwd !== false) {
318
            $importPaths[] = $cwd;
319
        }
320
321
        $importPaths[] = $resolveImportPath;
322
323
        $compilerConfig = new CompilerConfiguration(
324
            [
325
                'importPaths' => $importPaths,
326
                'outputStyle' => $this->debug ? OutputStyle::EXPANDED : OutputStyle::COMPRESSED,
327
            ]
328
        );
329
330
        try {
331
            $cssOutput = $this->scssCompiler->compileString(
332
                $compilerConfig,
333
                $features . $variables . $concatenatedStyles
334
            );
335
        } catch (\Throwable $exception) {
336
            throw new ThemeCompileException(
337
                $configuration->getTechnicalName(),
338
                $exception->getMessage()
339
            );
340
        }
341
        $autoPreFixer = new Autoprefixer($cssOutput);
342
        /** @var string|false $compiled */
343
        $compiled = $autoPreFixer->compile($this->debug);
344
        if ($compiled === false) {
345
            throw new ThemeCompileException(
346
                $configuration->getTechnicalName(),
347
                'CSS parser not initialized'
348
            );
349
        }
350
351
        return $compiled;
352
    }
353
354
    private function getImportFileExtension(string $extension): string
355
    {
356
        // If the import has no extension, it must be a SCSS module.
357
        if ($extension === '') {
358
            return '.scss';
359
        }
360
361
        // If the import has a .min extension, we assume it must be a compiled CSS file.
362
        if ($extension === 'min') {
363
            return '.css';
364
        }
365
366
        // If it has any other extension, we don't assume a specific extension.
367
        return '';
368
    }
369
370
    /**
371
     * Converts the feature config array to a SCSS map syntax.
372
     * This allows reading of the feature flag config inside SCSS via `map.get` function.
373
     *
374
     * Output example:
375
     * $sw-features: ("FEATURE_NEXT_1234": false, "FEATURE_NEXT_1235": true);
376
     *
377
     * @see https://sass-lang.com/documentation/values/maps
378
     */
379
    private function getFeatureConfigScssMap(): string
380
    {
381
        $allFeatures = Feature::getAll();
382
383
        $featuresScss = implode(',', array_map(function ($value, $key) {
384
            return sprintf('"%s": %s', $key, json_encode($value));
385
        }, $allFeatures, array_keys($allFeatures)));
386
387
        return sprintf('$sw-features: (%s);', $featuresScss);
388
    }
389
390
    /**
391
     * @param array<string, string> $variables
392
     *
393
     * @return array<string>
394
     */
395
    private function formatVariables(array $variables): array
396
    {
397
        return array_map(function ($value, $key) {
398
            return sprintf('$%s: %s;', $key, (!empty($value) ? $value : 0));
399
        }, $variables, array_keys($variables));
400
    }
401
402
    private function copyToLiveLocation(string $themePrefix, string $themeId): void
403
    {
404
        $backupPath = 'theme' . \DIRECTORY_SEPARATOR . 'backup' . \DIRECTORY_SEPARATOR . $themePrefix;
405
        $path = 'theme' . \DIRECTORY_SEPARATOR . $themePrefix;
406
407
        $themeCopyToLiveEvent = new ThemeCopyToLiveEvent($themeId, $path, $backupPath, $this->getTmpOutputPath($themePrefix));
408
        $this->eventDispatcher->dispatch($themeCopyToLiveEvent);
409
410
        $path = $themeCopyToLiveEvent->getPath();
411
        $backupPath = $themeCopyToLiveEvent->getBackupPath();
412
        $tmpPath = $themeCopyToLiveEvent->getTmpPath();
413
414
        if (
415
            !$this->filesystem->has($tmpPath)
416
            || ($this->filesystem->getMetaData($tmpPath) ?: ['type' => false])['type'] !== 'dir') {
417
            throw new ThemeFileCopyException(
418
                $themeId,
419
                sprintf('Compilation error. Compiled files not found in %s.', $tmpPath)
420
            );
421
        }
422
423
        // backup current theme files
424
        if ($this->filesystem->has($path)) {
425
            try {
426
                $this->filesystem->deleteDir($backupPath);
427
                $this->filesystem->rename($path, $backupPath);
428
            } catch (\Throwable $e) {
429
                throw new ThemeFileCopyException($themeId, $e->getMessage());
430
            }
431
        }
432
433
        // move new theme files to live dir. Move backup back if something failed.
434
        try {
435
            $this->filesystem->rename($tmpPath, $path);
436
        } catch (\Throwable $e) {
437
            if ($this->filesystem->has($path)) {
438
                try {
439
                    $this->filesystem->rename($path, $backupPath);
440
                } catch (\Throwable $innerE) {
441
                    throw new ThemeFileCopyException($themeId, $innerE->getMessage());
442
                }
443
            }
444
445
            throw new ThemeFileCopyException($themeId, $e->getMessage());
446
        }
447
    }
448
449
    /**
450
     * @param array{fields?: array{value: null|string|array<mixed>, scss?: bool, type: string}[]} $config
451
     */
452
    private function dumpVariables(array $config, string $salesChannelId, Context $context): string
453
    {
454
        $variables = [];
455
        foreach ($config['fields'] ?? [] as $key => $data) {
456
            if (!\is_array($data) || !$this->isDumpable($data)) {
457
                continue;
458
            }
459
460
            if (\in_array($data['type'], ['media', 'textarea'], true) && \is_string($data['value'])) {
461
                $variables[$key] = '\'' . $data['value'] . '\'';
462
            } elseif ($data['type'] === 'switch' || $data['type'] === 'checkbox') {
463
                $variables[$key] = (int) ($data['value']);
464
            } else {
465
                $variables[$key] = $data['value'];
466
            }
467
        }
468
469
        foreach ($this->packages as $key => $package) {
470
            $variables[sprintf('sw-asset-%s-url', $key)] = sprintf('\'%s\'', $package->getUrl(''));
471
        }
472
473
        Feature::callSilentIfInactive('v6.5.0.0', function () use (&$variables, $salesChannelId): void {
474
            /** @deprecated tag:v6.5.0 remove this event in 6.5.0 */
475
            $themeVariablesEvent = new ThemeCompilerEnrichScssVariablesEvent($variables, $salesChannelId);
0 ignored issues
show
Deprecated Code introduced by
The class Shopware\Storefront\Even...nrichScssVariablesEvent has been deprecated: tag:v6.5.0 - Will be removed. Use Shopware\Storefront\Theme\Event\ThemeCompilerEnrichScssVariablesEvent instead. ( Ignorable by Annotation )

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

475
            $themeVariablesEvent = /** @scrutinizer ignore-deprecated */ new ThemeCompilerEnrichScssVariablesEvent($variables, $salesChannelId);
Loading history...
476
            $this->eventDispatcher->dispatch($themeVariablesEvent);
477
            $variables = $themeVariablesEvent->getVariables();
478
        });
479
480
        /** @deprecated tag:v6.5.0 remove alias */
481
        $themeVariablesEvent = new ThemeCompilerEnrichScssVariablesEventNew(
482
            $variables,
483
            $salesChannelId,
484
            $context
485
        );
486
487
        $this->eventDispatcher->dispatch($themeVariablesEvent);
488
489
        $dump = str_replace(
490
            ['#class#', '#variables#'],
491
            [self::class, implode(\PHP_EOL, $this->formatVariables($themeVariablesEvent->getVariables()))],
492
            $this->getVariableDumpTemplate()
493
        );
494
495
        $this->tempFilesystem->put('theme-variables.scss', $dump);
496
497
        return $dump;
498
    }
499
500
    /**
501
     * @param array{value: string|array<mixed>|null, scss?: bool, type: string} $data
502
     */
503
    private function isDumpable(array $data): bool
504
    {
505
        if (!isset($data['value'])) {
506
            return false;
507
        }
508
509
        // Do not include fields which have the scss option set to false
510
        if (\array_key_exists('scss', $data) && $data['scss'] === false) {
511
            return false;
512
        }
513
514
        // Do not include fields which haa an array as value
515
        if (\is_array($data['value'])) {
516
            return false;
517
        }
518
519
        // value must not be an empty string since because an empty value can not be compiled
520
        if ($data['value'] === '') {
521
            return false;
522
        }
523
524
        // if no type is set just use the value and continue
525
        if (!isset($data['type'])) {
526
            return false;
527
        }
528
529
        return true;
530
    }
531
532
    private function getVariableDumpTemplate(): string
533
    {
534
        return <<<PHP_EOL
535
// ATTENTION! This file is auto generated by the #class# and should not be edited.
536
537
#variables#
538
539
PHP_EOL;
540
    }
541
542
    private function writeScriptFiles(
543
        string $tmpOutputPath,
544
        string $concatenatedScripts
545
    ): void {
546
        $tmpScriptFilepath = $tmpOutputPath . \DIRECTORY_SEPARATOR . 'js' . \DIRECTORY_SEPARATOR . 'all.js';
547
        $this->filesystem->put($tmpScriptFilepath, $concatenatedScripts);
548
    }
549
550
    private function getTmpOutputPath(string $themePrefix): string
551
    {
552
        return 'theme' . \DIRECTORY_SEPARATOR . 'temp' . \DIRECTORY_SEPARATOR . $themePrefix;
553
    }
554
555
    private function getTmpCssFilepath(string $themePrefix): string
556
    {
557
        return $this->getTmpOutputPath($themePrefix) . \DIRECTORY_SEPARATOR . 'css' . \DIRECTORY_SEPARATOR . 'all.css';
558
    }
559
560
    private function concatenateStyles(
561
        FileCollection $styleFiles,
562
        StorefrontPluginConfiguration $themeConfig,
563
        string $salesChannelId
564
    ): string {
565
        $concatenatedStyles = '';
566
        foreach ($styleFiles as $file) {
567
            $concatenatedStyles .= $this->themeFileImporter->getConcatenableStylePath($file, $themeConfig);
0 ignored issues
show
Bug introduced by
$file of type array is incompatible with the type Shopware\Storefront\Them...luginConfiguration\File expected by parameter $file of Shopware\Storefront\Them...ConcatenableStylePath(). ( Ignorable by Annotation )

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

567
            $concatenatedStyles .= $this->themeFileImporter->getConcatenableStylePath(/** @scrutinizer ignore-type */ $file, $themeConfig);
Loading history...
568
        }
569
        $concatenatedStylesEvent = new ThemeCompilerConcatenatedStylesEvent($concatenatedStyles, $salesChannelId);
570
        $this->eventDispatcher->dispatch($concatenatedStylesEvent);
571
572
        return $concatenatedStylesEvent->getConcatenatedStyles();
573
    }
574
575
    private function getConcatenatedScripts(
576
        FileCollection $scriptFiles,
577
        StorefrontPluginConfiguration $themeConfig,
578
        string $salesChannelId
579
    ): string {
580
        $concatenatedScripts = '';
581
        foreach ($scriptFiles as $file) {
582
            $concatenatedScripts .= $this->themeFileImporter->getConcatenableScriptPath($file, $themeConfig);
0 ignored issues
show
Bug introduced by
$file of type array is incompatible with the type Shopware\Storefront\Them...luginConfiguration\File expected by parameter $file of Shopware\Storefront\Them...oncatenableScriptPath(). ( Ignorable by Annotation )

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

582
            $concatenatedScripts .= $this->themeFileImporter->getConcatenableScriptPath(/** @scrutinizer ignore-type */ $file, $themeConfig);
Loading history...
583
        }
584
585
        $concatenatedScriptsEvent = new ThemeCompilerConcatenatedScriptsEvent($concatenatedScripts, $salesChannelId);
586
        $this->eventDispatcher->dispatch($concatenatedScriptsEvent);
587
588
        return $concatenatedScriptsEvent->getConcatenatedScripts();
589
    }
590
}
591