Passed
Push — develop ( 715c37...b81609 )
by Andrew
04:02
created

Optimize::createImageVariants()   C

Complexity

Conditions 8
Paths 3

Size

Total Lines 64
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 64
rs 6.8232
c 0
b 0
f 0
cc 8
eloc 46
nc 3
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
8
 * @copyright Copyright (c) 2017 nystudio107
9
 */
10
11
namespace nystudio107\imageoptimize\services;
12
13
use nystudio107\imageoptimize\ImageOptimize;
14
15
use Craft;
16
use craft\base\Component;
17
use craft\base\Image;
18
use craft\elements\Asset;
19
use craft\errors\ImageException;
20
use craft\errors\VolumeException;
21
use craft\events\AssetTransformImageEvent;
22
use craft\events\GetAssetUrlEvent;
23
use craft\events\GenerateTransformEvent;
24
use craft\helpers\FileHelper;
25
use craft\helpers\Assets as AssetsHelper;
26
use craft\helpers\Image as ImageHelper;
27
use craft\image\Raster;
28
use craft\models\AssetTransform;
29
use craft\models\AssetTransformIndex;
30
31
use mikehaertl\shellcommand\Command as ShellCommand;
32
use yii\base\InvalidConfigException;
33
34
/** @noinspection MissingPropertyAnnotationsInspection */
35
36
/**
37
 * @author    nystudio107
38
 * @package   ImageOptimize
39
 * @since     1.0.0
40
 */
41
class Optimize extends Component
42
{
43
    // Public Methods
44
    // =========================================================================
45
46
    /**
47
     * Handle responding to EVENT_GET_ASSET_URL events
48
     *
49
     * @param GetAssetUrlEvent $event
50
     *
51
     * @return string
52
     * @throws InvalidConfigException
53
     */
54
    public function handleGetAssetUrlEvent(GetAssetUrlEvent $event)
55
    {
56
        Craft::beginProfile('handleGetAssetUrlEvent', __METHOD__);
57
        $url = null;
58
        $settings = ImageOptimize::$plugin->getSettings();
59
        if ($settings->transformMethod != 'craft') {
60
            $asset = $event->asset;
61
            $transform = $event->transform;
62
            // If there's no transform requested, and we can't manipulate the image anyway, just return the URL
63
            if ($transform === null
64
                || !ImageHelper::canManipulateAsImage(pathinfo($asset->filename, PATHINFO_EXTENSION))) {
65
                $volume = $asset->getVolume();
66
67
                return AssetsHelper::generateUrl($volume, $asset);
68
            }
69
            // If we're passed in null, make a dummy AssetTransform model
70
            if (empty($transform)) {
71
                $transform = new AssetTransform([
72
                    'height'    => $asset->height,
73
                    'width'     => $asset->width,
74
                    'interlace' => 'line',
75
                ]);
76
            }
77
            // If we're passed an array, make an AssetTransform model out of it
78
            if (is_array($transform)) {
79
                $transform = new AssetTransform($transform);
80
            }
81
            // If we're passing in a string, look up the asset transform in the db
82
            if (is_string($transform)) {
83
                $assetTransforms = Craft::$app->getAssetTransforms();
84
                $transform = $assetTransforms->getTransformByHandle($transform);
85
            }
86
            // Generate an image transform url
87
            $url = ImageOptimize::$transformClass::getTransformUrl(
88
                $asset,
89
                $transform,
90
                ImageOptimize::$transformParams
91
            );
92
        }
93
        Craft::endProfile('handleGetAssetUrlEvent', __METHOD__);
94
95
        return $url;
96
    }
97
98
    /**
99
     * Handle responding to EVENT_GENERATE_TRANSFORM events
100
     *
101
     * @param GenerateTransformEvent $event
102
     *
103
     * @return string
104
     */
105
    public function handleGenerateTransformEvent(GenerateTransformEvent $event)
106
    {
107
        Craft::beginProfile('handleGenerateTransformEvent', __METHOD__);
108
        $tempPath = null;
109
110
        $settings = ImageOptimize::$plugin->getSettings();
111
        // Only do this for local Craft transforms
112
        if ($settings->transformMethod == 'craft' && !empty($event->asset)) {
113
            // Apply any filters to the image
114
            if (!empty($event->transformIndex->transform)) {
115
                $this->applyFiltersToImage($event->transformIndex->transform, $event->asset, $event->image);
116
            }
117
            // Save the transformed image to a temp file
118
            $tempPath = $this->saveTransformToTempFile(
119
                $event->transformIndex,
120
                $event->image
121
            );
122
            $originalFileSize = @filesize($tempPath);
123
            // Optimize the image
124
            $this->optimizeImage(
125
                $event->transformIndex,
126
                $tempPath
127
            );
128
            clearstatcache(true, $tempPath);
129
            // Log the results of the image optimization
130
            $optimizedFileSize = @filesize($tempPath);
131
            $index = $event->transformIndex;
132
            Craft::info(
133
                pathinfo($index->filename, PATHINFO_FILENAME)
134
                .'.'
135
                .$index->detectedFormat
136
                .' -> '
137
                .Craft::t('image-optimize', 'Original')
138
                .': '
139
                .$this->humanFileSize($originalFileSize, 1)
140
                .', '
141
                .Craft::t('image-optimize', 'Optimized')
142
                .': '
143
                .$this->humanFileSize($optimizedFileSize, 1)
144
                .' -> '
145
                .Craft::t('image-optimize', 'Savings')
146
                .': '
147
                .number_format(abs(100 - (($optimizedFileSize * 100) / $originalFileSize)), 1)
148
                .'%',
149
                __METHOD__
150
            );
151
            // Create any image variants
152
            $this->createImageVariants(
153
                $event->transformIndex,
154
                $event->asset,
155
                $tempPath
156
            );
157
        }
158
        Craft::endProfile('handleGenerateTransformEvent', __METHOD__);
159
160
        return $tempPath;
161
    }
162
163
    /**
164
     * Handle cleaning up any variant creator images
165
     *
166
     * @param AssetTransformImageEvent $event
167
     */
168
    public function handleAfterDeleteTransformsEvent(AssetTransformImageEvent $event)
169
    {
170
        $settings = ImageOptimize::$plugin->getSettings();
171
        // Only do this for local Craft transforms
172
        if ($settings->transformMethod == 'craft' && !empty($event->asset)) {
173
            $this->cleanupImageVariants($event->asset, $event->transformIndex);
174
        }
175
    }
176
177
    /**
178
     * Save out the image to a temp file
179
     *
180
     * @param AssetTransformIndex $index
181
     * @param Image               $image
182
     *
183
     * @return string
184
     */
185
    public function saveTransformToTempFile(AssetTransformIndex $index, Image $image): string
186
    {
187
        $tempFilename = uniqid(pathinfo($index->filename, PATHINFO_FILENAME), true).'.'.$index->detectedFormat;
188
        $tempPath = Craft::$app->getPath()->getTempPath().DIRECTORY_SEPARATOR.$tempFilename;
189
        try {
190
            $image->saveAs($tempPath);
191
        } catch (ImageException $e) {
192
            Craft::error('Transformed image save failed: '.$e->getMessage(), __METHOD__);
193
        }
194
        Craft::info('Transformed image saved to: '.$tempPath, __METHOD__);
195
196
        return $tempPath;
197
    }
198
199
    /**
200
     * Run any image post-processing/optimization on the image file
201
     *
202
     * @param AssetTransformIndex $index
203
     * @param string              $tempPath
204
     */
205
    public function optimizeImage(AssetTransformIndex $index, string $tempPath)
206
    {
207
        Craft::beginProfile('optimizeImage', __METHOD__);
208
        $settings = ImageOptimize::$plugin->getSettings();
209
        // Get the active processors for the transform format
210
        $activeImageProcessors = $settings->activeImageProcessors;
211
        $fileFormat = $index->detectedFormat;
212
        if (!empty($activeImageProcessors[$fileFormat])) {
213
            // Iterate through all of the processors for this format
214
            $imageProcessors = $settings->imageProcessors;
215
            if (!empty($activeImageProcessors[$fileFormat])) {
216
                foreach ($activeImageProcessors[$fileFormat] as $processor) {
217
                    if (!empty($processor) && !empty($imageProcessors[$processor])) {
218
                        $this->executeImageProcessor($imageProcessors[$processor], $tempPath);
219
                    }
220
                }
221
            }
222
        }
223
        Craft::endProfile('optimizeImage', __METHOD__);
224
    }
225
226
    /**
227
     * Translate bytes into something human-readable
228
     *
229
     * @param     $bytes
230
     * @param int $decimals
231
     *
232
     * @return string
233
     */
234
    public function humanFileSize($bytes, $decimals = 1): string
235
    {
236
        $oldSize = Craft::$app->formatter->sizeFormatBase;
237
        Craft::$app->formatter->sizeFormatBase = 1000;
238
        $result = Craft::$app->formatter->asShortSize($bytes, $decimals);
239
        Craft::$app->formatter->sizeFormatBase = $oldSize;
240
241
        return $result;
242
    }
243
244
    /**
245
     * Create any image variants for the image file
246
     *
247
     * @param AssetTransformIndex $index
248
     * @param Asset               $asset
249
     * @param string              $tempPath
250
     */
251
    public function createImageVariants(AssetTransformIndex $index, Asset $asset, string $tempPath)
252
    {
253
        Craft::beginProfile('createImageVariants', __METHOD__);
254
        $settings = ImageOptimize::$plugin->getSettings();
255
        // Get the active image variant creators
256
        $activeImageVariantCreators = $settings->activeImageVariantCreators;
257
        $fileFormat = $index->detectedFormat ?? $index->format;
258
        if (!empty($activeImageVariantCreators[$fileFormat])) {
259
            // Iterate through all of the image variant creators for this format
260
            $imageVariantCreators = $settings->imageVariantCreators;
261
            if (!empty($activeImageVariantCreators[$fileFormat])) {
262
                foreach ($activeImageVariantCreators[$fileFormat] as $variantCreator) {
263
                    if (!empty($variantCreator) && !empty($imageVariantCreators[$variantCreator])) {
264
                        // Create the image variant in a temporary folder
265
                        $generalConfig = Craft::$app->getConfig()->getGeneral();
266
                        $quality = $index->transform->quality ?: $generalConfig->defaultImageQuality;
267
                        $outputPath = $this->executeVariantCreator(
268
                            $imageVariantCreators[$variantCreator],
269
                            $tempPath,
270
                            $quality
271
                        );
272
273
                        if (!empty($outputPath)) {
274
                            // Get info on the original and the created variant
275
                            $originalFileSize = @filesize($tempPath);
276
                            $variantFileSize = @filesize($outputPath);
277
278
                            Craft::info(
279
                                pathinfo($tempPath, PATHINFO_FILENAME)
280
                                .'.'
281
                                .pathinfo($tempPath, PATHINFO_EXTENSION)
282
                                .' -> '
283
                                .pathinfo($outputPath, PATHINFO_FILENAME)
284
                                .'.'
285
                                .pathinfo($outputPath, PATHINFO_EXTENSION)
286
                                .' -> '
287
                                .Craft::t('image-optimize', 'Original')
288
                                .': '
289
                                .$this->humanFileSize($originalFileSize, 1)
290
                                .', '
291
                                .Craft::t('image-optimize', 'Variant')
292
                                .': '
293
                                .$this->humanFileSize($variantFileSize, 1)
294
                                .' -> '
295
                                .Craft::t('image-optimize', 'Savings')
296
                                .': '
297
                                .number_format(abs(100 - (($variantFileSize * 100) / $originalFileSize)), 1)
298
                                .'%',
299
                                __METHOD__
300
                            );
301
302
                            // Copy the image variant into place
303
                            $this->copyImageVariantToVolume(
304
                                $imageVariantCreators[$variantCreator],
305
                                $asset,
306
                                $index,
307
                                $outputPath
308
                            );
309
                        }
310
                    }
311
                }
312
            }
313
        }
314
        Craft::endProfile('createImageVariants', __METHOD__);
315
    }
316
317
    /**
318
     * Return an array of active image processors
319
     *
320
     * @return array
321
     */
322
    public function getActiveImageProcessors(): array
323
    {
324
        $result = [];
325
        $settings = ImageOptimize::$plugin->getSettings();
326
        // Get the active processors for the transform format
327
        $activeImageProcessors = $settings->activeImageProcessors;
328
        foreach ($activeImageProcessors as $imageFormat => $imageProcessor) {
329
            // Iterate through all of the processors for this format
330
            $imageProcessors = $settings->imageProcessors;
331
            foreach ($activeImageProcessors[$imageFormat] as $processor) {
332
                if (!empty($imageProcessors[$processor])) {
333
                    $thisImageProcessor = $imageProcessors[$processor];
334
                    $result[] = [
335
                        'format'    => $imageFormat,
336
                        'creator'   => $processor,
337
                        'command'   => $thisImageProcessor['commandPath']
338
                            .' '
339
                            .$thisImageProcessor['commandOptions'],
340
                        'installed' => is_file($thisImageProcessor['commandPath']),
341
                    ];
342
                }
343
            }
344
        }
345
346
        return $result;
347
    }
348
349
    /**
350
     * Return an array of active image variant creators
351
     *
352
     * @return array
353
     */
354
    public function getActiveVariantCreators(): array
355
    {
356
        $result = [];
357
        $settings = ImageOptimize::$plugin->getSettings();
358
        // Get the active image variant creators
359
        $activeImageVariantCreators = $settings->activeImageVariantCreators;
360
        foreach ($activeImageVariantCreators as $imageFormat => $imageCreator) {
361
            // Iterate through all of the image variant creators for this format
362
            $imageVariantCreators = $settings->imageVariantCreators;
363
            foreach ($activeImageVariantCreators[$imageFormat] as $variantCreator) {
364
                if (!empty($imageVariantCreators[$variantCreator])) {
365
                    $thisVariantCreator = $imageVariantCreators[$variantCreator];
366
                    $result[] = [
367
                        'format'    => $imageFormat,
368
                        'creator'   => $variantCreator,
369
                        'command'   => $thisVariantCreator['commandPath']
370
                            .' '
371
                            .$thisVariantCreator['commandOptions'],
372
                        'installed' => is_file($thisVariantCreator['commandPath']),
373
                    ];
374
                }
375
            }
376
        }
377
378
        return $result;
379
    }
380
381
    // Protected Methods
382
    // =========================================================================
383
384
    /** @noinspection PhpUnusedParameterInspection
385
     * @param AssetTransform $transform
386
     * @param Asset          $asset
387
     * @param Image          $image
388
     */
389
    protected function applyFiltersToImage(AssetTransform $transform, Asset $asset, Image $image)
390
    {
391
        $settings = ImageOptimize::$plugin->getSettings();
392
        // Only try to apply filters to Raster images
393
        if ($image instanceof Raster) {
394
            $imagineImage = $image->getImagineImage();
395
            if ($settings->autoSharpenScaledImages) {
396
                // See if the image has been scaled >= 50%
397
                $widthScale = $asset->getWidth() / $image->getWidth();
398
                $heightScale = $asset->getHeight() / $image->getHeight();
399
                if (($widthScale >= 2.0) || ($heightScale >= 2.0)) {
400
                    $imagineImage->effects()
401
                        ->sharpen();
402
                    Craft::debug(
403
                        Craft::t(
404
                            'image-optimize',
405
                            'Image transform >= 50%, sharpened the transformed image: {name}',
406
                            [
407
                                'name' => $asset->title,
408
                            ]
409
                        ),
410
                        __METHOD__
411
                    );
412
                }
413
            }
414
        }
415
    }
416
417
    /**
418
     * @param string  $tempPath
419
     * @param         $thisProcessor
420
     */
421
    protected function executeImageProcessor($thisProcessor, string $tempPath)
422
    {
423
        // Make sure the command exists
424
        if (is_file($thisProcessor['commandPath'])) {
425
            // Set any options for the command
426
            $commandOptions = '';
427
            if (!empty($thisProcessor['commandOptions'])) {
428
                $commandOptions = ' '
429
                    .$thisProcessor['commandOptions']
430
                    .' ';
431
            }
432
            // Redirect the command output if necessary for this processor
433
            $outputFileFlag = '';
434
            if (!empty($thisProcessor['commandOutputFileFlag'])) {
435
                $outputFileFlag = ' '
436
                    .$thisProcessor['commandOutputFileFlag']
437
                    .' '
438
                    .escapeshellarg($tempPath)
439
                    .' ';
440
            }
441
            // Build the command to execute
442
            $cmd =
443
                $thisProcessor['commandPath']
444
                .$commandOptions
445
                .$outputFileFlag
446
                .escapeshellarg($tempPath);
447
            // Execute the command
448
            $shellOutput = $this->executeShellCommand($cmd);
449
            Craft::info($cmd."\n".$shellOutput, __METHOD__);
450
        } else {
451
            Craft::error(
452
                $thisProcessor['commandPath']
453
                .' '
454
                .Craft::t('image-optimize', 'does not exist'),
455
                __METHOD__
456
            );
457
        }
458
    }
459
460
    /**
461
     * Execute a shell command
462
     *
463
     * @param string $command
464
     *
465
     * @return string
466
     */
467
    protected function executeShellCommand(string $command): string
468
    {
469
        // Create the shell command
470
        $shellCommand = new ShellCommand();
471
        $shellCommand->setCommand($command);
472
473
        // If we don't have proc_open, maybe we've got exec
474
        if (!function_exists('proc_open') && function_exists('exec')) {
475
            $shellCommand->useExec = true;
476
        }
477
478
        // Return the result of the command's output or error
479
        if ($shellCommand->execute()) {
480
            $result = $shellCommand->getOutput();
481
        } else {
482
            $result = $shellCommand->getError();
483
        }
484
485
        return $result;
486
    }
487
488
    /**
489
     * @param         $variantCreatorCommand
490
     * @param string  $tempPath
491
     * @param int     $imageQuality
492
     *
493
     * @return string|null the path to the created variant
494
     */
495
    protected function executeVariantCreator($variantCreatorCommand, string $tempPath, int $imageQuality)
496
    {
497
        $outputPath = $tempPath;
498
        // Make sure the command exists
499
        if (is_file($variantCreatorCommand['commandPath'])) {
500
            // Get the output file for this image variant
501
            $outputPath .= '.'.$variantCreatorCommand['imageVariantExtension'];
502
            // Set any options for the command
503
            $commandOptions = '';
504
            if (!empty($variantCreatorCommand['commandOptions'])) {
505
                $commandOptions = ' '
506
                    .$variantCreatorCommand['commandOptions']
507
                    .' ';
508
            }
509
            // Redirect the command output if necessary for this variantCreator
510
            $outputFileFlag = '';
511
            if (!empty($variantCreatorCommand['commandOutputFileFlag'])) {
512
                $outputFileFlag = ' '
513
                    .$variantCreatorCommand['commandOutputFileFlag']
514
                    .' '
515
                    .escapeshellarg($outputPath)
516
                    .' ';
517
            }
518
            // Get the quality setting of this transform
519
            $commandQualityFlag = '';
520
            if (!empty($variantCreatorCommand['commandQualityFlag'])) {
521
                $commandQualityFlag = ' '
522
                    .$variantCreatorCommand['commandQualityFlag']
523
                    .' '
524
                    .$imageQuality
525
                    .' ';
526
            }
527
            // Build the command to execute
528
            $cmd =
529
                $variantCreatorCommand['commandPath']
530
                .$commandOptions
531
                .$commandQualityFlag
532
                .$outputFileFlag
533
                .escapeshellarg($tempPath);
534
            // Execute the command
535
            $shellOutput = $this->executeShellCommand($cmd);
536
            Craft::info($cmd."\n".$shellOutput, __METHOD__);
537
        } else {
538
            Craft::error(
539
                $variantCreatorCommand['commandPath']
540
                .' '
541
                .Craft::t('image-optimize', 'does not exist'),
542
                __METHOD__
543
            );
544
            $outputPath = null;
545
        }
546
547
        return $outputPath;
548
    }
549
550
    /**
551
     * @param Asset               $asset
552
     * @param AssetTransformIndex $transformIndex
553
     */
554
    protected function cleanupImageVariants(Asset $asset, AssetTransformIndex $transformIndex)
555
    {
556
        $settings = ImageOptimize::$plugin->getSettings();
557
        $assetTransforms = Craft::$app->getAssetTransforms();
558
        // Get the active image variant creators
559
        $activeImageVariantCreators = $settings->activeImageVariantCreators;
560
        $fileFormat = $transformIndex->detectedFormat ?? $transformIndex->format;
561
        if (!empty($activeImageVariantCreators[$fileFormat])) {
562
            // Iterate through all of the image variant creators for this format
563
            $imageVariantCreators = $settings->imageVariantCreators;
564
            if (!empty($activeImageVariantCreators[$fileFormat])) {
565
                foreach ($activeImageVariantCreators[$fileFormat] as $variantCreator) {
566
                    if (!empty($variantCreator) && !empty($imageVariantCreators[$variantCreator])) {
567
                        // Create the image variant in a temporary folder
568
                        $variantCreatorCommand = $imageVariantCreators[$variantCreator];
569
                        try {
570
                            $volume = $asset->getVolume();
571
                        } catch (InvalidConfigException $e) {
572
                            $volume = null;
573
                            Craft::error(
574
                                'Asset volume error: '.$e->getMessage(),
575
                                __METHOD__
576
                            );
577
                        }
578
                        try {
579
                            $variantPath = $asset->getFolder()->path.$assetTransforms->getTransformSubpath(
580
                                $asset,
581
                                $transformIndex
582
                            );
583
                        } catch (InvalidConfigException $e) {
584
                            $variantPath = '';
585
                            Craft::error(
586
                                'Asset folder does not exist: '.$e->getMessage(),
587
                                __METHOD__
588
                            );
589
                        }
590
                        $variantPath .= '.'.$variantCreatorCommand['imageVariantExtension'];
591
                        // Delete the variant file in case it is stale
592
                        try {
593
                            $volume->deleteFile($variantPath);
594
                        } catch (VolumeException $e) {
595
                            // We're fine with that.
596
                        }
597
                        Craft::info(
598
                            'Deleted variant: '.$variantPath,
599
                            __METHOD__
600
                        );
601
                    }
602
                }
603
            }
604
        }
605
    }
606
607
    /**
608
     * @param                     $variantCreatorCommand
609
     * @param Asset               $asset
610
     * @param AssetTransformIndex $index
611
     * @param                     $outputPath
612
     */
613
    protected function copyImageVariantToVolume(
614
        $variantCreatorCommand,
615
        Asset $asset,
616
        AssetTransformIndex $index,
617
        $outputPath
618
    ) {
619
        // If the image variant creation succeeded, copy it into place
620
        if (!empty($outputPath) && is_file($outputPath)) {
621
            // Figure out the resulting path for the image variant
622
            try {
623
                $volume = $asset->getVolume();
624
            } catch (InvalidConfigException $e) {
625
                $volume = null;
626
                Craft::error(
627
                    'Asset volume error: '.$e->getMessage(),
628
                    __METHOD__
629
                );
630
            }
631
            $assetTransforms = Craft::$app->getAssetTransforms();
632
            try {
633
                $transformPath = $asset->getFolder()->path.$assetTransforms->getTransformSubpath($asset, $index);
634
            } catch (InvalidConfigException $e) {
635
                $transformPath = '';
636
                Craft::error(
637
                    'Error getting asset folder: '.$e->getMessage(),
638
                    __METHOD__
639
                );
640
            }
641
            $variantPath = $transformPath.'.'.$variantCreatorCommand['imageVariantExtension'];
642
643
            // Delete the variant file in case it is stale
644
            try {
645
                $volume->deleteFile($variantPath);
646
            } catch (VolumeException $e) {
647
                // We're fine with that.
648
            }
649
650
            Craft::info(
651
                'Variant output path: '.$outputPath.' - Variant path: '.$variantPath,
652
                __METHOD__
653
            );
654
655
            clearstatcache(true, $outputPath);
656
            $stream = @fopen($outputPath, 'rb');
657
            if ($stream !== false) {
658
                // Now create it
659
                try {
660
                    $volume->createFileByStream($variantPath, $stream, []);
661
                } catch (VolumeException $e) {
662
                    Craft::error(
663
                        Craft::t('image-optimize', 'Failed to create image variant at: ')
664
                        .$outputPath,
665
                        __METHOD__
666
                    );
667
                }
668
669
                FileHelper::unlink($outputPath);
670
            }
671
        } else {
672
            Craft::error(
673
                Craft::t('image-optimize', 'Failed to create image variant at: ')
674
                .$outputPath,
675
                __METHOD__
676
            );
677
        }
678
    }
679
680
    /**
681
     * @param string $path
682
     * @param string $extension
683
     *
684
     * @return string
685
     */
686
    protected function swapPathExtension(string $path, string $extension): string
687
    {
688
        $pathParts = pathinfo($path);
689
        $newPath = $pathParts['filename'].'.'.$extension;
690
        if (!empty($pathParts['dirname']) && $pathParts['dirname'] !== '.') {
691
            $newPath = $pathParts['dirname'].DIRECTORY_SEPARATOR.$newPath;
692
            $newPath = preg_replace('#/+#', '/', $newPath);
693
        }
694
695
        return $newPath;
696
    }
697
}
698