Passed
Push — develop ( 6a45d5...f44154 )
by Andrew
04:59
created

Optimize   F

Complexity

Total Complexity 107

Size/Duplication

Total Lines 881
Duplicated Lines 0 %

Importance

Changes 20
Bugs 0 Features 0
Metric Value
wmc 107
eloc 425
c 20
b 0
f 0
dl 0
loc 881
rs 2

22 Methods

Rating   Name   Duplication   Size   Complexity  
B handleGetAssetThumbUrlEvent() 0 33 8
A createImageTransformType() 0 15 3
A getAllImageTransformTypes() 0 13 1
C handleGetAssetUrlEvent() 0 54 14
A serverSupportsWebP() 0 11 4
A renderLazySizesJs() 0 26 3
A getActiveImageProcessors() 0 25 4
B createImageVariants() 0 66 8
A getActiveVariantCreators() 0 25 4
A handleAfterDeleteTransformsEvent() 0 6 3
B copyImageVariantToVolume() 0 63 8
A optimizeImage() 0 21 6
A executeShellCommand() 0 19 4
A handleGenerateTransformEvent() 0 55 4
A swapPathExtension() 0 10 3
A saveTransformToTempFile() 0 12 2
A applyFiltersToImage() 0 24 6
A executeVariantCreator() 0 53 5
A renderLazySizesFallbackJs() 0 26 3
A humanFileSize() 0 8 1
A executeImageProcessor() 0 35 4
B cleanupImageVariants() 0 46 9

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 3.x
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 nystudio107\imageoptimize\ImageOptimize;
14
use nystudio107\imageoptimize\helpers\PluginTemplate as PluginTemplateHelper;
15
use nystudio107\imageoptimize\imagetransforms\CraftImageTransform;
16
use nystudio107\imageoptimize\imagetransforms\ImageTransform;
17
use nystudio107\imageoptimize\imagetransforms\ImageTransformInterface;
18
use nystudio107\imageoptimizeimgix\imagetransforms\ImgixImageTransform;
19
use nystudio107\imageoptimizethumbor\imagetransforms\ThumborImageTransform;
20
use nystudio107\imageoptimizesharp\imagetransforms\SharpImageTransform;
21
22
use Craft;
23
use craft\base\Component;
24
use craft\base\Image;
25
use craft\elements\Asset;
26
use craft\errors\ImageException;
27
use craft\errors\VolumeException;
28
use craft\events\AssetTransformImageEvent;
29
use craft\events\GetAssetThumbUrlEvent;
30
use craft\events\GetAssetUrlEvent;
31
use craft\events\GenerateTransformEvent;
32
use craft\events\RegisterComponentTypesEvent;
33
use craft\helpers\Assets as AssetsHelper;
34
use craft\helpers\Component as ComponentHelper;
35
use craft\helpers\FileHelper;
36
use craft\helpers\Html;
37
use craft\helpers\Image as ImageHelper;
38
use craft\image\Raster;
39
use craft\models\AssetTransform;
40
use craft\models\AssetTransformIndex;
41
42
use mikehaertl\shellcommand\Command as ShellCommand;
43
44
use yii\base\InvalidConfigException;
45
46
/** @noinspection MissingPropertyAnnotationsInspection */
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...
47
48
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
49
 * @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...
50
 * @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...
51
 * @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...
52
 */
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...
53
class Optimize extends Component
54
{
55
    // Constants
56
    // =========================================================================
57
58
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
59
     * @event RegisterComponentTypesEvent The event that is triggered when registering
60
     *        Image Transform types
61
     *
62
     * Image Transform types must implement [[ImageTransformInterface]]. [[ImageTransform]]
63
     * provides a base implementation.
64
     *
65
     * ```php
66
     * use nystudio107\imageoptimize\services\Optimize;
67
     * use craft\events\RegisterComponentTypesEvent;
68
     * use yii\base\Event;
69
     *
70
     * Event::on(Optimize::class,
71
     *     Optimize::EVENT_REGISTER_IMAGE_TRANSFORM_TYPES,
72
     *     function(RegisterComponentTypesEvent $event) {
73
     *         $event->types[] = MyImageTransform::class;
74
     *     }
75
     * );
76
     * ```
77
     */
78
    const EVENT_REGISTER_IMAGE_TRANSFORM_TYPES = 'registerImageTransformTypes';
79
80
    const DEFAULT_IMAGE_TRANSFORM_TYPES = [
81
        CraftImageTransform::class,
82
        ImgixImageTransform::class,
83
        SharpImageTransform::class,
84
        ThumborImageTransform::class,
85
    ];
86
87
    // Public Methods
88
    // =========================================================================
89
90
    /**
91
     * Returns all available field type classes.
92
     *
93
     * @return string[] The available field type classes
94
     */
95
    public function getAllImageTransformTypes(): array
96
    {
97
        $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...
98
            ImageOptimize::$plugin->getSettings()->defaultImageTransformTypes ?? [],
99
            self::DEFAULT_IMAGE_TRANSFORM_TYPES
100
        ), 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...
101
102
        $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...
103
            'types' => $imageTransformTypes
104
        ]);
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...
105
        $this->trigger(self::EVENT_REGISTER_IMAGE_TRANSFORM_TYPES, $event);
106
107
        return $event->types;
108
    }
109
110
    /**
111
     * Creates an Image Transform with a given config.
112
     *
113
     * @param mixed $config The Image Transform’s class name, or its config,
114
     *                      with a `type` value and optionally a `settings` value
115
     *
116
     * @return null|ImageTransformInterface The Image Transform
117
     */
118
    public function createImageTransformType($config): ImageTransformInterface
119
    {
120
        if (is_string($config)) {
121
            $config = ['type' => $config];
122
        }
123
124
        try {
125
            /** @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...
126
            $imageTransform = ComponentHelper::createComponent($config, ImageTransformInterface::class);
127
        } catch (\Throwable $e) {
128
            $imageTransform = null;
129
            Craft::error($e->getMessage(), __METHOD__);
130
        }
131
132
        return $imageTransform;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $imageTransform could return the type null which is incompatible with the type-hinted return nystudio107\imageoptimiz...ImageTransformInterface. Consider adding an additional type-check to rule them out.
Loading history...
133
    }
134
135
    /**
136
     * Handle responding to EVENT_GET_ASSET_URL events
137
     *
138
     * @param GetAssetUrlEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
139
     *
140
     * @return null|string
141
     * @throws InvalidConfigException
142
     */
143
    public function handleGetAssetUrlEvent(GetAssetUrlEvent $event)
144
    {
145
        Craft::beginProfile('handleGetAssetUrlEvent', __METHOD__);
146
        $url = null;
147
        if (!ImageOptimize::$plugin->transformMethod instanceof CraftImageTransform) {
148
            $asset = $event->asset;
149
            $transform = $event->transform;
150
            // If the transform is empty in some regard, normalize it to null
151
            if (empty($transform)) {
152
                $transform = null;
153
            }
154
            // If there's no transform requested, and we can't manipulate the image anyway, just return the URL
155
            if ($transform === null
156
                && !ImageHelper::canManipulateAsImage(pathinfo($asset->filename, PATHINFO_EXTENSION))) {
0 ignored issues
show
Bug introduced by
It seems like $asset->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

156
                && !ImageHelper::canManipulateAsImage(pathinfo(/** @scrutinizer ignore-type */ $asset->filename, PATHINFO_EXTENSION))) {
Loading history...
Bug introduced by
It seems like pathinfo($asset->filenam...ces\PATHINFO_EXTENSION) can also be of type array; however, parameter $extension of craft\helpers\Image::canManipulateAsImage() 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

156
                && !ImageHelper::canManipulateAsImage(/** @scrutinizer ignore-type */ pathinfo($asset->filename, PATHINFO_EXTENSION))) {
Loading history...
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
157
                $volume = $asset->getVolume();
158
159
                return AssetsHelper::generateUrl($volume, $asset);
160
            }
161
            // If we're passed in null, make a dummy AssetTransform model for Thumbor
162
            // For backwards compatibility
163
            if ($transform === null && ImageOptimize::$plugin->transformMethod instanceof ThumborImageTransform) {
164
                $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...
165
                    'width'     => $asset->width,
166
                    'interlace' => 'line',
167
                ]);
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...
168
            }
169
            // If we're passed an array, make an AssetTransform model out of it
170
            if (\is_array($transform)) {
171
                $transform = new AssetTransform($transform);
172
            }
173
            // If we're passing in a string, look up the asset transform in the db
174
            if (\is_string($transform)) {
175
                $assetTransforms = Craft::$app->getAssetTransforms();
176
                $transform = $assetTransforms->getTransformByHandle($transform);
177
            }
178
            // If the final format is an SVG, don't attempt to transform it
179
            $finalFormat = empty($transform['format']) ? $asset->getExtension() : $transform['format'];
180
            if ($finalFormat === 'svg') {
181
                return null;
182
            }
183
            // Normalize the extension to lowercase, for some transform methods that require this
184
            if (!empty($transform) && !empty($finalFormat)) {
185
                $format = $transform['format'] ?? null;
186
                $transform['format'] = $format === null ? null : strtolower($finalFormat);
187
            }
188
            // Generate an image transform url
189
            $url = ImageOptimize::$plugin->transformMethod->getTransformUrl(
190
                $asset,
191
                $transform
192
            );
193
        }
194
        Craft::endProfile('handleGetAssetUrlEvent', __METHOD__);
195
196
        return $url;
197
    }
198
199
    /**
200
     * Handle responding to EVENT_GET_ASSET_THUMB_URL events
201
     *
202
     * @param GetAssetThumbUrlEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
203
     *
204
     * @return null|string
205
     */
206
    public function handleGetAssetThumbUrlEvent(GetAssetThumbUrlEvent $event)
207
    {
208
        Craft::beginProfile('handleGetAssetThumbUrlEvent', __METHOD__);
209
        $url = $event->url;
210
        if (!ImageOptimize::$plugin->transformMethod instanceof CraftImageTransform) {
211
            $asset = $event->asset;
212
            if (ImageHelper::canManipulateAsImage($asset->getExtension())) {
213
                $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...
214
                    'width' => $event->width,
215
		    'height' => $event->height,
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 20 spaces, but found 6.
Loading history...
Coding Style introduced by
Line indented incorrectly; expected at least 16 spaces, found 6
Loading history...
216
                    'interlace' => 'line',
217
                ]);
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...
218
                /** @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...
219
                $transformMethod = ImageOptimize::$plugin->transformMethod;
220
                // If the final format is an SVG, don't attempt to transform it
221
                $finalFormat = empty($transform['format']) ? $asset->getExtension() : $transform['format'];
222
                if ($finalFormat === 'svg') {
223
                    return null;
224
                }
225
                // Normalize the extension to lowercase, for some transform methods that require this
226
                if ($transform !== null && !empty($finalFormat)) {
227
                    $transform['format'] = strtolower($finalFormat);
228
                }
229
                // Generate an image transform url
230
                if ($transformMethod->hasProperty('generateTransformsBeforePageLoad')) {
231
                    $transformMethod->generateTransformsBeforePageLoad = $event->generate;
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...
232
                }
233
                $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...
234
            }
235
        }
236
        Craft::endProfile('handleGetAssetThumbUrlEvent', __METHOD__);
237
238
        return $url;
239
    }
240
241
    /**
242
     * Returns whether `.webp` is a format supported by the server
243
     *
244
     * @return bool
245
     */
246
    public function serverSupportsWebP(): bool
247
    {
248
        $result = false;
249
        $variantCreators = ImageOptimize::$plugin->optimize->getActiveVariantCreators();
250
        foreach ($variantCreators as $variantCreator) {
251
            if ($variantCreator['creator'] === 'cwebp' && $variantCreator['installed']) {
252
                $result = true;
253
            }
254
        }
255
256
        return $result;
257
    }
258
259
    /**
260
     * Render the LazySizes fallback JS
261
     *
262
     * @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...
263
     * @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...
264
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
265
     */
266
    public function renderLazySizesFallbackJs($scriptAttrs = [], $variables = [])
267
    {
268
        $minifier = 'minify';
269
        if ($scriptAttrs === null) {
0 ignored issues
show
introduced by
The condition $scriptAttrs === null is always false.
Loading history...
270
            $minifier = 'jsMin';
271
        }
272
        $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...
273
            'scriptSrc' => 'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.0/lazysizes.min.js',
274
            ],
275
            $variables,
276
        );
277
        $content = PluginTemplateHelper::renderPluginTemplate(
278
            'frontend/lazysizes-fallback-js',
279
            $vars,
280
            $minifier
281
        );
282
        $content = (string)$content;
283
        if ($scriptAttrs !== null) {
0 ignored issues
show
introduced by
The condition $scriptAttrs !== null is always true.
Loading history...
284
            $attrs = array_merge([
0 ignored issues
show
Unused Code introduced by
The assignment to $attrs is dead and can be removed.
Loading history...
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
285
                ],
286
                $scriptAttrs,
287
            );
288
            $content = Html::tag('script', $content, $scriptAttrs);
289
        }
290
291
        return $content;
292
    }
293
294
    /**
295
     * Render the LazySizes fallback JS
296
     *
297
     * @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...
298
     * @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...
299
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
300
     */
301
    public function renderLazySizesJs($scriptAttrs = [], $variables = [])
302
    {
303
        $minifier = 'minify';
304
        if ($scriptAttrs === null) {
0 ignored issues
show
introduced by
The condition $scriptAttrs === null is always false.
Loading history...
305
            $minifier = 'jsMin';
306
        }
307
        $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...
308
            'scriptSrc' => 'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.0/lazysizes.min.js',
309
        ],
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...
310
            $variables,
311
        );
312
        $content = PluginTemplateHelper::renderPluginTemplate(
313
            'frontend/lazysizes-js',
314
            $vars,
315
            $minifier
316
        );
317
        $content = (string)$content;
318
        if ($scriptAttrs !== null) {
0 ignored issues
show
introduced by
The condition $scriptAttrs !== null is always true.
Loading history...
319
            $attrs = array_merge([
0 ignored issues
show
Unused Code introduced by
The assignment to $attrs is dead and can be removed.
Loading history...
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
320
            ],
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...
321
                $scriptAttrs,
322
            );
323
            $content = Html::tag('script', $content, $scriptAttrs);
324
        }
325
326
        return $content;
327
    }
328
329
    /**
330
     * Handle responding to EVENT_GENERATE_TRANSFORM events
331
     *
332
     * @param GenerateTransformEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
333
     *
334
     * @return null|string
335
     */
336
    public function handleGenerateTransformEvent(GenerateTransformEvent $event)
337
    {
338
        Craft::beginProfile('handleGenerateTransformEvent', __METHOD__);
339
        $tempPath = null;
340
341
        // Only do this for local Craft transforms
342
        if (ImageOptimize::$plugin->transformMethod instanceof CraftImageTransform && $event->asset !== null) {
343
            // Apply any filters to the image
344
            if ($event->transformIndex->transform !== null) {
345
                $this->applyFiltersToImage($event->transformIndex->transform, $event->asset, $event->image);
346
            }
347
            // Save the transformed image to a temp file
348
            $tempPath = $this->saveTransformToTempFile(
349
                $event->transformIndex,
350
                $event->image
351
            );
352
            $originalFileSize = @filesize($tempPath);
353
            // Optimize the image
354
            $this->optimizeImage(
355
                $event->transformIndex,
356
                $tempPath
357
            );
358
            clearstatcache(true, $tempPath);
359
            // Log the results of the image optimization
360
            $optimizedFileSize = @filesize($tempPath);
361
            $index = $event->transformIndex;
362
            Craft::info(
363
                pathinfo($index->filename, PATHINFO_FILENAME)
0 ignored issues
show
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

363
                pathinfo(/** @scrutinizer ignore-type */ $index->filename, PATHINFO_FILENAME)
Loading history...
Bug introduced by
Are you sure pathinfo($index->filenam...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

363
                /** @scrutinizer ignore-type */ pathinfo($index->filename, PATHINFO_FILENAME)
Loading history...
364
                .'.'
365
                .$index->detectedFormat
366
                .' -> '
367
                .Craft::t('image-optimize', 'Original')
368
                .': '
369
                .$this->humanFileSize($originalFileSize, 1)
370
                .', '
371
                .Craft::t('image-optimize', 'Optimized')
372
                .': '
373
                .$this->humanFileSize($optimizedFileSize, 1)
374
                .' -> '
375
                .Craft::t('image-optimize', 'Savings')
376
                .': '
377
                .number_format(abs(100 - (($optimizedFileSize * 100) / $originalFileSize)), 1)
378
                .'%',
379
                __METHOD__
380
            );
381
            // Create any image variants
382
            $this->createImageVariants(
383
                $event->transformIndex,
384
                $event->asset,
385
                $tempPath
386
            );
387
        }
388
        Craft::endProfile('handleGenerateTransformEvent', __METHOD__);
389
390
        return $tempPath;
391
    }
392
393
    /**
394
     * Handle cleaning up any variant creator images
395
     *
396
     * @param AssetTransformImageEvent $event
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
397
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
398
    public function handleAfterDeleteTransformsEvent(AssetTransformImageEvent $event)
399
    {
400
        $settings = ImageOptimize::$plugin->getSettings();
0 ignored issues
show
Unused Code introduced by
The assignment to $settings is dead and can be removed.
Loading history...
401
        // Only do this for local Craft transforms
402
        if (ImageOptimize::$plugin->transformMethod instanceof CraftImageTransform && $event->asset !== null) {
403
            $this->cleanupImageVariants($event->asset, $event->transformIndex);
404
        }
405
    }
406
407
    /**
408
     * Save out the image to a temp file
409
     *
410
     * @param AssetTransformIndex $index
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
411
     * @param Image               $image
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
412
     *
413
     * @return string
414
     */
415
    public function saveTransformToTempFile(AssetTransformIndex $index, Image $image): string
416
    {
417
        $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

417
        $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

417
        $tempFilename = uniqid(pathinfo(/** @scrutinizer ignore-type */ $index->filename, PATHINFO_FILENAME), true).'.'.$index->detectedFormat;
Loading history...
418
        $tempPath = Craft::$app->getPath()->getTempPath().DIRECTORY_SEPARATOR.$tempFilename;
419
        try {
420
            $image->saveAs($tempPath);
421
        } catch (ImageException $e) {
422
            Craft::error('Transformed image save failed: '.$e->getMessage(), __METHOD__);
423
        }
424
        Craft::info('Transformed image saved to: '.$tempPath, __METHOD__);
425
426
        return $tempPath;
427
    }
428
429
    /**
430
     * Run any image post-processing/optimization on the image file
431
     *
432
     * @param AssetTransformIndex $index
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
433
     * @param string              $tempPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
434
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
435
    public function optimizeImage(AssetTransformIndex $index, string $tempPath)
436
    {
437
        Craft::beginProfile('optimizeImage', __METHOD__);
438
        $settings = ImageOptimize::$plugin->getSettings();
439
        // Get the active processors for the transform format
440
        $activeImageProcessors = $settings->activeImageProcessors;
441
        $fileFormat = $index->detectedFormat;
442
        // Special-case for 'jpeg'
443
        if ($fileFormat === 'jpeg') {
444
            $fileFormat = 'jpg';
445
        }
446
        if (!empty($activeImageProcessors[$fileFormat])) {
447
            // Iterate through all of the processors for this format
448
            $imageProcessors = $settings->imageProcessors;
449
            foreach ($activeImageProcessors[$fileFormat] as $processor) {
450
                if (!empty($processor) && !empty($imageProcessors[$processor])) {
451
                    $this->executeImageProcessor($imageProcessors[$processor], $tempPath);
452
                }
453
            }
454
        }
455
        Craft::endProfile('optimizeImage', __METHOD__);
456
    }
457
458
    /**
459
     * Translate bytes into something human-readable
460
     *
461
     * @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...
462
     * @param int $decimals
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
463
     *
464
     * @return string
465
     */
466
    public function humanFileSize($bytes, $decimals = 1): string
467
    {
468
        $oldSize = Craft::$app->formatter->sizeFormatBase;
469
        Craft::$app->formatter->sizeFormatBase = 1000;
470
        $result = Craft::$app->formatter->asShortSize($bytes, $decimals);
471
        Craft::$app->formatter->sizeFormatBase = $oldSize;
472
473
        return $result;
474
    }
475
476
    /**
477
     * Create any image variants for the image file
478
     *
479
     * @param AssetTransformIndex $index
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
480
     * @param Asset               $asset
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
481
     * @param string              $tempPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
482
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
483
    public function createImageVariants(AssetTransformIndex $index, Asset $asset, string $tempPath)
484
    {
485
        Craft::beginProfile('createImageVariants', __METHOD__);
486
        $settings = ImageOptimize::$plugin->getSettings();
487
        // Get the active image variant creators
488
        $activeImageVariantCreators = $settings->activeImageVariantCreators;
489
        $fileFormat = $index->detectedFormat ?? $index->format;
490
        // Special-case for 'jpeg'
491
        if ($fileFormat === 'jpeg') {
492
            $fileFormat = 'jpg';
493
        }
494
        if (!empty($activeImageVariantCreators[$fileFormat])) {
495
            // Iterate through all of the image variant creators for this format
496
            $imageVariantCreators = $settings->imageVariantCreators;
497
            foreach ($activeImageVariantCreators[$fileFormat] as $variantCreator) {
498
                if (!empty($variantCreator) && !empty($imageVariantCreators[$variantCreator])) {
499
                    // Create the image variant in a temporary folder
500
                    $generalConfig = Craft::$app->getConfig()->getGeneral();
501
                    $quality = $index->transform->quality ?: $generalConfig->defaultImageQuality;
502
                    $outputPath = $this->executeVariantCreator(
503
                        $imageVariantCreators[$variantCreator],
504
                        $tempPath,
505
                        $quality
506
                    );
507
508
                    if ($outputPath !== null) {
509
                        // Get info on the original and the created variant
510
                        $originalFileSize = @filesize($tempPath);
511
                        $variantFileSize = @filesize($outputPath);
512
513
                        Craft::info(
514
                            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

514
                            /** @scrutinizer ignore-type */ pathinfo($tempPath, PATHINFO_FILENAME)
Loading history...
515
                            .'.'
516
                            .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

516
                            ./** @scrutinizer ignore-type */ pathinfo($tempPath, PATHINFO_EXTENSION)
Loading history...
517
                            .' -> '
518
                            .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

518
                            ./** @scrutinizer ignore-type */ pathinfo($outputPath, PATHINFO_FILENAME)
Loading history...
519
                            .'.'
520
                            .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

520
                            ./** @scrutinizer ignore-type */ pathinfo($outputPath, PATHINFO_EXTENSION)
Loading history...
521
                            .' -> '
522
                            .Craft::t('image-optimize', 'Original')
523
                            .': '
524
                            .$this->humanFileSize($originalFileSize, 1)
525
                            .', '
526
                            .Craft::t('image-optimize', 'Variant')
527
                            .': '
528
                            .$this->humanFileSize($variantFileSize, 1)
529
                            .' -> '
530
                            .Craft::t('image-optimize', 'Savings')
531
                            .': '
532
                            .number_format(abs(100 - (($variantFileSize * 100) / $originalFileSize)), 1)
533
                            .'%',
534
                            __METHOD__
535
                        );
536
537
                        // Copy the image variant into place
538
                        $this->copyImageVariantToVolume(
539
                            $imageVariantCreators[$variantCreator],
540
                            $asset,
541
                            $index,
542
                            $outputPath
543
                        );
544
                    }
545
                }
546
            }
547
        }
548
        Craft::endProfile('createImageVariants', __METHOD__);
549
    }
550
551
    /**
552
     * Return an array of active image processors
553
     *
554
     * @return array
555
     */
556
    public function getActiveImageProcessors(): array
557
    {
558
        $result = [];
559
        $settings = ImageOptimize::$plugin->getSettings();
560
        // Get the active processors for the transform format
561
        $activeImageProcessors = $settings->activeImageProcessors;
562
        foreach ($activeImageProcessors as $imageFormat => $imageProcessor) {
563
            // Iterate through all of the processors for this format
564
            $imageProcessors = $settings->imageProcessors;
565
            foreach ($activeImageProcessors[$imageFormat] as $processor) {
566
                if (!empty($imageProcessors[$processor])) {
567
                    $thisImageProcessor = $imageProcessors[$processor];
568
                    $result[] = [
569
                        'format'    => $imageFormat,
570
                        'creator'   => $processor,
571
                        'command'   => $thisImageProcessor['commandPath']
572
                            .' '
573
                            .$thisImageProcessor['commandOptions'],
574
                        'installed' => is_file($thisImageProcessor['commandPath']),
575
                    ];
576
                }
577
            }
578
        }
579
580
        return $result;
581
    }
582
583
    /**
584
     * Return an array of active image variant creators
585
     *
586
     * @return array
587
     */
588
    public function getActiveVariantCreators(): array
589
    {
590
        $result = [];
591
        $settings = ImageOptimize::$plugin->getSettings();
592
        // Get the active image variant creators
593
        $activeImageVariantCreators = $settings->activeImageVariantCreators;
594
        foreach ($activeImageVariantCreators as $imageFormat => $imageCreator) {
595
            // Iterate through all of the image variant creators for this format
596
            $imageVariantCreators = $settings->imageVariantCreators;
597
            foreach ($activeImageVariantCreators[$imageFormat] as $variantCreator) {
598
                if (!empty($imageVariantCreators[$variantCreator])) {
599
                    $thisVariantCreator = $imageVariantCreators[$variantCreator];
600
                    $result[] = [
601
                        'format'    => $imageFormat,
602
                        'creator'   => $variantCreator,
603
                        'command'   => $thisVariantCreator['commandPath']
604
                            .' '
605
                            .$thisVariantCreator['commandOptions'],
606
                        'installed' => is_file($thisVariantCreator['commandPath']),
607
                    ];
608
                }
609
            }
610
        }
611
612
        return $result;
613
    }
614
615
    // Protected Methods
616
    // =========================================================================
617
618
    /** @noinspection PhpUnusedParameterInspection
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
Tag @noinspection cannot be grouped with parameter tags in a doc comment
Loading history...
619
     * @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...
620
     * @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 8 spaces but found 1
Loading history...
621
     * @param Image          $image
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...
622
     */
0 ignored issues
show
Coding Style introduced by
There must be no blank lines after the function comment
Loading history...
Coding Style introduced by
Missing @return tag in function comment
Loading history...
623
624
    protected function applyFiltersToImage(AssetTransform $transform, Asset $asset, Image $image)
625
    {
626
        $settings = ImageOptimize::$plugin->getSettings();
627
        // Only try to apply filters to Raster images
628
        if ($image instanceof Raster) {
629
            $imagineImage = $image->getImagineImage();
630
            if ($imagineImage !== null) {
631
                // Handle auto-sharpening scaled down images
632
                if ($settings->autoSharpenScaledImages) {
633
                    // See if the image has been scaled >= 50%
634
                    $widthScale = $asset->getWidth() / $image->getWidth();
635
                    $heightScale = $asset->getHeight() / $image->getHeight();
636
                    if (($widthScale >= 2.0) || ($heightScale >= 2.0)) {
637
                        $imagineImage->effects()
638
                            ->sharpen();
639
                        Craft::debug(
640
                            Craft::t(
641
                                'image-optimize',
642
                                'Image transform >= 50%, sharpened the transformed image: {name}',
643
                                [
644
                                    'name' => $asset->title,
645
                                ]
646
                            ),
647
                            __METHOD__
648
                        );
649
                    }
650
                }
651
            }
652
        }
653
    }
654
655
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
656
     * @param string  $tempPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter type; 2 found
Loading history...
Coding Style introduced by
Doc comment for parameter $tempPath does not match actual variable name $thisProcessor
Loading history...
657
     * @param         $thisProcessor
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $thisProcessor does not match actual variable name $tempPath
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 9
Loading history...
658
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
659
    protected function executeImageProcessor($thisProcessor, string $tempPath)
660
    {
661
        // Make sure the command exists
662
        if (is_file($thisProcessor['commandPath'])) {
663
            // Set any options for the command
664
            $commandOptions = '';
665
            if (!empty($thisProcessor['commandOptions'])) {
666
                $commandOptions = ' '
667
                    .$thisProcessor['commandOptions']
668
                    .' ';
669
            }
670
            // Redirect the command output if necessary for this processor
671
            $outputFileFlag = '';
672
            if (!empty($thisProcessor['commandOutputFileFlag'])) {
673
                $outputFileFlag = ' '
674
                    .$thisProcessor['commandOutputFileFlag']
675
                    .' '
676
                    .escapeshellarg($tempPath)
677
                    .' ';
678
            }
679
            // Build the command to execute
680
            $cmd =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
681
                $thisProcessor['commandPath']
682
                .$commandOptions
683
                .$outputFileFlag
684
                .escapeshellarg($tempPath);
685
            // Execute the command
686
            $shellOutput = $this->executeShellCommand($cmd);
687
            Craft::info($cmd."\n".$shellOutput, __METHOD__);
688
        } else {
689
            Craft::error(
690
                $thisProcessor['commandPath']
691
                .' '
692
                .Craft::t('image-optimize', 'does not exist'),
693
                __METHOD__
694
            );
695
        }
696
    }
697
698
    /**
699
     * Execute a shell command
700
     *
701
     * @param string $command
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
702
     *
703
     * @return string
704
     */
705
    protected function executeShellCommand(string $command): string
706
    {
707
        // Create the shell command
708
        $shellCommand = new ShellCommand();
709
        $shellCommand->setCommand($command);
710
711
        // If we don't have proc_open, maybe we've got exec
712
        if (!\function_exists('proc_open') && \function_exists('exec')) {
713
            $shellCommand->useExec = true;
714
        }
715
716
        // Return the result of the command's output or error
717
        if ($shellCommand->execute()) {
718
            $result = $shellCommand->getOutput();
719
        } else {
720
            $result = $shellCommand->getError();
721
        }
722
723
        return $result;
724
    }
725
726
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
727
     * @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...
728
     * @param string  $tempPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter type; 2 found
Loading history...
729
     * @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; 5 found
Loading history...
730
     *
731
     * @return string|null the path to the created variant
732
     */
733
    protected function executeVariantCreator($variantCreatorCommand, string $tempPath, int $imageQuality)
734
    {
735
        $outputPath = $tempPath;
736
        // Make sure the command exists
737
        if (is_file($variantCreatorCommand['commandPath'])) {
738
            // Get the output file for this image variant
739
            $outputPath .= '.'.$variantCreatorCommand['imageVariantExtension'];
740
            // Set any options for the command
741
            $commandOptions = '';
742
            if (!empty($variantCreatorCommand['commandOptions'])) {
743
                $commandOptions = ' '
744
                    .$variantCreatorCommand['commandOptions']
745
                    .' ';
746
            }
747
            // Redirect the command output if necessary for this variantCreator
748
            $outputFileFlag = '';
749
            if (!empty($variantCreatorCommand['commandOutputFileFlag'])) {
750
                $outputFileFlag = ' '
751
                    .$variantCreatorCommand['commandOutputFileFlag']
752
                    .' '
753
                    .escapeshellarg($outputPath)
754
                    .' ';
755
            }
756
            // Get the quality setting of this transform
757
            $commandQualityFlag = '';
758
            if (!empty($variantCreatorCommand['commandQualityFlag'])) {
759
                $commandQualityFlag = ' '
760
                    .$variantCreatorCommand['commandQualityFlag']
761
                    .' '
762
                    .$imageQuality
763
                    .' ';
764
            }
765
            // Build the command to execute
766
            $cmd =
0 ignored issues
show
Coding Style introduced by
Multi-line assignments must have the equal sign on the second line
Loading history...
767
                $variantCreatorCommand['commandPath']
768
                .$commandOptions
769
                .$commandQualityFlag
770
                .$outputFileFlag
771
                .escapeshellarg($tempPath);
772
            // Execute the command
773
            $shellOutput = $this->executeShellCommand($cmd);
774
            Craft::info($cmd."\n".$shellOutput, __METHOD__);
775
        } else {
776
            Craft::error(
777
                $variantCreatorCommand['commandPath']
778
                .' '
779
                .Craft::t('image-optimize', 'does not exist'),
780
                __METHOD__
781
            );
782
            $outputPath = null;
783
        }
784
785
        return $outputPath;
786
    }
787
788
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
789
     * @param Asset               $asset
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
790
     * @param AssetTransformIndex $transformIndex
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
791
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
792
    protected function cleanupImageVariants(Asset $asset, AssetTransformIndex $transformIndex)
793
    {
794
        $settings = ImageOptimize::$plugin->getSettings();
795
        $assetTransforms = Craft::$app->getAssetTransforms();
796
        // Get the active image variant creators
797
        $activeImageVariantCreators = $settings->activeImageVariantCreators;
798
        $fileFormat = $transformIndex->detectedFormat ?? $transformIndex->format;
799
        if (!empty($activeImageVariantCreators[$fileFormat])) {
800
            // Iterate through all of the image variant creators for this format
801
            $imageVariantCreators = $settings->imageVariantCreators;
802
            if (!empty($activeImageVariantCreators[$fileFormat])) {
803
                foreach ($activeImageVariantCreators[$fileFormat] as $variantCreator) {
804
                    if (!empty($variantCreator) && !empty($imageVariantCreators[$variantCreator])) {
805
                        // Create the image variant in a temporary folder
806
                        $variantCreatorCommand = $imageVariantCreators[$variantCreator];
807
                        try {
808
                            $volume = $asset->getVolume();
809
                        } catch (InvalidConfigException $e) {
810
                            $volume = null;
811
                            Craft::error(
812
                                'Asset volume error: '.$e->getMessage(),
813
                                __METHOD__
814
                            );
815
                        }
816
                        try {
817
                            $variantPath = $asset->getFolder()->path.$assetTransforms->getTransformSubpath(
818
                                $asset,
819
                                $transformIndex
820
                            );
821
                        } catch (InvalidConfigException $e) {
822
                            $variantPath = '';
823
                            Craft::error(
824
                                'Asset folder does not exist: '.$e->getMessage(),
825
                                __METHOD__
826
                            );
827
                        }
828
                        $variantPath .= '.'.$variantCreatorCommand['imageVariantExtension'];
829
                        // Delete the variant file in case it is stale
830
                        try {
831
                            $volume->deleteFile($variantPath);
832
                        } catch (VolumeException $e) {
833
                            // We're fine with that.
834
                        }
835
                        Craft::info(
836
                            'Deleted variant: '.$variantPath,
837
                            __METHOD__
838
                        );
839
                    }
840
                }
841
            }
842
        }
843
    }
844
845
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
846
     * @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 21
Loading history...
847
     * @param Asset               $asset
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
848
     * @param AssetTransformIndex $index
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
849
     * @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 1 spaces but found 21
Loading history...
850
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
851
    protected function copyImageVariantToVolume(
852
        $variantCreatorCommand,
853
        Asset $asset,
854
        AssetTransformIndex $index,
855
        $outputPath
856
    ) {
857
        // If the image variant creation succeeded, copy it into place
858
        if (!empty($outputPath) && is_file($outputPath)) {
859
            // Figure out the resulting path for the image variant
860
            try {
861
                $volume = $asset->getVolume();
862
            } catch (InvalidConfigException $e) {
863
                $volume = null;
864
                Craft::error(
865
                    'Asset volume error: '.$e->getMessage(),
866
                    __METHOD__
867
                );
868
            }
869
            $assetTransforms = Craft::$app->getAssetTransforms();
870
            try {
871
                $transformPath = $asset->getFolder()->path.$assetTransforms->getTransformSubpath($asset, $index);
872
            } catch (InvalidConfigException $e) {
873
                $transformPath = '';
874
                Craft::error(
875
                    'Error getting asset folder: '.$e->getMessage(),
876
                    __METHOD__
877
                );
878
            }
879
            $variantPath = $transformPath.'.'.$variantCreatorCommand['imageVariantExtension'];
880
881
            // Delete the variant file in case it is stale
882
            try {
883
                $volume->deleteFile($variantPath);
884
            } catch (VolumeException $e) {
885
                // We're fine with that.
886
            }
887
888
            Craft::info(
889
                'Variant output path: '.$outputPath.' - Variant path: '.$variantPath,
890
                __METHOD__
891
            );
892
893
            clearstatcache(true, $outputPath);
894
            $stream = @fopen($outputPath, 'rb');
895
            if ($stream !== false) {
896
                // Now create it
897
                try {
898
                    $volume->createFileByStream($variantPath, $stream, []);
899
                } catch (VolumeException $e) {
900
                    Craft::error(
901
                        Craft::t('image-optimize', 'Failed to create image variant at: ')
902
                        .$outputPath,
903
                        __METHOD__
904
                    );
905
                }
906
907
                FileHelper::unlink($outputPath);
908
            }
909
        } else {
910
            Craft::error(
911
                Craft::t('image-optimize', 'Failed to create image variant at: ')
912
                .$outputPath,
913
                __METHOD__
914
            );
915
        }
916
    }
917
918
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
919
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
920
     * @param string $extension
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
921
     *
922
     * @return string
923
     */
924
    protected function swapPathExtension(string $path, string $extension): string
925
    {
926
        $pathParts = pathinfo($path);
927
        $newPath = $pathParts['filename'].'.'.$extension;
928
        if (!empty($pathParts['dirname']) && $pathParts['dirname'] !== '.') {
929
            $newPath = $pathParts['dirname'].DIRECTORY_SEPARATOR.$newPath;
930
            $newPath = preg_replace('#/+#', '/', $newPath);
931
        }
932
933
        return $newPath;
934
    }
935
}
936