1 | <?php |
||||||
2 | /** |
||||||
3 | * ImageOptimize plugin for Craft CMS |
||||||
4 | * |
||||||
5 | * Automatically optimize images after they've been transformed |
||||||
6 | * |
||||||
7 | * @link https://nystudio107.com |
||||||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||||||
8 | * @copyright Copyright (c) 2017 nystudio107 |
||||||
0 ignored issues
–
show
|
|||||||
9 | */ |
||||||
0 ignored issues
–
show
|
|||||||
10 | |||||||
11 | namespace nystudio107\imageoptimize\services; |
||||||
12 | |||||||
13 | use Craft; |
||||||
14 | use craft\base\Component; |
||||||
15 | use craft\base\Image; |
||||||
16 | use craft\console\Application as ConsoleApplication; |
||||||
17 | use craft\elements\Asset; |
||||||
18 | use craft\errors\FsException; |
||||||
19 | use craft\errors\ImageException; |
||||||
20 | use craft\events\DefineAssetThumbUrlEvent; |
||||||
21 | use craft\events\DefineAssetUrlEvent; |
||||||
22 | use craft\events\ImageTransformerOperationEvent; |
||||||
23 | use craft\events\RegisterComponentTypesEvent; |
||||||
24 | use craft\helpers\Component as ComponentHelper; |
||||||
25 | use craft\helpers\FileHelper; |
||||||
26 | use craft\helpers\Html; |
||||||
27 | use craft\helpers\Image as ImageHelper; |
||||||
28 | use craft\image\Raster; |
||||||
29 | use craft\models\ImageTransform as AssetTransform; |
||||||
30 | use craft\models\ImageTransformIndex as AssetTransformIndex; |
||||||
31 | use mikehaertl\shellcommand\Command as ShellCommand; |
||||||
32 | use nystudio107\imageoptimize\helpers\PluginTemplate as PluginTemplateHelper; |
||||||
33 | use nystudio107\imageoptimize\ImageOptimize; |
||||||
34 | use nystudio107\imageoptimize\imagetransforms\CraftImageTransform; |
||||||
35 | use nystudio107\imageoptimize\imagetransforms\ImageTransform; |
||||||
36 | use nystudio107\imageoptimize\imagetransforms\ImageTransformInterface; |
||||||
37 | use nystudio107\imageoptimize\models\Settings; |
||||||
38 | use nystudio107\imageoptimizeimgix\imagetransforms\ImgixImageTransform; |
||||||
39 | use nystudio107\imageoptimizesharp\imagetransforms\SharpImageTransform; |
||||||
40 | use nystudio107\imageoptimizethumbor\imagetransforms\ThumborImageTransform; |
||||||
41 | use Throwable; |
||||||
42 | use yii\base\Configurable; |
||||||
43 | use yii\base\InvalidConfigException; |
||||||
44 | use function function_exists; |
||||||
45 | use function is_array; |
||||||
46 | use function is_string; |
||||||
47 | |||||||
48 | /** @noinspection MissingPropertyAnnotationsInspection */ |
||||||
0 ignored issues
–
show
|
|||||||
49 | |||||||
50 | /** |
||||||
0 ignored issues
–
show
|
|||||||
51 | * @author nystudio107 |
||||||
0 ignored issues
–
show
Content of the @author tag must be in the form "Display Name <[email protected]>"
![]() |
|||||||
52 | * @package ImageOptimize |
||||||
0 ignored issues
–
show
|
|||||||
53 | * @since 1.0.0 |
||||||
0 ignored issues
–
show
|
|||||||
54 | */ |
||||||
0 ignored issues
–
show
|
|||||||
55 | class Optimize extends Component |
||||||
56 | { |
||||||
57 | // Constants |
||||||
58 | // ========================================================================= |
||||||
59 | /** |
||||||
0 ignored issues
–
show
|
|||||||
60 | * @event RegisterComponentTypesEvent The event that is triggered when registering |
||||||
61 | * Image Transform types |
||||||
62 | * |
||||||
63 | * Image Transform types must implement [[ImageTransformInterface]]. [[ImageTransform]] |
||||||
64 | * provides a base implementation. |
||||||
65 | * |
||||||
66 | * ```php |
||||||
67 | * use nystudio107\imageoptimize\services\Optimize; |
||||||
68 | * use craft\events\RegisterComponentTypesEvent; |
||||||
69 | * use yii\base\Event; |
||||||
70 | * |
||||||
71 | * Event::on(Optimize::class, |
||||||
72 | * Optimize::EVENT_REGISTER_IMAGE_TRANSFORM_TYPES, |
||||||
73 | * function(RegisterComponentTypesEvent $event) { |
||||||
74 | * $event->types[] = MyImageTransform::class; |
||||||
75 | * } |
||||||
76 | * ); |
||||||
77 | * ``` |
||||||
78 | * @var string |
||||||
0 ignored issues
–
show
|
|||||||
79 | */ |
||||||
80 | public const EVENT_REGISTER_IMAGE_TRANSFORM_TYPES = 'registerImageTransformTypes'; |
||||||
81 | |||||||
82 | /** |
||||||
0 ignored issues
–
show
|
|||||||
83 | * @var array<class-string<Configurable>> |
||||||
84 | */ |
||||||
85 | public const DEFAULT_IMAGE_TRANSFORM_TYPES = [ |
||||||
86 | CraftImageTransform::class, |
||||||
87 | ImgixImageTransform::class, |
||||||
88 | SharpImageTransform::class, |
||||||
89 | ThumborImageTransform::class, |
||||||
90 | ]; |
||||||
91 | |||||||
92 | // Public Methods |
||||||
93 | // ========================================================================= |
||||||
94 | |||||||
95 | /** |
||||||
96 | * Returns all available field type classes. |
||||||
97 | * |
||||||
98 | * @return string[] The available field type classes |
||||||
99 | */ |
||||||
100 | public function getAllImageTransformTypes(): array |
||||||
101 | { |
||||||
102 | $imageTransformTypes = array_unique(array_merge( |
||||||
0 ignored issues
–
show
|
|||||||
103 | ImageOptimize::$plugin->getSettings()->defaultImageTransformTypes ?? [], |
||||||
0 ignored issues
–
show
The method
getSettings() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
104 | self::DEFAULT_IMAGE_TRANSFORM_TYPES |
||||||
105 | ), SORT_REGULAR); |
||||||
0 ignored issues
–
show
For multi-line function calls, the closing parenthesis should be on a new line.
If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line: someFunctionCall(
$firstArgument,
$secondArgument,
$thirdArgument
); // Closing parenthesis on a new line.
![]() |
|||||||
106 | |||||||
107 | $event = new RegisterComponentTypesEvent([ |
||||||
0 ignored issues
–
show
|
|||||||
108 | 'types' => $imageTransformTypes, |
||||||
109 | ]); |
||||||
0 ignored issues
–
show
For multi-line function calls, the closing parenthesis should be on a new line.
If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line: someFunctionCall(
$firstArgument,
$secondArgument,
$thirdArgument
); // Closing parenthesis on a new line.
![]() |
|||||||
110 | $this->trigger(self::EVENT_REGISTER_IMAGE_TRANSFORM_TYPES, $event); |
||||||
111 | |||||||
112 | return $event->types; |
||||||
113 | } |
||||||
114 | |||||||
115 | /** |
||||||
116 | * Creates an Image Transform with a given config. |
||||||
117 | * |
||||||
118 | * @param string|array $config The Image Transform’s class name, or its config, |
||||||
119 | * with a `type` value and optionally a `settings` value |
||||||
0 ignored issues
–
show
|
|||||||
120 | * |
||||||
121 | * @return ?ImageTransformInterface The Image Transform |
||||||
122 | */ |
||||||
123 | public function createImageTransformType(string|array $config): ?ImageTransformInterface |
||||||
124 | { |
||||||
125 | if (is_string($config)) { |
||||||
0 ignored issues
–
show
|
|||||||
126 | $config = ['type' => $config]; |
||||||
127 | } |
||||||
128 | |||||||
129 | try { |
||||||
130 | /** @var ImageTransform $imageTransform */ |
||||||
0 ignored issues
–
show
|
|||||||
131 | $imageTransform = ComponentHelper::createComponent($config, ImageTransformInterface::class); |
||||||
132 | } catch (Throwable $e) { |
||||||
133 | $imageTransform = null; |
||||||
134 | Craft::error($e->getMessage(), __METHOD__); |
||||||
135 | } |
||||||
136 | |||||||
137 | return $imageTransform; |
||||||
138 | } |
||||||
139 | |||||||
140 | /** |
||||||
141 | * Handle responding to EVENT_GET_ASSET_URL events |
||||||
142 | * |
||||||
143 | * @param DefineAssetUrlEvent $event |
||||||
0 ignored issues
–
show
|
|||||||
144 | * |
||||||
145 | * @return ?string |
||||||
146 | */ |
||||||
147 | public function handleGetAssetUrlEvent(DefineAssetUrlEvent $event): ?string |
||||||
148 | { |
||||||
149 | Craft::beginProfile('handleGetAssetUrlEvent', __METHOD__); |
||||||
150 | $url = null; |
||||||
151 | if (!ImageOptimize::$plugin->transformMethod instanceof CraftImageTransform) { |
||||||
152 | $asset = $event->asset; |
||||||
0 ignored issues
–
show
The property
craft\events\DefineAssetUrlEvent::$asset has been deprecated: in 4.3.0. [[$sender]] should be used instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This property has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead. ![]() |
|||||||
153 | $transform = $event->transform; |
||||||
154 | // If the transform is empty in some regard, normalize it to null |
||||||
155 | if (empty($transform)) { |
||||||
156 | $transform = null; |
||||||
157 | } |
||||||
158 | // If there's no transform requested, return `null` so other plugins have a crack at it |
||||||
159 | if ($transform === null) { |
||||||
160 | return null; |
||||||
161 | } |
||||||
162 | // If we're passed in null, make a dummy AssetTransform model for Thumbor |
||||||
163 | // For backwards compatibility |
||||||
164 | if (ImageOptimize::$plugin->transformMethod instanceof ThumborImageTransform) { |
||||||
165 | $transform = new AssetTransform([ |
||||||
0 ignored issues
–
show
|
|||||||
166 | 'width' => $asset->width, |
||||||
167 | 'interlace' => 'line', |
||||||
168 | ]); |
||||||
0 ignored issues
–
show
For multi-line function calls, the closing parenthesis should be on a new line.
If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line: someFunctionCall(
$firstArgument,
$secondArgument,
$thirdArgument
); // Closing parenthesis on a new line.
![]() |
|||||||
169 | } |
||||||
170 | // If we're passed an array, make an AssetTransform model out of it |
||||||
171 | if (is_array($transform)) { |
||||||
172 | $transform = new AssetTransform($transform); |
||||||
173 | } |
||||||
174 | // If we're passing in a string, look up the asset transform in the db |
||||||
175 | if (is_string($transform)) { |
||||||
176 | $imageTransforms = Craft::$app->getImageTransforms(); |
||||||
177 | $transform = $imageTransforms->getTransformByHandle($transform); |
||||||
178 | } |
||||||
179 | $finalFormat = empty($transform['format']) ? $asset->getExtension() : $transform['format']; |
||||||
180 | // Normalize the extension to lowercase, for some transform methods that require this |
||||||
181 | $finalFormat = strtolower($finalFormat); |
||||||
182 | // Special-case for 'jpeg' |
||||||
183 | if ($finalFormat === 'jpeg') { |
||||||
184 | $finalFormat = 'jpg'; |
||||||
185 | } |
||||||
186 | // If the final format is an SVG, don't attempt to transform it |
||||||
187 | if ($finalFormat === 'svg') { |
||||||
188 | return null; |
||||||
189 | } |
||||||
190 | // Normalize the extension to lowercase, for some transform methods that require this |
||||||
191 | if (!empty($transform) && !empty($finalFormat)) { |
||||||
192 | $format = $transform['format'] ?? null; |
||||||
193 | $transform['format'] = $format === null ? null : strtolower($finalFormat); |
||||||
194 | } |
||||||
195 | // Generate an image transform url |
||||||
196 | $url = ImageOptimize::$plugin->transformMethod->getTransformUrl( |
||||||
197 | $asset, |
||||||
198 | $transform |
||||||
199 | ); |
||||||
200 | } |
||||||
201 | Craft::endProfile('handleGetAssetUrlEvent', __METHOD__); |
||||||
202 | |||||||
203 | return $url; |
||||||
204 | } |
||||||
205 | |||||||
206 | /** |
||||||
207 | * Handle responding to EVENT_GET_ASSET_THUMB_URL events |
||||||
208 | * |
||||||
209 | * @param DefineAssetThumbUrlEvent $event |
||||||
0 ignored issues
–
show
|
|||||||
210 | * |
||||||
211 | * @return ?string |
||||||
212 | */ |
||||||
213 | public function handleGetAssetThumbUrlEvent(DefineAssetThumbUrlEvent $event): ?string |
||||||
214 | { |
||||||
215 | Craft::beginProfile('handleGetAssetThumbUrlEvent', __METHOD__); |
||||||
216 | $url = $event->url; |
||||||
217 | if (!ImageOptimize::$plugin->transformMethod instanceof CraftImageTransform) { |
||||||
218 | $asset = $event->asset; |
||||||
219 | if (ImageHelper::canManipulateAsImage($asset->getExtension())) { |
||||||
220 | $transform = new AssetTransform([ |
||||||
0 ignored issues
–
show
|
|||||||
221 | 'width' => $event->width, |
||||||
222 | 'height' => $event->height, |
||||||
223 | 'interlace' => 'line', |
||||||
224 | ]); |
||||||
0 ignored issues
–
show
For multi-line function calls, the closing parenthesis should be on a new line.
If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line: someFunctionCall(
$firstArgument,
$secondArgument,
$thirdArgument
); // Closing parenthesis on a new line.
![]() |
|||||||
225 | /** @var ImageTransform $transformMethod */ |
||||||
0 ignored issues
–
show
|
|||||||
226 | $transformMethod = ImageOptimize::$plugin->transformMethod; |
||||||
227 | $finalFormat = empty($transform['format']) ? $asset->getExtension() : $transform['format']; |
||||||
228 | // Normalize the extension to lowercase, for some transform methods that require this |
||||||
229 | $finalFormat = strtolower($finalFormat); |
||||||
230 | // Special-case for 'jpeg' |
||||||
231 | if ($finalFormat === 'jpeg') { |
||||||
232 | $finalFormat = 'jpg'; |
||||||
233 | } |
||||||
234 | // If the final format is an SVG, don't attempt to transform it |
||||||
235 | if ($finalFormat === 'svg') { |
||||||
236 | return null; |
||||||
237 | } |
||||||
238 | // Generate an image transform url |
||||||
239 | if ($transformMethod->hasProperty('generateTransformsBeforePageLoad')) { |
||||||
240 | // This is a dynamic property that some image transforms have |
||||||
241 | /** @phpstan-ignore-next-line */ |
||||||
0 ignored issues
–
show
|
|||||||
242 | $transformMethod->generateTransformsBeforePageLoad = true; |
||||||
0 ignored issues
–
show
The property
generateTransformsBeforePageLoad does not exist on nystudio107\imageoptimiz...ansforms\ImageTransform . Since you implemented __set , consider adding a @property annotation.
![]() |
|||||||
243 | } |
||||||
244 | $url = $transformMethod->getTransformUrl($asset, $transform); |
||||||
0 ignored issues
–
show
Are you sure the assignment to
$url is correct as $transformMethod->getTra...Url($asset, $transform) targeting nystudio107\imageoptimiz...form::getTransformUrl() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
245 | } |
||||||
246 | } |
||||||
247 | Craft::endProfile('handleGetAssetThumbUrlEvent', __METHOD__); |
||||||
248 | |||||||
249 | return $url; |
||||||
250 | } |
||||||
251 | |||||||
252 | /** |
||||||
253 | * Returns whether `.webp` is a format supported by the server |
||||||
254 | * |
||||||
255 | * @return bool |
||||||
256 | */ |
||||||
257 | public function serverSupportsWebP(): bool |
||||||
258 | { |
||||||
259 | $result = false; |
||||||
260 | $variantCreators = ImageOptimize::$plugin->optimize->getActiveVariantCreators(); |
||||||
261 | foreach ($variantCreators as $variantCreator) { |
||||||
262 | if ($variantCreator['creator'] === 'cwebp' && $variantCreator['installed']) { |
||||||
263 | $result = true; |
||||||
264 | } |
||||||
265 | } |
||||||
266 | |||||||
267 | return $result; |
||||||
268 | } |
||||||
269 | |||||||
270 | /** |
||||||
271 | * Render the LazySizes fallback JS |
||||||
272 | * |
||||||
273 | * @param array $scriptAttrs |
||||||
0 ignored issues
–
show
|
|||||||
274 | * @param array $variables |
||||||
0 ignored issues
–
show
|
|||||||
275 | * @return string |
||||||
0 ignored issues
–
show
|
|||||||
276 | */ |
||||||
277 | public function renderLazySizesFallbackJs(array $scriptAttrs = [], array $variables = []): string |
||||||
278 | { |
||||||
279 | $minifier = 'minify'; |
||||||
280 | $vars = array_merge([ |
||||||
0 ignored issues
–
show
|
|||||||
281 | 'scriptSrc' => 'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.0/lazysizes.min.js', |
||||||
282 | ], |
||||||
0 ignored issues
–
show
|
|||||||
283 | $variables |
||||||
284 | ); |
||||||
285 | $content = PluginTemplateHelper::renderPluginTemplate( |
||||||
286 | 'frontend/lazysizes-fallback.twig.js', |
||||||
287 | $vars, |
||||||
288 | $minifier |
||||||
289 | ); |
||||||
290 | if ($scriptAttrs !== null) { |
||||||
0 ignored issues
–
show
|
|||||||
291 | $attrs = array_merge([ |
||||||
0 ignored issues
–
show
|
|||||||
292 | ], |
||||||
0 ignored issues
–
show
|
|||||||
293 | $scriptAttrs |
||||||
294 | ); |
||||||
295 | $content = Html::tag('script', $content, $attrs); |
||||||
296 | } |
||||||
297 | |||||||
298 | return $content; |
||||||
299 | } |
||||||
300 | |||||||
301 | /** |
||||||
302 | * Render the LazySizes fallback JS |
||||||
303 | * |
||||||
304 | * @param array $scriptAttrs |
||||||
0 ignored issues
–
show
|
|||||||
305 | * @param array $variables |
||||||
0 ignored issues
–
show
|
|||||||
306 | * @return string |
||||||
0 ignored issues
–
show
|
|||||||
307 | */ |
||||||
308 | public function renderLazySizesJs(array $scriptAttrs = [], array $variables = []): string |
||||||
309 | { |
||||||
310 | $minifier = 'minify'; |
||||||
311 | $vars = array_merge([ |
||||||
0 ignored issues
–
show
|
|||||||
312 | 'scriptSrc' => 'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.0/lazysizes.min.js', |
||||||
313 | ], |
||||||
0 ignored issues
–
show
|
|||||||
314 | $variables |
||||||
315 | ); |
||||||
316 | $content = PluginTemplateHelper::renderPluginTemplate( |
||||||
317 | 'frontend/lazysizes.twig.js', |
||||||
318 | $vars, |
||||||
319 | $minifier |
||||||
320 | ); |
||||||
321 | if ($scriptAttrs !== null) { |
||||||
0 ignored issues
–
show
|
|||||||
322 | $attrs = array_merge([ |
||||||
0 ignored issues
–
show
|
|||||||
323 | ], |
||||||
0 ignored issues
–
show
|
|||||||
324 | $scriptAttrs |
||||||
325 | ); |
||||||
326 | $content = Html::tag('script', $content, $attrs); |
||||||
327 | } |
||||||
328 | |||||||
329 | return $content; |
||||||
330 | } |
||||||
331 | |||||||
332 | /** |
||||||
333 | * Handle responding to EVENT_TRANSFORM_IMAGE events |
||||||
334 | * |
||||||
335 | * @param ImageTransformerOperationEvent $event |
||||||
0 ignored issues
–
show
|
|||||||
336 | * |
||||||
337 | * @return ?string |
||||||
338 | * @throws InvalidConfigException |
||||||
339 | */ |
||||||
340 | public function handleGenerateTransformEvent(ImageTransformerOperationEvent $event): ?string |
||||||
341 | { |
||||||
342 | Craft::beginProfile('handleGenerateTransformEvent', __METHOD__); |
||||||
343 | $tempPath = null; |
||||||
344 | // Only do this for local Craft transforms |
||||||
345 | $asset = $event->asset; |
||||||
346 | |||||||
347 | if (ImageOptimize::$plugin->transformMethod instanceof CraftImageTransform) { |
||||||
348 | // Apply any filters to the image |
||||||
349 | $imageTransformIndex = $event->imageTransformIndex; |
||||||
350 | $image = $event->image; |
||||||
351 | |||||||
352 | if ($imageTransformIndex->getTransform() !== null) { |
||||||
353 | $this->applyFiltersToImage($imageTransformIndex->getTransform(), $asset, $image); |
||||||
0 ignored issues
–
show
It seems like
$image can also be of type null ; however, parameter $image of nystudio107\imageoptimiz...::applyFiltersToImage() does only seem to accept craft\base\Image , 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
![]() |
|||||||
354 | } |
||||||
355 | // Save the transformed image to a temp file |
||||||
356 | $tempPath = $this->saveTransformToTempFile( |
||||||
357 | $imageTransformIndex, |
||||||
358 | $image |
||||||
0 ignored issues
–
show
It seems like
$image can also be of type null ; however, parameter $image of nystudio107\imageoptimiz...veTransformToTempFile() does only seem to accept craft\base\Image , 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
![]() |
|||||||
359 | ); |
||||||
360 | $originalFileSize = @filesize($tempPath); |
||||||
361 | // Optimize the image |
||||||
362 | $this->optimizeImage( |
||||||
363 | $imageTransformIndex, |
||||||
364 | $tempPath |
||||||
365 | ); |
||||||
366 | clearstatcache(true, $tempPath); |
||||||
367 | // Log the results of the image optimization |
||||||
368 | $optimizedFileSize = @filesize($tempPath); |
||||||
369 | $message = |
||||||
0 ignored issues
–
show
|
|||||||
370 | pathinfo($imageTransformIndex->filename, PATHINFO_FILENAME) |
||||||
0 ignored issues
–
show
Are you sure
pathinfo($imageTransform...ices\PATHINFO_FILENAME) of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$imageTransformIndex->filename can also be of type null ; however, parameter $path of pathinfo() 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
![]() |
|||||||
371 | . '.' |
||||||
372 | . $imageTransformIndex->detectedFormat |
||||||
373 | . ' -> ' |
||||||
374 | . Craft::t('image-optimize', 'Original') |
||||||
375 | . ': ' |
||||||
376 | . $this->humanFileSize($originalFileSize, 1) |
||||||
377 | . ', ' |
||||||
378 | . Craft::t('image-optimize', 'Optimized') |
||||||
379 | . ': ' |
||||||
380 | . $this->humanFileSize($optimizedFileSize, 1) |
||||||
381 | . ' -> ' |
||||||
382 | . Craft::t('image-optimize', 'Savings') |
||||||
383 | . ': ' |
||||||
384 | . number_format(abs(100 - (($optimizedFileSize * 100) / $originalFileSize)), 1) |
||||||
385 | . '%'; |
||||||
386 | Craft::info($message, __METHOD__); |
||||||
387 | if (Craft::$app instanceof ConsoleApplication) { |
||||||
388 | echo $message . PHP_EOL; |
||||||
389 | } |
||||||
390 | // Create any image variants |
||||||
391 | $this->createImageVariants( |
||||||
392 | $imageTransformIndex, |
||||||
393 | $asset, |
||||||
394 | $tempPath, |
||||||
395 | $event->path |
||||||
396 | ); |
||||||
397 | } |
||||||
398 | Craft::endProfile('handleGenerateTransformEvent', __METHOD__); |
||||||
399 | |||||||
400 | return $tempPath; |
||||||
401 | } |
||||||
402 | |||||||
403 | /** |
||||||
404 | * Handle cleaning up any variant creator images |
||||||
405 | * |
||||||
406 | * @param ImageTransformerOperationEvent $event |
||||||
0 ignored issues
–
show
|
|||||||
407 | */ |
||||||
0 ignored issues
–
show
|
|||||||
408 | public function handleAfterDeleteTransformsEvent(ImageTransformerOperationEvent $event): void |
||||||
409 | { |
||||||
410 | // Only do this for local Craft transforms |
||||||
411 | if (ImageOptimize::$plugin->transformMethod instanceof CraftImageTransform) { |
||||||
412 | $this->cleanupImageVariants($event->asset, $event->imageTransformIndex, $event->path); |
||||||
413 | } |
||||||
414 | } |
||||||
415 | |||||||
416 | /** |
||||||
417 | * Save out the image to a temp file |
||||||
418 | * |
||||||
419 | * @param AssetTransformIndex $index |
||||||
0 ignored issues
–
show
|
|||||||
420 | * @param Image $image |
||||||
0 ignored issues
–
show
|
|||||||
421 | * |
||||||
422 | * @return string |
||||||
423 | */ |
||||||
424 | public function saveTransformToTempFile(AssetTransformIndex $index, Image $image): string |
||||||
425 | { |
||||||
426 | $tempFilename = uniqid(pathinfo($index->filename, PATHINFO_FILENAME), true) . '.' . $index->detectedFormat; |
||||||
0 ignored issues
–
show
It seems like
pathinfo($index->filenam...ices\PATHINFO_FILENAME) can also be of type array ; however, parameter $prefix of uniqid() 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
![]() It seems like
$index->filename can also be of type null ; however, parameter $path of pathinfo() 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
![]() |
|||||||
427 | $tempPath = Craft::$app->getPath()->getTempPath() . DIRECTORY_SEPARATOR . $tempFilename; |
||||||
428 | try { |
||||||
429 | $image->saveAs($tempPath); |
||||||
430 | } catch (ImageException $e) { |
||||||
431 | Craft::error('Transformed image save failed: ' . $e->getMessage(), __METHOD__); |
||||||
432 | } |
||||||
433 | Craft::info('Transformed image saved to: ' . $tempPath, __METHOD__); |
||||||
434 | |||||||
435 | return $tempPath; |
||||||
436 | } |
||||||
437 | |||||||
438 | /** |
||||||
439 | * Run any image post-processing/optimization on the image file |
||||||
440 | * |
||||||
441 | * @param AssetTransformIndex $index |
||||||
0 ignored issues
–
show
|
|||||||
442 | * @param string $tempPath |
||||||
0 ignored issues
–
show
|
|||||||
443 | */ |
||||||
0 ignored issues
–
show
|
|||||||
444 | public function optimizeImage(AssetTransformIndex $index, string $tempPath): void |
||||||
445 | { |
||||||
446 | Craft::beginProfile('optimizeImage', __METHOD__); |
||||||
447 | /** @var Settings $settings */ |
||||||
0 ignored issues
–
show
|
|||||||
448 | $settings = ImageOptimize::$plugin->getSettings(); |
||||||
449 | // Get the active processors for the transform format |
||||||
450 | $activeImageProcessors = $settings->activeImageProcessors; |
||||||
451 | $fileFormat = $index->detectedFormat ?? $index->format; |
||||||
452 | $fileFormat = strtolower($fileFormat); |
||||||
0 ignored issues
–
show
It seems like
$fileFormat can also be of type null ; however, parameter $string of strtolower() 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
![]() |
|||||||
453 | // Special-case for 'jpeg' |
||||||
454 | if ($fileFormat === 'jpeg') { |
||||||
455 | $fileFormat = 'jpg'; |
||||||
456 | } |
||||||
457 | if (!empty($activeImageProcessors[$fileFormat])) { |
||||||
458 | // Iterate through all the processors for this format |
||||||
459 | $imageProcessors = $settings->imageProcessors; |
||||||
460 | foreach ($activeImageProcessors[$fileFormat] as $processor) { |
||||||
461 | if (!empty($processor) && !empty($imageProcessors[$processor])) { |
||||||
462 | $this->executeImageProcessor($imageProcessors[$processor], $tempPath); |
||||||
463 | } |
||||||
464 | } |
||||||
465 | } |
||||||
466 | Craft::endProfile('optimizeImage', __METHOD__); |
||||||
467 | } |
||||||
468 | |||||||
469 | /** |
||||||
470 | * Translate bytes into something human-readable |
||||||
471 | * |
||||||
472 | * @param $bytes |
||||||
0 ignored issues
–
show
|
|||||||
473 | * @param int $decimals |
||||||
0 ignored issues
–
show
|
|||||||
474 | * |
||||||
475 | * @return string |
||||||
476 | */ |
||||||
477 | public function humanFileSize($bytes, int $decimals = 1): string |
||||||
478 | { |
||||||
479 | $oldSize = Craft::$app->formatter->sizeFormatBase; |
||||||
480 | Craft::$app->formatter->sizeFormatBase = 1000; |
||||||
481 | $result = Craft::$app->formatter->asShortSize($bytes, $decimals); |
||||||
482 | Craft::$app->formatter->sizeFormatBase = $oldSize; |
||||||
483 | |||||||
484 | return $result; |
||||||
485 | } |
||||||
486 | |||||||
487 | /** |
||||||
488 | * Create any image variants for the image file |
||||||
489 | * |
||||||
490 | * @param AssetTransformIndex $index |
||||||
0 ignored issues
–
show
|
|||||||
491 | * @param Asset $asset |
||||||
0 ignored issues
–
show
|
|||||||
492 | * @param string $tempPath |
||||||
0 ignored issues
–
show
|
|||||||
493 | * @param string $uri |
||||||
0 ignored issues
–
show
|
|||||||
494 | */ |
||||||
0 ignored issues
–
show
|
|||||||
495 | public function createImageVariants(AssetTransformIndex $index, Asset $asset, string $tempPath, string $uri): void |
||||||
496 | { |
||||||
497 | Craft::beginProfile('createImageVariants', __METHOD__); |
||||||
498 | /** @var Settings $settings */ |
||||||
0 ignored issues
–
show
|
|||||||
499 | $settings = ImageOptimize::$plugin->getSettings(); |
||||||
500 | // Get the active image variant creators |
||||||
501 | $activeImageVariantCreators = $settings->activeImageVariantCreators; |
||||||
502 | $fileFormat = $index->detectedFormat ?? $index->format; |
||||||
503 | $fileFormat = strtolower($fileFormat); |
||||||
0 ignored issues
–
show
It seems like
$fileFormat can also be of type null ; however, parameter $string of strtolower() 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
![]() |
|||||||
504 | // Special-case for 'jpeg' |
||||||
505 | if ($fileFormat === 'jpeg') { |
||||||
506 | $fileFormat = 'jpg'; |
||||||
507 | } |
||||||
508 | if (!empty($activeImageVariantCreators[$fileFormat])) { |
||||||
509 | // Iterate through all of the image variant creators for this format |
||||||
510 | $imageVariantCreators = $settings->imageVariantCreators; |
||||||
511 | foreach ($activeImageVariantCreators[$fileFormat] as $variantCreator) { |
||||||
512 | if (!empty($variantCreator) && !empty($imageVariantCreators[$variantCreator])) { |
||||||
513 | // Create the image variant in a temporary folder |
||||||
514 | $generalConfig = Craft::$app->getConfig()->getGeneral(); |
||||||
515 | $quality = $index->transform->quality ?: $generalConfig->defaultImageQuality; |
||||||
516 | $outputPath = $this->executeVariantCreator( |
||||||
517 | $imageVariantCreators[$variantCreator], |
||||||
518 | $tempPath, |
||||||
519 | $quality |
||||||
520 | ); |
||||||
521 | if ($outputPath !== null) { |
||||||
522 | // Get info on the original and the created variant |
||||||
523 | $originalFileSize = @filesize($tempPath); |
||||||
524 | $variantFileSize = @filesize($outputPath); |
||||||
525 | $message = |
||||||
0 ignored issues
–
show
|
|||||||
526 | pathinfo($tempPath, PATHINFO_FILENAME) |
||||||
0 ignored issues
–
show
Are you sure
pathinfo($tempPath, nyst...ices\PATHINFO_FILENAME) of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
527 | . '.' |
||||||
528 | . pathinfo($tempPath, PATHINFO_EXTENSION) |
||||||
0 ignored issues
–
show
Are you sure
pathinfo($tempPath, nyst...ces\PATHINFO_EXTENSION) of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
529 | . ' -> ' |
||||||
530 | . pathinfo($outputPath, PATHINFO_FILENAME) |
||||||
0 ignored issues
–
show
Are you sure
pathinfo($outputPath, ny...ices\PATHINFO_FILENAME) of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
531 | . '.' |
||||||
532 | . pathinfo($outputPath, PATHINFO_EXTENSION) |
||||||
0 ignored issues
–
show
Are you sure
pathinfo($outputPath, ny...ces\PATHINFO_EXTENSION) of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
533 | . ' -> ' |
||||||
534 | . Craft::t('image-optimize', 'Original') |
||||||
535 | . ': ' |
||||||
536 | . $this->humanFileSize($originalFileSize, 1) |
||||||
537 | . ', ' |
||||||
538 | . Craft::t('image-optimize', 'Variant') |
||||||
539 | . ': ' |
||||||
540 | . $this->humanFileSize($variantFileSize, 1) |
||||||
541 | . ' -> ' |
||||||
542 | . Craft::t('image-optimize', 'Savings') |
||||||
543 | . ': ' |
||||||
544 | . number_format(abs(100 - (($variantFileSize * 100) / $originalFileSize)), 1) |
||||||
545 | . '%'; |
||||||
546 | Craft::info($message, __METHOD__); |
||||||
547 | if (Craft::$app instanceof ConsoleApplication) { |
||||||
548 | echo $message . PHP_EOL; |
||||||
549 | } |
||||||
550 | // Copy the image variant into place |
||||||
551 | $this->copyImageVariantToVolume( |
||||||
552 | $imageVariantCreators[$variantCreator], |
||||||
553 | $asset, |
||||||
554 | $index, |
||||||
555 | $outputPath, |
||||||
556 | $uri |
||||||
557 | ); |
||||||
558 | } |
||||||
559 | } |
||||||
560 | } |
||||||
561 | } |
||||||
562 | Craft::endProfile('createImageVariants', __METHOD__); |
||||||
563 | } |
||||||
564 | |||||||
565 | /** |
||||||
566 | * Return an array of active image processors |
||||||
567 | * |
||||||
568 | * @return array |
||||||
569 | */ |
||||||
570 | public function getActiveImageProcessors(): array |
||||||
571 | { |
||||||
572 | $result = []; |
||||||
573 | /** @var Settings $settings */ |
||||||
0 ignored issues
–
show
|
|||||||
574 | $settings = ImageOptimize::$plugin->getSettings(); |
||||||
575 | // Get the active processors for the transform format |
||||||
576 | $activeImageProcessors = $settings->activeImageProcessors; |
||||||
577 | foreach ($activeImageProcessors as $imageFormat => $imageProcessor) { |
||||||
578 | // Iterate through all the processors for this format |
||||||
579 | $imageProcessors = $settings->imageProcessors; |
||||||
580 | foreach ($activeImageProcessors[$imageFormat] as $processor) { |
||||||
581 | if (!empty($imageProcessors[$processor])) { |
||||||
582 | $thisImageProcessor = $imageProcessors[$processor]; |
||||||
583 | $result[] = [ |
||||||
584 | 'format' => $imageFormat, |
||||||
585 | 'creator' => $processor, |
||||||
586 | 'command' => $thisImageProcessor['commandPath'] |
||||||
587 | . ' ' |
||||||
588 | . $thisImageProcessor['commandOptions'], |
||||||
589 | 'installed' => is_file($thisImageProcessor['commandPath']), |
||||||
590 | ]; |
||||||
591 | } |
||||||
592 | } |
||||||
593 | } |
||||||
594 | |||||||
595 | return $result; |
||||||
596 | } |
||||||
597 | |||||||
598 | /** |
||||||
599 | * Return an array of active image variant creators |
||||||
600 | * |
||||||
601 | * @return array |
||||||
602 | */ |
||||||
603 | public function getActiveVariantCreators(): array |
||||||
604 | { |
||||||
605 | $result = []; |
||||||
606 | /** @var Settings $settings */ |
||||||
0 ignored issues
–
show
|
|||||||
607 | $settings = ImageOptimize::$plugin->getSettings(); |
||||||
608 | // Get the active image variant creators |
||||||
609 | $activeImageVariantCreators = $settings->activeImageVariantCreators; |
||||||
610 | foreach ($activeImageVariantCreators as $imageFormat => $imageCreator) { |
||||||
611 | // Iterate through all the image variant creators for this format |
||||||
612 | $imageVariantCreators = $settings->imageVariantCreators; |
||||||
613 | foreach ($activeImageVariantCreators[$imageFormat] as $variantCreator) { |
||||||
614 | if (!empty($imageVariantCreators[$variantCreator])) { |
||||||
615 | $thisVariantCreator = $imageVariantCreators[$variantCreator]; |
||||||
616 | $result[] = [ |
||||||
617 | 'format' => $imageFormat, |
||||||
618 | 'creator' => $variantCreator, |
||||||
619 | 'command' => $thisVariantCreator['commandPath'] |
||||||
620 | . ' ' |
||||||
621 | . $thisVariantCreator['commandOptions'], |
||||||
622 | 'installed' => is_file($thisVariantCreator['commandPath']), |
||||||
623 | ]; |
||||||
624 | } |
||||||
625 | } |
||||||
626 | } |
||||||
627 | |||||||
628 | return $result; |
||||||
629 | } |
||||||
630 | |||||||
631 | // Protected Methods |
||||||
632 | // ========================================================================= |
||||||
633 | |||||||
634 | /** @noinspection PhpUnusedParameterInspection |
||||||
0 ignored issues
–
show
|
|||||||
635 | * @param AssetTransform $transform |
||||||
0 ignored issues
–
show
|
|||||||
636 | * @param Asset $asset |
||||||
0 ignored issues
–
show
|
|||||||
637 | * @param Image $image |
||||||
0 ignored issues
–
show
|
|||||||
638 | */ |
||||||
0 ignored issues
–
show
|
|||||||
639 | protected function applyFiltersToImage(AssetTransform $transform, Asset $asset, Image $image): void |
||||||
640 | { |
||||||
641 | /** @var Settings $settings */ |
||||||
0 ignored issues
–
show
|
|||||||
642 | $settings = ImageOptimize::$plugin->getSettings(); |
||||||
643 | // Only try to apply filters to Raster images |
||||||
644 | if ($image instanceof Raster && $asset->getWidth() > 0 && $asset->getHeight() > 0) { |
||||||
645 | $imagineImage = $image->getImagineImage(); |
||||||
646 | // Handle auto-sharpening scaled down images |
||||||
647 | if ($imagineImage !== null && $settings->autoSharpenScaledImages) { |
||||||
648 | // See if the image has been scaled >= 50% |
||||||
649 | $widthScale = (int)(($image->getWidth() / $asset->getWidth()) * 100); |
||||||
650 | $heightScale = (int)(($image->getHeight() / $asset->getHeight()) * 100); |
||||||
651 | if (($widthScale >= $settings->sharpenScaledImagePercentage) || ($heightScale >= $settings->sharpenScaledImagePercentage)) { |
||||||
652 | $imagineImage->effects() |
||||||
653 | ->sharpen(); |
||||||
654 | Craft::debug( |
||||||
655 | Craft::t( |
||||||
656 | 'image-optimize', |
||||||
657 | 'Image transform >= 50%, sharpened the transformed image: {name}', |
||||||
658 | [ |
||||||
659 | 'name' => $asset->title, |
||||||
660 | ] |
||||||
661 | ), |
||||||
662 | __METHOD__ |
||||||
663 | ); |
||||||
664 | } |
||||||
665 | } |
||||||
666 | } |
||||||
667 | } |
||||||
668 | |||||||
669 | /** |
||||||
0 ignored issues
–
show
|
|||||||
670 | * @param $thisProcessor |
||||||
0 ignored issues
–
show
|
|||||||
671 | * @param string $tempPath |
||||||
0 ignored issues
–
show
|
|||||||
672 | */ |
||||||
0 ignored issues
–
show
|
|||||||
673 | protected function executeImageProcessor($thisProcessor, string $tempPath): void |
||||||
674 | { |
||||||
675 | // Make sure the command exists |
||||||
676 | if (is_file($thisProcessor['commandPath'])) { |
||||||
677 | // Set any options for the command |
||||||
678 | $commandOptions = ''; |
||||||
679 | if (!empty($thisProcessor['commandOptions'])) { |
||||||
680 | $commandOptions = ' ' |
||||||
681 | . $thisProcessor['commandOptions'] |
||||||
682 | . ' '; |
||||||
683 | } |
||||||
684 | // Redirect the command output if necessary for this processor |
||||||
685 | $outputFileFlag = ''; |
||||||
686 | if (!empty($thisProcessor['commandOutputFileFlag'])) { |
||||||
687 | $outputFileFlag = ' ' |
||||||
688 | . $thisProcessor['commandOutputFileFlag'] |
||||||
689 | . ' ' |
||||||
690 | . escapeshellarg($tempPath) |
||||||
691 | . ' '; |
||||||
692 | } |
||||||
693 | // If both $commandOptions & $outputFileFlag are empty, pad it with a space |
||||||
694 | if (empty($commandOptions) && empty($outputFileFlag)) { |
||||||
695 | $commandOptions = ' '; |
||||||
696 | } |
||||||
697 | // Build the command to execute |
||||||
698 | $cmd = |
||||||
0 ignored issues
–
show
|
|||||||
699 | $thisProcessor['commandPath'] |
||||||
700 | . $commandOptions |
||||||
701 | . $outputFileFlag |
||||||
702 | . escapeshellarg($tempPath); |
||||||
703 | // Execute the command |
||||||
704 | $shellOutput = $this->executeShellCommand($cmd); |
||||||
705 | Craft::info($cmd . "\n" . $shellOutput, __METHOD__); |
||||||
706 | } else { |
||||||
707 | Craft::error( |
||||||
708 | $thisProcessor['commandPath'] |
||||||
709 | . ' ' |
||||||
710 | . Craft::t('image-optimize', 'does not exist'), |
||||||
711 | __METHOD__ |
||||||
712 | ); |
||||||
713 | } |
||||||
714 | } |
||||||
715 | |||||||
716 | /** |
||||||
717 | * Execute a shell command |
||||||
718 | * |
||||||
719 | * @param string $command |
||||||
0 ignored issues
–
show
|
|||||||
720 | * |
||||||
721 | * @return string |
||||||
722 | */ |
||||||
723 | protected function executeShellCommand(string $command): string |
||||||
724 | { |
||||||
725 | // Create the shell command |
||||||
726 | $shellCommand = new ShellCommand(); |
||||||
727 | $shellCommand->setCommand($command); |
||||||
728 | |||||||
729 | // If we don't have proc_open, maybe we've got exec |
||||||
730 | if (!function_exists('proc_open') && function_exists('exec')) { |
||||||
731 | $shellCommand->useExec = true; |
||||||
732 | } |
||||||
733 | |||||||
734 | // Return the result of the command's output or error |
||||||
735 | if ($shellCommand->execute()) { |
||||||
736 | $result = $shellCommand->getOutput(); |
||||||
737 | } else { |
||||||
738 | $result = $shellCommand->getError(); |
||||||
739 | } |
||||||
740 | |||||||
741 | return $result; |
||||||
742 | } |
||||||
743 | |||||||
744 | /** |
||||||
0 ignored issues
–
show
|
|||||||
745 | * @param $variantCreatorCommand |
||||||
0 ignored issues
–
show
|
|||||||
746 | * @param string $tempPath |
||||||
0 ignored issues
–
show
|
|||||||
747 | * @param int $imageQuality |
||||||
0 ignored issues
–
show
|
|||||||
748 | * |
||||||
749 | * @return ?string the path to the created variant |
||||||
750 | */ |
||||||
751 | protected function executeVariantCreator($variantCreatorCommand, string $tempPath, int $imageQuality): ?string |
||||||
752 | { |
||||||
753 | $outputPath = $tempPath; |
||||||
754 | // Make sure the command exists |
||||||
755 | if (is_file($variantCreatorCommand['commandPath'])) { |
||||||
756 | // Get the output file for this image variant |
||||||
757 | $outputPath .= '.' . $variantCreatorCommand['imageVariantExtension']; |
||||||
758 | // Set any options for the command |
||||||
759 | $commandOptions = ''; |
||||||
760 | if (!empty($variantCreatorCommand['commandOptions'])) { |
||||||
761 | $commandOptions = ' ' |
||||||
762 | . $variantCreatorCommand['commandOptions'] |
||||||
763 | . ' '; |
||||||
764 | } |
||||||
765 | // Redirect the command output if necessary for this variantCreator |
||||||
766 | $outputFileFlag = ''; |
||||||
767 | if (!empty($variantCreatorCommand['commandOutputFileFlag'])) { |
||||||
768 | $outputFileFlag = ' ' |
||||||
769 | . $variantCreatorCommand['commandOutputFileFlag'] |
||||||
770 | . ' ' |
||||||
771 | . escapeshellarg($outputPath) |
||||||
772 | . ' '; |
||||||
773 | } |
||||||
774 | // Get the quality setting of this transform |
||||||
775 | $commandQualityFlag = ''; |
||||||
776 | if (!empty($variantCreatorCommand['commandQualityFlag'])) { |
||||||
777 | $commandQualityFlag = ' ' |
||||||
778 | . $variantCreatorCommand['commandQualityFlag'] |
||||||
779 | . ' ' |
||||||
780 | . $imageQuality |
||||||
781 | . ' '; |
||||||
782 | } |
||||||
783 | // Build the command to execute |
||||||
784 | $cmd = |
||||||
0 ignored issues
–
show
|
|||||||
785 | $variantCreatorCommand['commandPath'] |
||||||
786 | . $commandOptions |
||||||
787 | . $commandQualityFlag |
||||||
788 | . $outputFileFlag |
||||||
789 | . escapeshellarg($tempPath); |
||||||
790 | // Execute the command |
||||||
791 | $shellOutput = $this->executeShellCommand($cmd); |
||||||
792 | Craft::info($cmd . "\n" . $shellOutput, __METHOD__); |
||||||
793 | } else { |
||||||
794 | Craft::error( |
||||||
795 | $variantCreatorCommand['commandPath'] |
||||||
796 | . ' ' |
||||||
797 | . Craft::t('image-optimize', 'does not exist'), |
||||||
798 | __METHOD__ |
||||||
799 | ); |
||||||
800 | $outputPath = null; |
||||||
801 | } |
||||||
802 | |||||||
803 | return $outputPath; |
||||||
804 | } |
||||||
805 | |||||||
806 | /** |
||||||
0 ignored issues
–
show
|
|||||||
807 | * @param Asset $asset |
||||||
0 ignored issues
–
show
|
|||||||
808 | * @param AssetTransformIndex $transformIndex |
||||||
0 ignored issues
–
show
|
|||||||
809 | * @param string $uri |
||||||
0 ignored issues
–
show
|
|||||||
810 | */ |
||||||
0 ignored issues
–
show
|
|||||||
811 | protected function cleanupImageVariants(Asset $asset, AssetTransformIndex $transformIndex, string $uri): void |
||||||
812 | { |
||||||
813 | /** @var Settings $settings */ |
||||||
0 ignored issues
–
show
|
|||||||
814 | $settings = ImageOptimize::$plugin->getSettings(); |
||||||
815 | // Get the active image variant creators |
||||||
816 | $activeImageVariantCreators = $settings->activeImageVariantCreators; |
||||||
817 | $fileFormat = $transformIndex->detectedFormat ?? $transformIndex->format ?? $asset->getExtension(); |
||||||
818 | $fileFormat = empty($fileFormat) ? $asset->getExtension() : $fileFormat; |
||||||
819 | // Normalize the extension to lowercase, for some transform methods that require this |
||||||
820 | $fileFormat = strtolower($fileFormat); |
||||||
821 | // Special-case for 'jpeg' |
||||||
822 | if ($fileFormat === 'jpeg') { |
||||||
823 | $fileFormat = 'jpg'; |
||||||
824 | } |
||||||
825 | if (!empty($activeImageVariantCreators[$fileFormat])) { |
||||||
826 | // Iterate through all the image variant creators for this format |
||||||
827 | $imageVariantCreators = $settings->imageVariantCreators; |
||||||
828 | if (!empty($activeImageVariantCreators[$fileFormat])) { |
||||||
829 | foreach ($activeImageVariantCreators[$fileFormat] as $variantCreator) { |
||||||
830 | if (!empty($variantCreator) && !empty($imageVariantCreators[$variantCreator])) { |
||||||
831 | // Create the image variant in a temporary folder |
||||||
832 | $variantCreatorCommand = $imageVariantCreators[$variantCreator]; |
||||||
833 | try { |
||||||
834 | $fs = $asset->getVolume()->getTransformFs(); |
||||||
835 | } catch (InvalidConfigException $invalidConfigException) { |
||||||
836 | $fs = null; |
||||||
837 | Craft::error( |
||||||
838 | 'Asset file system error: ' . $invalidConfigException->getMessage(), |
||||||
839 | __METHOD__ |
||||||
840 | ); |
||||||
841 | } |
||||||
842 | |||||||
843 | $variantPath = $uri . '.' . $variantCreatorCommand['imageVariantExtension']; |
||||||
844 | |||||||
845 | // Delete the variant file in case it is stale |
||||||
846 | $fs->deleteFile($variantPath); |
||||||
847 | Craft::info( |
||||||
848 | 'Deleted variant: ' . $variantPath, |
||||||
849 | __METHOD__ |
||||||
850 | ); |
||||||
851 | } |
||||||
852 | } |
||||||
853 | } |
||||||
854 | } |
||||||
855 | } |
||||||
856 | |||||||
857 | /** |
||||||
0 ignored issues
–
show
|
|||||||
858 | * @param $variantCreatorCommand |
||||||
0 ignored issues
–
show
|
|||||||
859 | * @param Asset $asset |
||||||
0 ignored issues
–
show
|
|||||||
860 | * @param AssetTransformIndex $index |
||||||
0 ignored issues
–
show
|
|||||||
861 | * @param $outputPath |
||||||
0 ignored issues
–
show
|
|||||||
862 | * @param $uri |
||||||
0 ignored issues
–
show
|
|||||||
863 | * @throws FsException |
||||||
0 ignored issues
–
show
|
|||||||
864 | */ |
||||||
0 ignored issues
–
show
|
|||||||
865 | protected function copyImageVariantToVolume( |
||||||
866 | $variantCreatorCommand, |
||||||
867 | Asset $asset, |
||||||
868 | AssetTransformIndex $index, |
||||||
0 ignored issues
–
show
The parameter
$index is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
869 | $outputPath, |
||||||
870 | $uri, |
||||||
871 | ): void { |
||||||
872 | // If the image variant creation succeeded, copy it into place |
||||||
873 | if (!empty($outputPath) && is_file($outputPath)) { |
||||||
874 | // Figure out the resulting path for the image variant |
||||||
875 | try { |
||||||
876 | $fs = $asset->getVolume()->getTransformFs(); |
||||||
877 | } catch (InvalidConfigException $e) { |
||||||
878 | $fs = null; |
||||||
879 | Craft::error( |
||||||
880 | 'Asset volume error: ' . $e->getMessage(), |
||||||
881 | __METHOD__ |
||||||
882 | ); |
||||||
883 | } |
||||||
884 | |||||||
885 | $variantPath = $uri . '.' . $variantCreatorCommand['imageVariantExtension']; |
||||||
886 | |||||||
887 | // Delete the variant file in case it is stale |
||||||
888 | $fs->deleteFile($variantPath); |
||||||
889 | Craft::info( |
||||||
890 | 'Variant output path: ' . $outputPath . ' - Variant path: ' . $variantPath, |
||||||
891 | __METHOD__ |
||||||
892 | ); |
||||||
893 | clearstatcache(true, $outputPath); |
||||||
894 | $stream = @fopen($outputPath, 'rb'); |
||||||
895 | if ($stream !== false) { |
||||||
896 | // Now create it |
||||||
897 | $fs->writeFileFromStream($variantPath, $stream, []); |
||||||
898 | FileHelper::unlink($outputPath); |
||||||
899 | } |
||||||
900 | } else { |
||||||
901 | Craft::error( |
||||||
902 | Craft::t('image-optimize', 'Failed to create image variant at: ') |
||||||
903 | . $outputPath, |
||||||
904 | __METHOD__ |
||||||
905 | ); |
||||||
906 | } |
||||||
907 | } |
||||||
908 | |||||||
909 | /** |
||||||
0 ignored issues
–
show
|
|||||||
910 | * @param string $path |
||||||
0 ignored issues
–
show
|
|||||||
911 | * @param string $extension |
||||||
0 ignored issues
–
show
|
|||||||
912 | * |
||||||
913 | * @return string |
||||||
914 | */ |
||||||
915 | protected function swapPathExtension(string $path, string $extension): string |
||||||
916 | { |
||||||
917 | $pathParts = pathinfo($path); |
||||||
918 | $newPath = $pathParts['filename'] . '.' . $extension; |
||||||
919 | if (!empty($pathParts['dirname']) && $pathParts['dirname'] !== '.') { |
||||||
920 | $newPath = $pathParts['dirname'] . DIRECTORY_SEPARATOR . $newPath; |
||||||
921 | $newPath = preg_replace('#/+#', '/', $newPath); |
||||||
922 | } |
||||||
923 | |||||||
924 | return $newPath; |
||||||
925 | } |
||||||
926 | } |
||||||
927 |