|
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 |
|
|
|
|
|
|
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; |
|
|
|
|
|
|
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( |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
171
|
|
|
} |
|
172
|
|
|
$concatenatedStylesEvent = new ThemeCompilerConcatenatedStylesEvent($concatenatedStyles, $salesChannelId); |
|
173
|
|
|
$this->eventDispatcher->dispatch($concatenatedStylesEvent); |
|
174
|
|
|
$compiled = $this->compileStyles( |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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)); |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
583
|
|
|
} |
|
584
|
|
|
|
|
585
|
|
|
$concatenatedScriptsEvent = new ThemeCompilerConcatenatedScriptsEvent($concatenatedScripts, $salesChannelId); |
|
586
|
|
|
$this->eventDispatcher->dispatch($concatenatedScriptsEvent); |
|
587
|
|
|
|
|
588
|
|
|
return $concatenatedScriptsEvent->getConcatenatedScripts(); |
|
589
|
|
|
} |
|
590
|
|
|
} |
|
591
|
|
|
|
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.