|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
/* |
|
6
|
|
|
* This file is part of Cecil. |
|
7
|
|
|
* |
|
8
|
|
|
* Copyright (c) Arnaud Ligny <[email protected]> |
|
9
|
|
|
* |
|
10
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
11
|
|
|
* file that was distributed with this source code. |
|
12
|
|
|
*/ |
|
13
|
|
|
|
|
14
|
|
|
namespace Cecil\Assets; |
|
15
|
|
|
|
|
16
|
|
|
use Cecil\Assets\Image\Optimizer; |
|
17
|
|
|
use Cecil\Builder; |
|
18
|
|
|
use Cecil\Collection\Page\Page; |
|
19
|
|
|
use Cecil\Config; |
|
20
|
|
|
use Cecil\Exception\ConfigException; |
|
21
|
|
|
use Cecil\Exception\RuntimeException; |
|
22
|
|
|
use Cecil\Url; |
|
23
|
|
|
use Cecil\Util; |
|
24
|
|
|
use MatthiasMullie\Minify; |
|
25
|
|
|
use ScssPhp\ScssPhp\Compiler; |
|
26
|
|
|
use ScssPhp\ScssPhp\OutputStyle; |
|
27
|
|
|
use wapmorgan\Mp3Info\Mp3Info; |
|
28
|
|
|
|
|
29
|
|
|
class Asset implements \ArrayAccess |
|
30
|
|
|
{ |
|
31
|
|
|
/** @var Builder */ |
|
32
|
|
|
protected $builder; |
|
33
|
|
|
|
|
34
|
|
|
/** @var Config */ |
|
35
|
|
|
protected $config; |
|
36
|
|
|
|
|
37
|
|
|
/** @var array */ |
|
38
|
|
|
protected $data = []; |
|
39
|
|
|
|
|
40
|
|
|
/** @var bool */ |
|
41
|
|
|
protected $fingerprinted = false; |
|
42
|
|
|
|
|
43
|
|
|
/** @var bool */ |
|
44
|
|
|
protected $compiled = false; |
|
45
|
|
|
|
|
46
|
|
|
/** @var bool */ |
|
47
|
|
|
protected $minified = false; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* Creates an Asset from a file path, an array of files path or an URL. |
|
51
|
|
|
* Options: |
|
52
|
|
|
* [ |
|
53
|
|
|
* 'fingerprint' => <bool>, |
|
54
|
|
|
* 'minify' => <bool>, |
|
55
|
|
|
* 'optimize' => <bool>, |
|
56
|
|
|
* 'filename' => <string>, |
|
57
|
|
|
* 'ignore_missing' => <bool>, |
|
58
|
|
|
* 'fallback' => <string>, |
|
59
|
|
|
* 'leading_slash' => <bool> |
|
60
|
|
|
* ] |
|
61
|
1 |
|
* |
|
62
|
|
|
* @param Builder $builder |
|
63
|
1 |
|
* @param string|array $paths |
|
64
|
1 |
|
* @param array|null $options |
|
65
|
1 |
|
* |
|
66
|
1 |
|
* @throws RuntimeException |
|
67
|
1 |
|
*/ |
|
68
|
|
|
public function __construct(Builder $builder, string|array $paths, array|null $options = null) |
|
69
|
|
|
{ |
|
70
|
1 |
|
$this->builder = $builder; |
|
71
|
|
|
$this->config = $builder->getConfig(); |
|
72
|
|
|
$paths = \is_array($paths) ? $paths : [$paths]; |
|
73
|
1 |
|
// checks path(s) |
|
74
|
|
|
array_walk($paths, function ($path) { |
|
75
|
|
|
// must be a string |
|
76
|
1 |
|
if (!\is_string($path)) { |
|
77
|
1 |
|
throw new RuntimeException(\sprintf('The path of an asset must be a string ("%s" given).', \gettype($path))); |
|
78
|
1 |
|
} |
|
79
|
1 |
|
// can't be empty |
|
80
|
1 |
|
if (empty($path)) { |
|
81
|
1 |
|
throw new RuntimeException('The path of an asset can\'t be empty.'); |
|
82
|
1 |
|
} |
|
83
|
1 |
|
// can't be relative |
|
84
|
1 |
|
if (substr($path, 0, 2) == '..') { |
|
85
|
1 |
|
throw new RuntimeException(\sprintf('The path of asset "%s" is wrong: it must be directly relative to `assets` or `static` directory, or a remote URL.', $path)); |
|
86
|
1 |
|
} |
|
87
|
1 |
|
}); |
|
88
|
1 |
|
$this->data = [ |
|
89
|
1 |
|
'file' => '', // absolute file path |
|
90
|
1 |
|
'files' => [], // array of absolute files path |
|
91
|
1 |
|
'path' => '', // public path |
|
92
|
1 |
|
'_path' => '', // public path before any modification |
|
93
|
|
|
'url' => null, // URL if it's a remote file |
|
94
|
|
|
'missing' => false, // if file not found but missing allowed: 'missing' is true |
|
95
|
1 |
|
'ext' => '', // file extension |
|
96
|
1 |
|
'type' => '', // file type (e.g.: image, audio, video, etc.) |
|
97
|
1 |
|
'subtype' => '', // file media type (e.g.: image/png, audio/mp3, etc.) |
|
98
|
1 |
|
'size' => 0, // file size (in bytes) |
|
99
|
1 |
|
'width' => 0, // image width (in pixels) |
|
100
|
1 |
|
'height' => 0, // image height (in pixels) |
|
101
|
1 |
|
'exif' => [], // image exif data |
|
102
|
1 |
|
'content' => '', // file content |
|
103
|
|
|
]; |
|
104
|
|
|
|
|
105
|
1 |
|
// handles options |
|
106
|
1 |
|
$options = array_merge( |
|
107
|
1 |
|
[ |
|
108
|
1 |
|
'fingerprint' => $this->config->isEnabled('assets.fingerprint'), |
|
109
|
1 |
|
'minify' => $this->config->isEnabled('assets.minify'), |
|
110
|
1 |
|
'optimize' => $this->config->isEnabled('assets.images.optimize'), |
|
111
|
|
|
'filename' => '', |
|
112
|
1 |
|
'ignore_missing' => false, |
|
113
|
|
|
'fallback' => null, |
|
114
|
1 |
|
'leading_slash' => true, |
|
115
|
1 |
|
], |
|
116
|
|
|
\is_array($options) ? $options : [] |
|
117
|
|
|
); |
|
118
|
|
|
// cache tags |
|
119
|
|
|
$tags = []; |
|
120
|
1 |
|
foreach ($options as $key => $value) { |
|
121
|
1 |
|
if (\is_bool($value) && $value === true) { |
|
122
|
1 |
|
$tags[] = $key; |
|
123
|
|
|
} |
|
124
|
1 |
|
if (\is_string($value) && !empty($value)) { |
|
125
|
|
|
$tags[] = $value; |
|
126
|
|
|
} |
|
127
|
1 |
|
} |
|
128
|
1 |
|
// DEBUG |
|
129
|
1 |
|
echo implode('_', $tags) . "\n"; |
|
130
|
1 |
|
echo hash('crc32', implode('_', $tags)) . "\n"; |
|
131
|
1 |
|
die('debug'); |
|
132
|
1 |
|
|
|
133
|
1 |
|
// cache |
|
134
|
1 |
|
//$cache = new Cache($this->builder, 'assets'); |
|
135
|
1 |
|
//$cacheKey = $cache->createKeyFromAsset($this, $fingerprint ? ['fingerprinted'] : []); |
|
136
|
1 |
|
|
|
137
|
|
|
//$cacheKey = \sprintf('%s__%s', $filename ?: implode('_', $paths), $this->builder->getVersion()); |
|
138
|
1 |
|
|
|
139
|
1 |
|
// locate file(s) and get content |
|
140
|
1 |
|
$pathsCount = \count($paths); |
|
|
|
|
|
|
141
|
1 |
|
for ($i = 0; $i < $pathsCount; $i++) { |
|
142
|
1 |
|
try { |
|
143
|
|
|
$this->data['missing'] = false; |
|
144
|
|
|
$locate = $this->locateFile($paths[$i], $options['fallback']); |
|
145
|
|
|
$file = $locate['file']; |
|
146
|
1 |
|
$path = $locate['path']; |
|
147
|
1 |
|
$type = Util\File::getMediaType($file)[0]; |
|
148
|
1 |
|
if ($i > 0) { // bundle |
|
149
|
1 |
|
if ($type != $this->data['type']) { |
|
150
|
1 |
|
throw new RuntimeException(\sprintf('Asset bundle type error (%s != %s).', $type, $this->data['type'])); |
|
151
|
1 |
|
} |
|
152
|
1 |
|
} |
|
153
|
1 |
|
$this->data['file'] = $file; |
|
154
|
1 |
|
$this->data['files'][] = $file; |
|
155
|
|
|
$this->data['path'] = $path; |
|
156
|
|
|
$this->data['url'] = Util\File::isRemote($paths[$i]) ? $paths[$i] : null; |
|
157
|
|
|
$this->data['ext'] = Util\File::getExtension($file); |
|
158
|
|
|
$this->data['type'] = $type; |
|
159
|
|
|
$this->data['subtype'] = Util\File::getMediaType($file)[1]; |
|
160
|
1 |
|
$this->data['size'] += filesize($file); |
|
161
|
1 |
|
$this->data['content'] .= Util\File::fileGetContents($file); |
|
162
|
1 |
|
// bundle default filename |
|
163
|
|
|
$filename = $options['filename']; |
|
164
|
|
|
if ($pathsCount > 1 && empty($filename)) { |
|
165
|
|
|
switch ($this->data['ext']) { |
|
166
|
1 |
|
case 'scss': |
|
167
|
|
|
case 'css': |
|
168
|
1 |
|
$filename = 'styles.css'; |
|
169
|
1 |
|
break; |
|
170
|
|
|
case 'js': |
|
171
|
1 |
|
$filename = 'scripts.js'; |
|
172
|
1 |
|
break; |
|
173
|
|
|
default: |
|
174
|
|
|
throw new RuntimeException(\sprintf('Asset bundle supports %s files only.', '.scss, .css and .js')); |
|
175
|
1 |
|
} |
|
176
|
|
|
} |
|
177
|
1 |
|
// apply bundle filename to path |
|
178
|
1 |
|
if (!empty($filename)) { |
|
179
|
|
|
$this->data['path'] = '/' . ltrim($filename, '/'); |
|
180
|
|
|
} |
|
181
|
1 |
|
// force root slash |
|
182
|
1 |
|
if ($options['leading_slash']) { |
|
183
|
|
|
$this->data['path'] = '/' . ltrim($this->data['path'], '/'); |
|
184
|
|
|
} |
|
185
|
1 |
|
$this->data['_path'] = $this->data['path']; |
|
186
|
1 |
|
} catch (RuntimeException $e) { |
|
187
|
|
|
if ($options['ignore_missing']) { |
|
188
|
|
|
$this->data['missing'] = true; |
|
189
|
|
|
continue; |
|
190
|
|
|
} |
|
191
|
|
|
throw new RuntimeException(\sprintf('Can\'t handle asset "%s" (%s).', $paths[$i], $e->getMessage())); |
|
192
|
|
|
} |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
1 |
|
// missing |
|
196
|
|
|
if ($this->data['missing']) { |
|
197
|
1 |
|
return; |
|
198
|
|
|
} |
|
199
|
1 |
|
|
|
200
|
|
|
// cache |
|
201
|
|
|
$cache = new Cache($this->builder, 'assets'); |
|
202
|
|
|
$cacheKey = $cache->createKeyFromAsset($this, $fingerprint ? ['fingerprinted'] : []); |
|
203
|
1 |
|
if (!$cache->has($cacheKey)) { |
|
204
|
|
|
// image: width, height and exif |
|
205
|
|
|
if ($this->data['type'] == 'image') { |
|
206
|
|
|
$this->data['width'] = $this->getWidth(); |
|
207
|
1 |
|
$this->data['height'] = $this->getHeight(); |
|
208
|
|
|
if ($this->data['subtype'] == 'image/jpeg') { |
|
209
|
|
|
$this->data['exif'] = Util\File::readExif($this->data['file']); |
|
210
|
|
|
} |
|
211
|
|
|
} |
|
212
|
|
|
// fingerprinting |
|
213
|
1 |
|
if ($options['fingerprint']) { |
|
214
|
|
|
$this->fingerprint(); |
|
215
|
1 |
|
} |
|
216
|
1 |
|
$cache->set($cacheKey, $this->data); |
|
217
|
|
|
$this->builder->getLogger()->debug(\sprintf('Asset created: "%s"', $this->data['path'])); |
|
218
|
|
|
// optimizing images files (in cache) |
|
219
|
1 |
|
if ($options['optimize'] && $this->data['type'] == 'image' && !$this->isImageInCdn()) { |
|
220
|
1 |
|
$this->optimize($cache->getContentFilePathname($this->data['path']), $this->data['path']); |
|
221
|
1 |
|
} |
|
222
|
1 |
|
} |
|
223
|
1 |
|
$this->data = $cache->get($cacheKey); |
|
224
|
1 |
|
|
|
225
|
1 |
|
// compiling Sass files |
|
226
|
1 |
|
$this->compile(); |
|
227
|
1 |
|
// minifying (CSS and JavScript files) |
|
228
|
1 |
|
if ($options['minify']) { |
|
229
|
1 |
|
$this->minify(); |
|
230
|
|
|
} |
|
231
|
1 |
|
} |
|
232
|
|
|
|
|
233
|
1 |
|
/** |
|
234
|
|
|
* Returns path. |
|
235
|
1 |
|
*/ |
|
236
|
|
|
public function __toString(): string |
|
237
|
|
|
{ |
|
238
|
|
|
$this->save(); |
|
239
|
|
|
|
|
240
|
|
|
if ($this->isImageInCdn()) { |
|
241
|
|
|
return $this->buildImageCdnUrl(); |
|
242
|
|
|
} |
|
243
|
1 |
|
|
|
244
|
|
|
if ($this->builder->getConfig()->isEnabled('canonicalurl')) { |
|
245
|
1 |
|
return (string) new Url($this->builder, $this->data['path'], ['canonical' => true]); |
|
246
|
1 |
|
} |
|
247
|
|
|
|
|
248
|
|
|
return $this->data['path']; |
|
249
|
1 |
|
} |
|
250
|
1 |
|
|
|
251
|
|
|
/** |
|
252
|
|
|
* Compiles a SCSS. |
|
253
|
1 |
|
* |
|
254
|
1 |
|
* @throws RuntimeException |
|
255
|
1 |
|
*/ |
|
256
|
1 |
|
public function compile(): self |
|
257
|
|
|
{ |
|
258
|
1 |
|
if ($this->compiled) { |
|
259
|
1 |
|
return $this; |
|
260
|
1 |
|
} |
|
261
|
1 |
|
|
|
262
|
1 |
|
if ($this->data['ext'] != 'scss') { |
|
263
|
1 |
|
return $this; |
|
264
|
1 |
|
} |
|
265
|
1 |
|
|
|
266
|
1 |
|
$cache = new Cache($this->builder, 'assets'); |
|
267
|
1 |
|
$cacheKey = $cache->createKeyFromAsset($this, ['compiled']); |
|
268
|
1 |
|
if (!$cache->has($cacheKey)) { |
|
269
|
1 |
|
$scssPhp = new Compiler(); |
|
270
|
|
|
// import paths |
|
271
|
|
|
$importDir = []; |
|
272
|
1 |
|
$importDir[] = Util::joinPath($this->config->getStaticPath()); |
|
273
|
1 |
|
$importDir[] = Util::joinPath($this->config->getAssetsPath()); |
|
274
|
|
|
$scssDir = (array) $this->config->get('assets.compile.import'); |
|
275
|
1 |
|
$themes = $this->config->getTheme() ?? []; |
|
276
|
|
|
foreach ($scssDir as $dir) { |
|
277
|
|
|
$importDir[] = Util::joinPath($this->config->getStaticPath(), $dir); |
|
278
|
|
|
$importDir[] = Util::joinPath($this->config->getAssetsPath(), $dir); |
|
279
|
|
|
$importDir[] = Util::joinPath(\dirname($this->data['file']), $dir); |
|
280
|
|
|
foreach ($themes as $theme) { |
|
281
|
|
|
$importDir[] = Util::joinPath($this->config->getThemeDirPath($theme, "static/$dir")); |
|
282
|
|
|
$importDir[] = Util::joinPath($this->config->getThemeDirPath($theme, "assets/$dir")); |
|
283
|
|
|
} |
|
284
|
|
|
} |
|
285
|
|
|
$scssPhp->setQuietDeps(true); |
|
286
|
|
|
$scssPhp->setImportPaths(array_unique($importDir)); |
|
287
|
|
|
// source map |
|
288
|
|
|
if ($this->builder->isDebug() && $this->config->isEnabled('assets.compile.sourcemap')) { |
|
289
|
|
|
$importDir = []; |
|
290
|
|
|
$assetDir = (string) $this->config->get('assets.dir'); |
|
291
|
|
|
$assetDirPos = strrpos($this->data['file'], DIRECTORY_SEPARATOR . $assetDir . DIRECTORY_SEPARATOR); |
|
292
|
|
|
$fileRelPath = substr($this->data['file'], $assetDirPos + 8); |
|
293
|
1 |
|
$filePath = Util::joinFile($this->config->getOutputPath(), $fileRelPath); |
|
294
|
1 |
|
$importDir[] = \dirname($filePath); |
|
295
|
1 |
|
foreach ($scssDir as $dir) { |
|
296
|
|
|
$importDir[] = Util::joinFile($this->config->getOutputPath(), $dir); |
|
297
|
|
|
} |
|
298
|
1 |
|
$scssPhp->setImportPaths(array_unique($importDir)); |
|
299
|
|
|
$scssPhp->setSourceMap(Compiler::SOURCE_MAP_INLINE); |
|
300
|
1 |
|
$scssPhp->setSourceMapOptions([ |
|
301
|
1 |
|
'sourceMapBasepath' => Util::joinPath($this->config->getOutputPath()), |
|
302
|
1 |
|
'sourceRoot' => '/', |
|
303
|
1 |
|
]); |
|
304
|
|
|
} |
|
305
|
|
|
// output style |
|
306
|
1 |
|
$outputStyles = ['expanded', 'compressed']; |
|
307
|
1 |
|
$outputStyle = strtolower((string) $this->config->get('assets.compile.style')); |
|
308
|
1 |
|
if (!\in_array($outputStyle, $outputStyles)) { |
|
309
|
|
|
throw new ConfigException(\sprintf('"%s" value must be "%s".', 'assets.compile.style', implode('" or "', $outputStyles))); |
|
310
|
|
|
} |
|
311
|
1 |
|
$scssPhp->setOutputStyle($outputStyle == 'compressed' ? OutputStyle::COMPRESSED : OutputStyle::EXPANDED); |
|
312
|
1 |
|
// variables |
|
313
|
1 |
|
$variables = $this->config->get('assets.compile.variables'); |
|
314
|
1 |
|
if (!empty($variables)) { |
|
315
|
1 |
|
$variables = array_map('ScssPhp\ScssPhp\ValueConverter::parseValue', $variables); |
|
316
|
1 |
|
$scssPhp->replaceVariables($variables); |
|
317
|
1 |
|
} |
|
318
|
1 |
|
// debug |
|
319
|
1 |
|
if ($this->builder->isDebug()) { |
|
320
|
|
|
$scssPhp->setQuietDeps(false); |
|
321
|
1 |
|
$this->builder->getLogger()->debug(\sprintf("SCSS compiler imported paths:\n%s", Util\Str::arrayToList(array_unique($importDir)))); |
|
322
|
|
|
} |
|
323
|
1 |
|
// update data |
|
324
|
|
|
$this->data['path'] = preg_replace('/sass|scss/m', 'css', $this->data['path']); |
|
325
|
|
|
$this->data['ext'] = 'css'; |
|
326
|
|
|
$this->data['type'] = 'text'; |
|
327
|
|
|
$this->data['subtype'] = 'text/css'; |
|
328
|
|
|
$this->data['content'] = $scssPhp->compileString($this->data['content'])->getCss(); |
|
329
|
|
|
$this->data['size'] = \strlen($this->data['content']); |
|
330
|
|
|
$cache->set($cacheKey, $this->data); |
|
331
|
1 |
|
$this->compiled = true; |
|
332
|
|
|
$this->builder->getLogger()->debug(\sprintf('Asset compiled: "%s"', $this->data['path'])); |
|
333
|
|
|
} |
|
334
|
1 |
|
$this->data = $cache->get($cacheKey); |
|
335
|
|
|
|
|
336
|
|
|
return $this; |
|
337
|
|
|
} |
|
338
|
1 |
|
|
|
339
|
|
|
/** |
|
340
|
|
|
* Minifying a CSS or a JS. |
|
341
|
|
|
* |
|
342
|
1 |
|
* @throws RuntimeException |
|
343
|
|
|
*/ |
|
344
|
|
|
public function minify(): self |
|
345
|
|
|
{ |
|
346
|
1 |
|
// in debug mode, disable minify to preserve inline source map |
|
347
|
|
|
if ($this->builder->isDebug() && $this->config->isEnabled('assets.compile.sourcemap')) { |
|
348
|
|
|
return $this; |
|
349
|
|
|
} |
|
350
|
1 |
|
|
|
351
|
|
|
if ($this->data['ext'] != 'css' && $this->data['ext'] != 'js') { |
|
352
|
|
|
return $this; |
|
353
|
|
|
} |
|
354
|
|
|
|
|
355
|
|
|
if (substr($this->data['path'], -8) == '.min.css' || substr($this->data['path'], -7) == '.min.js') { |
|
356
|
1 |
|
$this->minified = true; |
|
357
|
1 |
|
} |
|
358
|
1 |
|
|
|
359
|
1 |
|
if ($this->minified) { |
|
360
|
1 |
|
return $this; |
|
361
|
1 |
|
} |
|
362
|
1 |
|
|
|
363
|
1 |
|
if ($this->data['ext'] == 'scss') { |
|
364
|
1 |
|
$this->compile(); |
|
365
|
1 |
|
} |
|
366
|
|
|
|
|
367
|
|
|
$cache = new Cache($this->builder, 'assets'); |
|
368
|
|
|
$cacheKey = $cache->createKeyFromAsset($this, ['minified']); |
|
369
|
1 |
|
if (!$cache->has($cacheKey)) { |
|
370
|
1 |
|
switch ($this->data['ext']) { |
|
371
|
1 |
|
case 'css': |
|
372
|
1 |
|
$minifier = new Minify\CSS($this->data['content']); |
|
373
|
1 |
|
break; |
|
374
|
1 |
|
case 'js': |
|
375
|
1 |
|
$minifier = new Minify\JS($this->data['content']); |
|
376
|
1 |
|
break; |
|
377
|
1 |
|
default: |
|
378
|
1 |
|
throw new RuntimeException(\sprintf('Not able to minify "%s".', $this->data['path'])); |
|
379
|
|
|
} |
|
380
|
1 |
|
$this->data['content'] = $minifier->minify(); |
|
381
|
|
|
$this->data['size'] = \strlen($this->data['content']); |
|
382
|
1 |
|
$cache->set($cacheKey, $this->data); |
|
383
|
|
|
$this->minified = true; |
|
384
|
|
|
$this->builder->getLogger()->debug(\sprintf('Asset minified: "%s"', $this->data['path'])); |
|
385
|
|
|
} |
|
386
|
|
|
$this->data = $cache->get($cacheKey); |
|
387
|
|
|
|
|
388
|
|
|
return $this; |
|
389
|
1 |
|
} |
|
390
|
|
|
|
|
391
|
1 |
|
/** |
|
392
|
1 |
|
* Add hash to the file name. |
|
393
|
1 |
|
*/ |
|
394
|
1 |
|
public function fingerprint(): self |
|
395
|
1 |
|
{ |
|
396
|
1 |
|
if ($this->fingerprinted) { |
|
397
|
|
|
return $this; |
|
398
|
|
|
} |
|
399
|
|
|
|
|
400
|
|
|
$cache = new Cache($this->builder, 'assets'); |
|
401
|
|
|
$cacheKey = $cache->createKeyFromAsset($this, ['fingerprinted']); |
|
402
|
|
|
if (!$cache->has($cacheKey)) { |
|
403
|
|
|
$hash = hash('md5', $this->data['content']); |
|
404
|
1 |
|
$this->data['path'] = preg_replace( |
|
405
|
|
|
'/\.' . $this->data['ext'] . '$/m', |
|
406
|
1 |
|
".$hash." . $this->data['ext'], |
|
407
|
|
|
$this->data['path'] |
|
408
|
|
|
); |
|
409
|
|
|
$cache->set($cacheKey, $this->data); |
|
410
|
|
|
$this->fingerprinted = true; |
|
411
|
|
|
$this->builder->getLogger()->debug(\sprintf('Asset fingerprinted: "%s"', $this->data['path'])); |
|
412
|
|
|
} |
|
413
|
|
|
$this->data = $cache->get($cacheKey); |
|
414
|
1 |
|
|
|
415
|
|
|
return $this; |
|
416
|
1 |
|
} |
|
417
|
|
|
|
|
418
|
|
|
/** |
|
419
|
1 |
|
* Optimizing $filepath image. |
|
420
|
|
|
* Returns the new file size. |
|
421
|
|
|
*/ |
|
422
|
1 |
|
public function optimize(string $filepath, string $path): int |
|
423
|
1 |
|
{ |
|
424
|
|
|
$quality = (int) $this->config->get('assets.images.quality'); |
|
425
|
|
|
$message = \sprintf('Asset processed: "%s"', $path); |
|
426
|
1 |
|
$sizeBefore = filesize($filepath); |
|
427
|
1 |
|
Optimizer::create($quality)->optimize($filepath); |
|
428
|
|
|
$sizeAfter = filesize($filepath); |
|
429
|
1 |
|
if ($sizeAfter < $sizeBefore) { |
|
430
|
|
|
$message = \sprintf( |
|
431
|
|
|
'Asset optimized: "%s" (%s Ko -> %s Ko)', |
|
432
|
|
|
$path, |
|
433
|
|
|
ceil($sizeBefore / 1000), |
|
434
|
|
|
ceil($sizeAfter / 1000) |
|
435
|
1 |
|
); |
|
436
|
1 |
|
} |
|
437
|
1 |
|
$this->builder->getLogger()->debug($message); |
|
438
|
1 |
|
|
|
439
|
1 |
|
return $sizeAfter; |
|
440
|
1 |
|
} |
|
441
|
1 |
|
|
|
442
|
1 |
|
/** |
|
443
|
1 |
|
* Resizes an image with a new $width. |
|
444
|
1 |
|
* |
|
445
|
1 |
|
* @throws RuntimeException |
|
446
|
1 |
|
*/ |
|
447
|
1 |
|
public function resize(int $width): self |
|
448
|
|
|
{ |
|
449
|
1 |
|
if ($this->data['missing']) { |
|
450
|
1 |
|
throw new RuntimeException(\sprintf('Not able to resize "%s": file not found.', $this->data['path'])); |
|
451
|
|
|
} |
|
452
|
1 |
|
if ($this->data['type'] != 'image') { |
|
453
|
|
|
throw new RuntimeException(\sprintf('Not able to resize "%s": not an image.', $this->data['path'])); |
|
454
|
1 |
|
} |
|
455
|
|
|
if ($width >= $this->data['width']) { |
|
456
|
|
|
return $this; |
|
457
|
|
|
} |
|
458
|
|
|
|
|
459
|
|
|
$assetResized = clone $this; |
|
460
|
|
|
$assetResized->data['width'] = $width; |
|
461
|
|
|
|
|
462
|
1 |
|
if ($this->isImageInCdn()) { |
|
463
|
|
|
$assetResized->data['height'] = round($this->data['height'] / ($this->data['width'] / $width)); |
|
464
|
1 |
|
|
|
465
|
|
|
return $assetResized; // returns asset with the new dimensions only: CDN do the rest of the job |
|
466
|
|
|
} |
|
467
|
|
|
|
|
468
|
1 |
|
$quality = (int) $this->config->get('assets.images.quality'); |
|
469
|
1 |
|
$cache = new Cache($this->builder, 'assets'); |
|
470
|
|
|
$cacheKey = $cache->createKeyFromAsset($assetResized, ["{$width}x", "q$quality"]); |
|
471
|
|
|
if (!$cache->has($cacheKey)) { |
|
472
|
1 |
|
$assetResized->data['content'] = Image::resize($assetResized, $width, $quality); |
|
473
|
1 |
|
$assetResized->data['path'] = '/' . Util::joinPath( |
|
474
|
1 |
|
(string) $this->config->get('assets.target'), |
|
475
|
|
|
'thumbnails', |
|
476
|
1 |
|
(string) $width, |
|
477
|
|
|
$assetResized->data['path'] |
|
478
|
|
|
); |
|
479
|
|
|
$assetResized->data['height'] = $assetResized->getHeight(); |
|
480
|
1 |
|
$assetResized->data['size'] = \strlen($assetResized->data['content']); |
|
481
|
1 |
|
|
|
482
|
1 |
|
$cache->set($cacheKey, $assetResized->data); |
|
483
|
1 |
|
$this->builder->getLogger()->debug(\sprintf('Asset resized: "%s" (%sx)', $assetResized->data['path'], $width)); |
|
484
|
|
|
} |
|
485
|
1 |
|
$assetResized->data = $cache->get($cacheKey); |
|
486
|
1 |
|
|
|
487
|
1 |
|
return $assetResized; |
|
488
|
|
|
} |
|
489
|
|
|
|
|
490
|
|
|
/** |
|
491
|
|
|
* Converts an image asset to $format format. |
|
492
|
|
|
* |
|
493
|
|
|
* @throws RuntimeException |
|
494
|
|
|
*/ |
|
495
|
|
|
public function convert(string $format, ?int $quality = null): self |
|
496
|
|
|
{ |
|
497
|
|
|
if ($this->data['type'] != 'image') { |
|
498
|
|
|
throw new RuntimeException(\sprintf('Not able to convert "%s" (%s) to %s: not an image.', $this->data['path'], $this->data['type'], $format)); |
|
499
|
|
|
} |
|
500
|
|
|
|
|
501
|
|
|
if ($quality === null) { |
|
502
|
|
|
$quality = (int) $this->config->get('assets.images.quality'); |
|
503
|
|
|
} |
|
504
|
|
|
|
|
505
|
|
|
$asset = clone $this; |
|
506
|
|
|
$asset['ext'] = $format; |
|
507
|
|
|
$asset->data['subtype'] = "image/$format"; |
|
508
|
|
|
|
|
509
|
|
|
if ($this->isImageInCdn()) { |
|
510
|
|
|
return $asset; // returns the asset with the new extension only: CDN do the rest of the job |
|
511
|
|
|
} |
|
512
|
|
|
|
|
513
|
1 |
|
$cache = new Cache($this->builder, 'assets'); |
|
514
|
|
|
$tags = ["q$quality"]; |
|
515
|
1 |
|
if ($this->data['width']) { |
|
516
|
|
|
array_unshift($tags, "{$this->data['width']}x"); |
|
517
|
|
|
} |
|
518
|
|
|
$cacheKey = $cache->createKeyFromAsset($asset, $tags); |
|
519
|
|
|
if (!$cache->has($cacheKey)) { |
|
520
|
|
|
$asset->data['content'] = Image::convert($asset, $format, $quality); |
|
521
|
1 |
|
$asset->data['path'] = preg_replace('/\.' . $this->data['ext'] . '$/m', ".$format", $this->data['path']); |
|
522
|
|
|
$asset->data['size'] = \strlen($asset->data['content']); |
|
523
|
|
|
$cache->set($cacheKey, $asset->data); |
|
524
|
1 |
|
$this->builder->getLogger()->debug(\sprintf('Asset converted: "%s" (%s -> %s)', $asset->data['path'], $this->data['ext'], $format)); |
|
525
|
1 |
|
} |
|
526
|
|
|
$asset->data = $cache->get($cacheKey); |
|
527
|
|
|
|
|
528
|
|
|
return $asset; |
|
529
|
|
|
} |
|
530
|
|
|
|
|
531
|
|
|
/** |
|
532
|
1 |
|
* Converts an image asset to WebP format. |
|
533
|
|
|
* |
|
534
|
|
|
* @throws RuntimeException |
|
535
|
1 |
|
*/ |
|
536
|
|
|
public function webp(?int $quality = null): self |
|
537
|
|
|
{ |
|
538
|
|
|
return $this->convert('webp', $quality); |
|
539
|
|
|
} |
|
540
|
|
|
|
|
541
|
|
|
/** |
|
542
|
|
|
* Converts an image asset to AVIF format. |
|
543
|
|
|
* |
|
544
|
|
|
* @throws RuntimeException |
|
545
|
|
|
*/ |
|
546
|
|
|
public function avif(?int $quality = null): self |
|
547
|
|
|
{ |
|
548
|
|
|
return $this->convert('avif', $quality); |
|
549
|
|
|
} |
|
550
|
1 |
|
|
|
551
|
|
|
/** |
|
552
|
|
|
* Implements \ArrayAccess. |
|
553
|
1 |
|
*/ |
|
554
|
|
|
#[\ReturnTypeWillChange] |
|
555
|
|
|
public function offsetSet($offset, $value): void |
|
556
|
|
|
{ |
|
557
|
|
|
if (!\is_null($offset)) { |
|
558
|
|
|
$this->data[$offset] = $value; |
|
559
|
|
|
} |
|
560
|
|
|
} |
|
561
|
|
|
|
|
562
|
1 |
|
/** |
|
563
|
|
|
* Implements \ArrayAccess. |
|
564
|
1 |
|
*/ |
|
565
|
|
|
#[\ReturnTypeWillChange] |
|
566
|
|
|
public function offsetExists($offset): bool |
|
567
|
|
|
{ |
|
568
|
|
|
return isset($this->data[$offset]); |
|
569
|
|
|
} |
|
570
|
|
|
|
|
571
|
|
|
/** |
|
572
|
1 |
|
* Implements \ArrayAccess. |
|
573
|
|
|
*/ |
|
574
|
1 |
|
#[\ReturnTypeWillChange] |
|
575
|
|
|
public function offsetUnset($offset): void |
|
576
|
|
|
{ |
|
577
|
|
|
unset($this->data[$offset]); |
|
578
|
1 |
|
} |
|
579
|
|
|
|
|
580
|
|
|
/** |
|
581
|
|
|
* Implements \ArrayAccess. |
|
582
|
|
|
*/ |
|
583
|
|
|
#[\ReturnTypeWillChange] |
|
584
|
|
|
public function offsetGet($offset) |
|
585
|
|
|
{ |
|
586
|
1 |
|
return isset($this->data[$offset]) ? $this->data[$offset] : null; |
|
587
|
|
|
} |
|
588
|
1 |
|
|
|
589
|
|
|
/** |
|
590
|
|
|
* Hashing content of an asset with the specified algo, sha384 by default. |
|
591
|
|
|
* Used for SRI (Subresource Integrity). |
|
592
|
1 |
|
* |
|
593
|
|
|
* @see https://developer.mozilla.org/fr/docs/Web/Security/Subresource_Integrity |
|
594
|
|
|
*/ |
|
595
|
|
|
public function getIntegrity(string $algo = 'sha384'): string |
|
596
|
|
|
{ |
|
597
|
|
|
return \sprintf('%s-%s', $algo, base64_encode(hash($algo, $this->data['content'], true))); |
|
598
|
|
|
} |
|
599
|
|
|
|
|
600
|
1 |
|
/** |
|
601
|
|
|
* Returns MP3 file infos. |
|
602
|
1 |
|
* |
|
603
|
1 |
|
* @see https://github.com/wapmorgan/Mp3Info |
|
604
|
|
|
*/ |
|
605
|
|
|
public function getAudio(): Mp3Info |
|
606
|
1 |
|
{ |
|
607
|
|
|
if ($this->data['type'] !== 'audio') { |
|
608
|
|
|
throw new RuntimeException(\sprintf('Not able to get audio infos of "%s".', $this->data['path'])); |
|
609
|
|
|
} |
|
610
|
|
|
|
|
611
|
|
|
return new Mp3Info($this->data['file']); |
|
612
|
|
|
} |
|
613
|
|
|
|
|
614
|
1 |
|
/** |
|
615
|
|
|
* Returns MP4 file infos. |
|
616
|
1 |
|
* |
|
617
|
1 |
|
* @see https://github.com/clwu88/php-read-mp4info |
|
618
|
1 |
|
*/ |
|
619
|
|
|
public function getVideo(): array |
|
620
|
|
|
{ |
|
621
|
|
|
if ($this->data['type'] !== 'video') { |
|
622
|
|
|
throw new RuntimeException(\sprintf('Not able to get video infos of "%s".', $this->data['path'])); |
|
623
|
|
|
} |
|
624
|
|
|
|
|
625
|
|
|
return (array) \Clwu\Mp4::getInfo($this->data['file']); |
|
626
|
|
|
} |
|
627
|
1 |
|
|
|
628
|
|
|
/** |
|
629
|
|
|
* Returns the Data URL (encoded in Base64). |
|
630
|
1 |
|
* |
|
631
|
1 |
|
* @throws RuntimeException |
|
632
|
|
|
*/ |
|
633
|
1 |
|
public function dataurl(): string |
|
634
|
|
|
{ |
|
635
|
1 |
|
if ($this->data['type'] == 'image' && !Image::isSVG($this)) { |
|
636
|
|
|
return Image::getDataUrl($this, (int) $this->config->get('assets.images.quality')); |
|
637
|
|
|
} |
|
638
|
|
|
|
|
639
|
|
|
return \sprintf('data:%s;base64,%s', $this->data['subtype'], base64_encode($this->data['content'])); |
|
640
|
|
|
} |
|
641
|
|
|
|
|
642
|
|
|
/** |
|
643
|
|
|
* Adds asset path to the list of assets to save. |
|
644
|
|
|
* |
|
645
|
|
|
* @throws RuntimeException |
|
646
|
|
|
*/ |
|
647
|
|
|
public function save(): void |
|
648
|
|
|
{ |
|
649
|
|
|
if ($this->data['missing']) { |
|
650
|
|
|
return; |
|
651
|
|
|
} |
|
652
|
1 |
|
|
|
653
|
|
|
$cache = new Cache($this->builder, 'assets'); |
|
654
|
1 |
|
if (!Util\File::getFS()->exists($cache->getContentFilePathname($this->data['path']))) { |
|
655
|
1 |
|
throw new RuntimeException( |
|
656
|
1 |
|
\sprintf('Can\'t add "%s" to assets list. Please clear cache and retry.', $this->data['path']) |
|
657
|
1 |
|
); |
|
658
|
1 |
|
} |
|
659
|
1 |
|
|
|
660
|
1 |
|
$this->builder->addAsset($this->data['path']); |
|
661
|
1 |
|
} |
|
662
|
1 |
|
|
|
663
|
1 |
|
/** |
|
664
|
1 |
|
* Is the asset an image and is it in CDN? |
|
665
|
|
|
*/ |
|
666
|
|
|
public function isImageInCdn(): bool |
|
667
|
|
|
{ |
|
668
|
1 |
|
if ( |
|
669
|
1 |
|
$this->data['type'] == 'image' |
|
670
|
1 |
|
&& $this->config->isEnabled('assets.images.cdn') |
|
671
|
1 |
|
&& $this->data['ext'] != 'ico' |
|
672
|
1 |
|
&& (Image::isSVG($this) && $this->config->isEnabled('assets.images.cdn.svg')) |
|
673
|
|
|
) { |
|
674
|
1 |
|
return true; |
|
675
|
|
|
} |
|
676
|
|
|
// handle remote image? |
|
677
|
|
|
if ($this->data['url'] !== null && $this->config->isEnabled('assets.images.cdn.remote')) { |
|
678
|
|
|
return true; |
|
679
|
|
|
} |
|
680
|
|
|
|
|
681
|
1 |
|
return false; |
|
682
|
1 |
|
} |
|
683
|
1 |
|
|
|
684
|
1 |
|
/** |
|
685
|
1 |
|
* Builds a relative path from a URL. |
|
686
|
1 |
|
* Used for remote files. |
|
687
|
|
|
*/ |
|
688
|
1 |
|
public static function buildPathFromUrl(string $url): string |
|
689
|
|
|
{ |
|
690
|
|
|
$host = parse_url($url, PHP_URL_HOST); |
|
691
|
|
|
$path = parse_url($url, PHP_URL_PATH); |
|
692
|
|
|
$query = parse_url($url, PHP_URL_QUERY); |
|
693
|
|
|
$ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); |
|
694
|
1 |
|
|
|
695
|
|
|
// Google Fonts hack |
|
696
|
|
|
if (Util\Str::endsWith($path, '/css') || Util\Str::endsWith($path, '/css2')) { |
|
697
|
|
|
$ext = 'css'; |
|
698
|
1 |
|
} |
|
699
|
1 |
|
|
|
700
|
|
|
return Page::slugify(\sprintf('%s%s%s%s', $host, self::sanitize($path), $query ? "-$query" : '', $query && $ext ? ".$ext" : '')); |
|
701
|
|
|
} |
|
702
|
|
|
|
|
703
|
1 |
|
/** |
|
704
|
1 |
|
* Replaces some characters by '_'. |
|
705
|
|
|
*/ |
|
706
|
1 |
|
public static function sanitize(string $string): string |
|
707
|
1 |
|
{ |
|
708
|
1 |
|
return str_replace(['<', '>', ':', '"', '\\', '|', '?', '*'], '_', $string); |
|
709
|
1 |
|
} |
|
710
|
1 |
|
|
|
711
|
1 |
|
/** |
|
712
|
1 |
|
* Returns local file path and updated path, or throw an exception. |
|
713
|
1 |
|
* |
|
714
|
|
|
* Try to locate the file in: |
|
715
|
1 |
|
* (1. remote file) |
|
716
|
|
|
* 1. assets |
|
717
|
|
|
* 2. themes/<theme>/assets |
|
718
|
|
|
* 3. static |
|
719
|
|
|
* 4. themes/<theme>/static |
|
720
|
|
|
* |
|
721
|
|
|
* If $fallback is set, it will be used if the file is not found. |
|
722
|
|
|
* |
|
723
|
|
|
* @throws RuntimeException |
|
724
|
|
|
*/ |
|
725
|
|
|
private function locateFile(string $path, ?string $fallback = null): array |
|
726
|
|
|
{ |
|
727
|
|
|
// remote file |
|
728
|
|
|
if (Util\File::isRemote($path)) { |
|
729
|
1 |
|
try { |
|
730
|
|
|
$content = $this->getRemoteFileContent($path); |
|
731
|
|
|
$path = self::buildPathFromUrl($path); |
|
732
|
1 |
|
$cache = new Cache($this->builder, 'assets/remote'); |
|
733
|
1 |
|
if (!$cache->has($path)) { |
|
734
|
|
|
$cache->set($path, [ |
|
735
|
1 |
|
'content' => $content, |
|
736
|
1 |
|
'path' => $path, |
|
737
|
1 |
|
], \DateInterval::createFromDateString('7 days')); |
|
738
|
1 |
|
} |
|
739
|
|
|
return [ |
|
740
|
1 |
|
'file' => $cache->getContentFilePathname($path), |
|
741
|
1 |
|
'path' => $path, |
|
742
|
|
|
]; |
|
743
|
1 |
|
} catch (RuntimeException $e) { |
|
744
|
1 |
|
if ($fallback === null) { |
|
745
|
1 |
|
throw new RuntimeException($e->getMessage(), previous: $e); |
|
746
|
1 |
|
} |
|
747
|
1 |
|
$path = $fallback; |
|
748
|
1 |
|
} |
|
749
|
1 |
|
} |
|
750
|
1 |
|
|
|
751
|
|
|
// checks in assets/ |
|
752
|
1 |
|
$file = Util::joinFile($this->config->getAssetsPath(), $path); |
|
753
|
|
|
if (Util\File::getFS()->exists($file)) { |
|
754
|
|
|
return [ |
|
755
|
1 |
|
'file' => $file, |
|
756
|
|
|
'path' => $path, |
|
757
|
|
|
]; |
|
758
|
1 |
|
} |
|
759
|
|
|
|
|
760
|
|
|
// checks in each themes/<theme>/assets/ |
|
761
|
1 |
|
foreach ($this->config->getTheme() ?? [] as $theme) { |
|
762
|
1 |
|
$file = Util::joinFile($this->config->getThemeDirPath($theme, 'assets'), $path); |
|
763
|
|
|
if (Util\File::getFS()->exists($file)) { |
|
764
|
|
|
return [ |
|
765
|
|
|
'file' => $file, |
|
766
|
|
|
'path' => $path, |
|
767
|
|
|
]; |
|
768
|
|
|
} |
|
769
|
|
|
} |
|
770
|
|
|
|
|
771
|
|
|
// checks in static/ |
|
772
|
|
|
$file = Util::joinFile($this->config->getStaticTargetPath(), $path); |
|
773
|
|
|
if (Util\File::getFS()->exists($file)) { |
|
774
|
|
|
return [ |
|
775
|
|
|
'file' => $file, |
|
776
|
|
|
'path' => $path, |
|
777
|
1 |
|
]; |
|
778
|
|
|
} |
|
779
|
|
|
|
|
780
|
1 |
|
// checks in each themes/<theme>/static/ |
|
781
|
|
|
foreach ($this->config->getTheme() ?? [] as $theme) { |
|
782
|
|
|
$file = Util::joinFile($this->config->getThemeDirPath($theme, 'static'), $path); |
|
783
|
|
|
if (Util\File::getFS()->exists($file)) { |
|
784
|
1 |
|
return [ |
|
785
|
1 |
|
'file' => $file, |
|
786
|
1 |
|
'path' => $path, |
|
787
|
|
|
]; |
|
788
|
|
|
} |
|
789
|
|
|
} |
|
790
|
1 |
|
|
|
791
|
1 |
|
throw new RuntimeException(\sprintf('Can\'t locate file "%s".', $path)); |
|
792
|
1 |
|
} |
|
793
|
1 |
|
|
|
794
|
|
|
/** |
|
795
|
|
|
* Try to get remote file content. |
|
796
|
|
|
* Returns file content or throw an exception. |
|
797
|
|
|
* |
|
798
|
1 |
|
* @throws RuntimeException |
|
799
|
1 |
|
*/ |
|
800
|
1 |
|
private function getRemoteFileContent(string $path): string |
|
801
|
|
|
{ |
|
802
|
|
|
try { |
|
803
|
|
|
if (!Util\File::isRemoteExists($path)) { |
|
804
|
1 |
|
throw new RuntimeException(\sprintf('Remote file "%s" doesn\'t exists', $path)); |
|
805
|
1 |
|
} |
|
806
|
1 |
|
if (false === $content = Util\File::fileGetContents($path, true)) { |
|
807
|
1 |
|
throw new RuntimeException(\sprintf('Can\'t get content of remote file "%s".', $path)); |
|
808
|
|
|
} |
|
809
|
|
|
if (\strlen($content) <= 1) { |
|
810
|
|
|
throw new RuntimeException(\sprintf('Remote file "%s" is empty.', $path)); |
|
811
|
1 |
|
} |
|
812
|
|
|
} catch (RuntimeException $e) { |
|
813
|
|
|
throw new RuntimeException($e->getMessage()); |
|
814
|
|
|
} |
|
815
|
|
|
|
|
816
|
|
|
return $content; |
|
817
|
|
|
} |
|
818
|
|
|
|
|
819
|
1 |
|
/** |
|
820
|
|
|
* Returns the width of an image/SVG. |
|
821
|
1 |
|
* |
|
822
|
|
|
* @throws RuntimeException |
|
823
|
|
|
*/ |
|
824
|
1 |
|
private function getWidth(): int |
|
825
|
1 |
|
{ |
|
826
|
|
|
if ($this->data['type'] != 'image') { |
|
827
|
1 |
|
return 0; |
|
828
|
|
|
} |
|
829
|
|
|
if (Image::isSVG($this) && false !== $svg = Image::getSvgAttributes($this)) { |
|
830
|
|
|
return (int) $svg->width; |
|
831
|
1 |
|
} |
|
832
|
|
|
if (false === $size = $this->getImageSize()) { |
|
833
|
|
|
throw new RuntimeException(\sprintf('Not able to get width of "%s".', $this->data['path'])); |
|
834
|
|
|
} |
|
835
|
|
|
|
|
836
|
|
|
return $size[0]; |
|
837
|
|
|
} |
|
838
|
|
|
|
|
839
|
1 |
|
/** |
|
840
|
|
|
* Returns the height of an image/SVG. |
|
841
|
1 |
|
* |
|
842
|
|
|
* @throws RuntimeException |
|
843
|
|
|
*/ |
|
844
|
1 |
|
private function getHeight(): int |
|
845
|
1 |
|
{ |
|
846
|
|
|
if ($this->data['type'] != 'image') { |
|
847
|
1 |
|
return 0; |
|
848
|
|
|
} |
|
849
|
|
|
if (Image::isSVG($this) && false !== $svg = Image::getSvgAttributes($this)) { |
|
850
|
|
|
return (int) $svg->height; |
|
851
|
1 |
|
} |
|
852
|
|
|
if (false === $size = $this->getImageSize()) { |
|
853
|
|
|
throw new RuntimeException(\sprintf('Not able to get height of "%s".', $this->data['path'])); |
|
854
|
|
|
} |
|
855
|
|
|
|
|
856
|
|
|
return $size[1]; |
|
857
|
|
|
} |
|
858
|
|
|
|
|
859
|
|
|
/** |
|
860
|
|
|
* Returns image size informations. |
|
861
|
1 |
|
* |
|
862
|
|
|
* @see https://www.php.net/manual/function.getimagesize.php |
|
863
|
1 |
|
* |
|
864
|
|
|
* @return array|false |
|
865
|
|
|
*/ |
|
866
|
|
|
private function getImageSize() |
|
867
|
|
|
{ |
|
868
|
1 |
|
if (!$this->data['type'] == 'image') { |
|
869
|
1 |
|
return false; |
|
870
|
|
|
} |
|
871
|
|
|
|
|
872
|
|
|
try { |
|
873
|
|
|
if (false === $size = getimagesizefromstring($this->data['content'])) { |
|
874
|
|
|
return false; |
|
875
|
1 |
|
} |
|
876
|
|
|
} catch (\Exception $e) { |
|
877
|
|
|
throw new RuntimeException(\sprintf('Handling asset "%s" failed: "%s"', $this->data['path'], $e->getMessage())); |
|
878
|
|
|
} |
|
879
|
|
|
|
|
880
|
|
|
return $size; |
|
881
|
1 |
|
} |
|
882
|
|
|
|
|
883
|
1 |
|
/** |
|
884
|
|
|
* Builds CDN image URL. |
|
885
|
|
|
*/ |
|
886
|
|
|
private function buildImageCdnUrl(): string |
|
887
|
|
|
{ |
|
888
|
|
|
return str_replace( |
|
889
|
|
|
[ |
|
890
|
|
|
'%account%', |
|
891
|
|
|
'%image_url%', |
|
892
|
|
|
'%width%', |
|
893
|
|
|
'%quality%', |
|
894
|
|
|
'%format%', |
|
895
|
|
|
], |
|
896
|
|
|
[ |
|
897
|
|
|
$this->config->get('assets.images.cdn.account') ?? '', |
|
898
|
|
|
ltrim($this->data['url'] ?? (string) new Url($this->builder, $this->data['path'], ['canonical' => $this->config->get('assets.images.cdn.canonical') ?? true]), '/'), |
|
899
|
|
|
$this->data['width'], |
|
900
|
|
|
(int) $this->config->get('assets.images.quality'), |
|
901
|
|
|
$this->data['ext'], |
|
902
|
|
|
], |
|
903
|
|
|
(string) $this->config->get('assets.images.cdn.url') |
|
904
|
|
|
); |
|
905
|
|
|
} |
|
906
|
|
|
} |
|
907
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return,dieorexitstatements that have been added for debug purposes.In the above example, the last
return falsewill never be executed, because a return statement has already been met in every possible execution path.