Optimize   F
last analyzed

Complexity

Total Complexity 104

Size/Duplication

Total Lines 870
Duplicated Lines 0 %

Importance

Changes 11
Bugs 4 Features 0
Metric Value
wmc 104
eloc 406
c 11
b 4
f 0
dl 0
loc 870
rs 2

22 Methods

Rating   Name   Duplication   Size   Complexity  
A humanFileSize() 0 8 1
A renderLazySizesJs() 0 22 2
A getActiveImageProcessors() 0 26 4
B createImageVariants() 0 68 9
A getActiveVariantCreators() 0 26 4
A handleAfterDeleteTransformsEvent() 0 5 2
B handleGetAssetThumbUrlEvent() 0 37 7
A optimizeImage() 0 23 6
A executeShellCommand() 0 19 4
A createImageTransformType() 0 15 3
A handleGenerateTransformEvent() 0 61 4
A getAllImageTransformTypes() 0 13 1
A saveTransformToTempFile() 0 12 2
A executeVariantCreator() 0 53 5
A renderLazySizesFallbackJs() 0 22 2
A serverSupportsWebP() 0 11 4
B executeImageProcessor() 0 39 6
B cleanupImageVariants() 0 39 9
A copyImageVariantToVolume() 0 40 5
A swapPathExtension() 0 10 3
C handleGetAssetUrlEvent() 0 57 13
B applyFiltersToImage() 0 24 8

How to fix   Complexity   

Complex Class

Complex classes like Optimize often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Optimize, and based on these observations, apply Extract Interface, too.

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
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c) 2017 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
49
50
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
51
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
52
 * @package   ImageOptimize
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
53
 * @since     1.0.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
54
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
55
class Optimize extends Component
56
{
57
    // Constants
58
    // =========================================================================
59
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
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
Coding Style introduced by
Tag value for @var tag indented incorrectly; expected 3 spaces but found 1
Loading history...
79
     */
80
    public const EVENT_REGISTER_IMAGE_TRANSFORM_TYPES = 'registerImageTransformTypes';
81
82
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
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
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
103
            ImageOptimize::$plugin->getSettings()->defaultImageTransformTypes ?? [],
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

103
            ImageOptimize::$plugin->/** @scrutinizer ignore-call */ 
104
                                    getSettings()->defaultImageTransformTypes ?? [],

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.

Loading history...
104
            self::DEFAULT_IMAGE_TRANSFORM_TYPES
105
        ), SORT_REGULAR);
0 ignored issues
show
Coding Style introduced by
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.
Loading history...
106
107
        $event = new RegisterComponentTypesEvent([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
108
            'types' => $imageTransformTypes,
109
        ]);
0 ignored issues
show
Coding Style introduced by
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.
Loading history...
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
Coding Style introduced by
Parameter comment not aligned correctly; expected 29 spaces but found 22
Loading history...
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
introduced by
The condition is_string($config) is always false.
Loading history...
126
            $config = ['type' => $config];
127
        }
128
129
        try {
130
            /** @var ImageTransform $imageTransform */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
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
Deprecated Code introduced by
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 ignore-deprecated  annotation

152
            $asset = /** @scrutinizer ignore-deprecated */ $event->asset;

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.

Loading history...
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
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
166
                    'width' => $asset->width,
167
                    'interlace' => 'line',
168
                ]);
0 ignored issues
show
Coding Style introduced by
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.
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
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
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
221
                    'width' => $event->width,
222
                    'height' => $event->height,
223
                    'interlace' => 'line',
224
                ]);
0 ignored issues
show
Coding Style introduced by
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.
Loading history...
225
                /** @var ImageTransform $transformMethod */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
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
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
242
                    $transformMethod->generateTransformsBeforePageLoad = true;
0 ignored issues
show
Bug Best Practice introduced by
The property generateTransformsBeforePageLoad does not exist on nystudio107\imageoptimiz...ansforms\ImageTransform. Since you implemented __set, consider adding a @property annotation.
Loading history...
243
                }
244
                $url = $transformMethod->getTransformUrl($asset, $transform);
0 ignored issues
show
Bug introduced by
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 getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
274
     * @param array $variables
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
275
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
276
     */
277
    public function renderLazySizesFallbackJs(array $scriptAttrs = [], array $variables = []): string
278
    {
279
        $minifier = 'minify';
280
        $vars = array_merge([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
281
            'scriptSrc' => 'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.0/lazysizes.min.js',
282
        ],
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 8.
Loading history...
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
introduced by
The condition $scriptAttrs !== null is always true.
Loading history...
291
            $attrs = array_merge([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
292
            ],
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 16 spaces, but found 12.
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
305
     * @param array $variables
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
306
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
307
     */
308
    public function renderLazySizesJs(array $scriptAttrs = [], array $variables = []): string
309
    {
310
        $minifier = 'minify';
311
        $vars = array_merge([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
312
            'scriptSrc' => 'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.0/lazysizes.min.js',
313
        ],
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 8.
Loading history...
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
introduced by
The condition $scriptAttrs !== null is always true.
Loading history...
322
            $attrs = array_merge([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
323
            ],
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 16 spaces, but found 12.
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
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
Bug introduced by
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 ignore-type  annotation

353
                $this->applyFiltersToImage($imageTransformIndex->getTransform(), $asset, /** @scrutinizer ignore-type */ $image);
Loading history...
354
            }
355
            // Save the transformed image to a temp file
356
            $tempPath = $this->saveTransformToTempFile(
357
                $imageTransformIndex,
358
                $image
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

358
                /** @scrutinizer ignore-type */ $image
Loading history...
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
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
370
                pathinfo($imageTransformIndex->filename, PATHINFO_FILENAME)
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

370
                /** @scrutinizer ignore-type */ pathinfo($imageTransformIndex->filename, PATHINFO_FILENAME)
Loading history...
Bug introduced by
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 ignore-type  annotation

370
                pathinfo(/** @scrutinizer ignore-type */ $imageTransformIndex->filename, PATHINFO_FILENAME)
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
407
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
420
     * @param Image $image
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 15 spaces after parameter type; 1 found
Loading history...
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
Bug introduced by
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 ignore-type  annotation

426
        $tempFilename = uniqid(/** @scrutinizer ignore-type */ pathinfo($index->filename, PATHINFO_FILENAME), true) . '.' . $index->detectedFormat;
Loading history...
Bug introduced by
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 ignore-type  annotation

426
        $tempFilename = uniqid(pathinfo(/** @scrutinizer ignore-type */ $index->filename, PATHINFO_FILENAME), true) . '.' . $index->detectedFormat;
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
442
     * @param string $tempPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 14 spaces after parameter type; 1 found
Loading history...
443
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
444
    public function optimizeImage(AssetTransformIndex $index, string $tempPath): void
445
    {
446
        Craft::beginProfile('optimizeImage', __METHOD__);
447
        /** @var Settings $settings */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
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
Bug introduced by
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 ignore-type  annotation

452
        $fileFormat = strtolower(/** @scrutinizer ignore-type */ $fileFormat);
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 5
Loading history...
473
     * @param int $decimals
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
491
     * @param Asset $asset
0 ignored issues
show
Coding Style introduced by
Expected 15 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
492
     * @param string $tempPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 14 spaces after parameter type; 1 found
Loading history...
493
     * @param string $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 14 spaces after parameter type; 1 found
Loading history...
494
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
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
Bug introduced by
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 ignore-type  annotation

503
        $fileFormat = strtolower(/** @scrutinizer ignore-type */ $fileFormat);
Loading history...
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
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
526
                            pathinfo($tempPath, PATHINFO_FILENAME)
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

526
                            /** @scrutinizer ignore-type */ pathinfo($tempPath, PATHINFO_FILENAME)
Loading history...
527
                            . '.'
528
                            . pathinfo($tempPath, PATHINFO_EXTENSION)
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

528
                            . /** @scrutinizer ignore-type */ pathinfo($tempPath, PATHINFO_EXTENSION)
Loading history...
529
                            . ' -> '
530
                            . pathinfo($outputPath, PATHINFO_FILENAME)
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

530
                            . /** @scrutinizer ignore-type */ pathinfo($outputPath, PATHINFO_FILENAME)
Loading history...
531
                            . '.'
532
                            . pathinfo($outputPath, PATHINFO_EXTENSION)
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

532
                            . /** @scrutinizer ignore-type */ pathinfo($outputPath, PATHINFO_EXTENSION)
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Tag @noinspection cannot be grouped with parameter tags in a doc comment
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
635
     * @param AssetTransform $transform
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 8 spaces but found 1
Loading history...
636
     * @param Asset $asset
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 8 spaces but found 1
Loading history...
637
     * @param Image $image
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 8 spaces but found 1
Loading history...
638
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
639
    protected function applyFiltersToImage(AssetTransform $transform, Asset $asset, Image $image): void
640
    {
641
        /** @var Settings $settings */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
670
     * @param         $thisProcessor
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 9
Loading history...
671
     * @param string $tempPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
672
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
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
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
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
Coding Style introduced by
Missing parameter comment
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
745
     * @param         $variantCreatorCommand
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 9
Loading history...
746
     * @param string $tempPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
747
     * @param int $imageQuality
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 4 spaces after parameter type; 1 found
Loading history...
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
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
807
     * @param Asset $asset
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 15 spaces after parameter type; 1 found
Loading history...
808
     * @param AssetTransformIndex $transformIndex
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
809
     * @param string $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 14 spaces after parameter type; 1 found
Loading history...
810
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
811
    protected function cleanupImageVariants(Asset $asset, AssetTransformIndex $transformIndex, string $uri): void
812
    {
813
        /** @var Settings $settings */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
858
     * @param                     $variantCreatorCommand
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 21
Loading history...
859
     * @param Asset $asset
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
Coding Style introduced by
Expected 15 spaces after parameter type; 1 found
Loading history...
860
     * @param AssetTransformIndex $index
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
861
     * @param                     $outputPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 21
Loading history...
862
     * @param $uri
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
863
     * @throws FsException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
864
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
865
    protected function copyImageVariantToVolume(
866
        $variantCreatorCommand,
867
        Asset $asset,
868
        AssetTransformIndex $index,
0 ignored issues
show
Unused Code introduced by
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 ignore-unused  annotation

868
        /** @scrutinizer ignore-unused */ AssetTransformIndex $index,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
Coding Style introduced by
Missing short description in doc comment
Loading history...
910
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
911
     * @param string $extension
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
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