Passed
Push — v1 ( d83c7a...f9e26d )
by Andrew
22:52 queued 10:13
created

OptimizedImages::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 1
1
<?php
2
/**
3
 * Image Optimize 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\fields;
12
13
use nystudio107\imageoptimize\fields\OptimizedImages as OptimizedImagesField;
14
use nystudio107\imageoptimize\gql\types\generators\OptimizedImagesGenerator;
15
use nystudio107\imageoptimize\assetbundles\optimizedimagesfield\OptimizedImagesFieldAsset;
16
use nystudio107\imageoptimize\ImageOptimize;
17
use nystudio107\imageoptimize\models\OptimizedImage;
18
19
use Craft;
20
use craft\base\ElementInterface;
21
use craft\base\Field;
22
use craft\base\Volume;
23
use craft\elements\Asset;
24
use craft\fields\Matrix;
25
use craft\helpers\Json;
26
use craft\models\FieldLayout;
27
use craft\validators\ArrayValidator;
28
29
use yii\base\InvalidConfigException;
30
use yii\db\Exception;
31
use yii\db\Schema;
32
33
use verbb\supertable\fields\SuperTableField;
0 ignored issues
show
Bug introduced by
The type verbb\supertable\fields\SuperTableField was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
35
/** @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...
36
37
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
38
 * @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...
39
 * @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...
40
 * @since     1.2.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...
41
 */
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...
42
class OptimizedImages extends Field
43
{
44
    // Constants
45
    // =========================================================================
46
47
    const DEFAULT_ASPECT_RATIOS = [
48
        ['x' => 16, 'y' => 9],
49
    ];
50
    const DEFAULT_IMAGE_VARIANTS = [
51
        [
52
            'width'          => 1200,
53
            'useAspectRatio' => true,
54
            'aspectRatioX'   => 16.0,
55
            'aspectRatioY'   => 9.0,
56
            'retinaSizes'    => ['1'],
57
            'quality'        => 82,
58
            'format'         => 'jpg',
59
        ],
60
    ];
61
62
    // Public Properties
63
    // =========================================================================
64
65
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
66
     * @var array
67
     */
68
    public $fieldVolumeSettings = [];
69
70
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
71
     * @var array
72
     */
73
    public $ignoreFilesOfType = [];
74
75
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
76
     * @var bool
77
     */
78
    public $displayOptimizedImageVariants = true;
79
80
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
81
     * @var bool
82
     */
83
    public $displayDominantColorPalette = true;
84
85
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
86
     * @var bool
87
     */
88
    public $displayLazyLoadPlaceholderImages = true;
89
90
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
91
     * @var array
92
     */
93
    public $variants = [];
94
95
    // Private Properties
96
    // =========================================================================
97
98
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
99
     * @var array
100
     */
101
    private $aspectRatios = [];
0 ignored issues
show
Coding Style introduced by
Private member variable "aspectRatios" must be prefixed with an underscore
Loading history...
102
103
    // Static Methods
104
    // =========================================================================
105
106
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $config should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
107
     * @inheritdoc
108
     */
109
    public function __construct(array $config = [])
110
    {
111
        // Unset any deprecated properties
112
        if (!empty($config)) {
113
            unset($config['transformMethod'], $config['imgixDomain']);
114
        }
115
        parent::__construct($config);
116
    }
117
118
    // Public Methods
119
    // =========================================================================
120
121
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
122
     * @inheritdoc
123
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
124
    public static function displayName(): string
125
    {
126
        return 'OptimizedImages';
127
    }
128
129
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
130
     * @inheritdoc
131
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
132
    public function init()
133
    {
134
        parent::init();
135
136
        // Handle cases where the plugin has been uninstalled
137
        if (ImageOptimize::$plugin !== null) {
138
            $settings = ImageOptimize::$plugin->getSettings();
139
            if ($settings) {
0 ignored issues
show
introduced by
$settings is of type nystudio107\imageoptimize\models\Settings, thus it always evaluated to true.
Loading history...
140
                if (empty($this->variants)) {
141
                    $this->variants = $settings->defaultVariants;
142
                }
143
                $this->aspectRatios = $settings->defaultAspectRatios;
144
            }
145
        }
146
        // If the user has deleted all default aspect ratios, provide a fallback
147
        if (empty($this->aspectRatios)) {
148
            $this->aspectRatios = self::DEFAULT_ASPECT_RATIOS;
149
        }
150
        // If the user has deleted all default variants, provide a fallback
151
        if (empty($this->variants)) {
152
            $this->variants = self::DEFAULT_IMAGE_VARIANTS;
153
        }
154
    }
155
156
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
157
     * @inheritdoc
158
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
159
    public function rules()
160
    {
161
        $rules = parent::rules();
162
        $rules = array_merge($rules, [
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...
163
            [
164
                [
165
                    'displayOptimizedImageVariants',
166
                    'displayDominantColorPalette',
167
                    'displayLazyLoadPlaceholderImages',
168
                ],
169
                'boolean',
170
            ],
171
            [
172
                [
173
                    'ignoreFilesOfType',
174
                    'variants',
175
                ],
176
                ArrayValidator::class
177
            ],
178
        ]);
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...
179
180
        return $rules;
181
    }
182
183
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
184
     * @inheritdoc
185
     * @since 1.6.2
0 ignored issues
show
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 6 spaces but found 1
Loading history...
186
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
187
    public function getContentGqlType()
188
    {
189
        $typeArray = OptimizedImagesGenerator::generateTypes($this);
190
191
        return [
192
            'name' => $this->handle,
193
            'description' => 'Optimized Images field',
194
            'type' => array_shift($typeArray),
195
        ];
196
    }
197
198
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $asset should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $isNew should have a doc-comment as per coding-style.
Loading history...
199
     * @inheritdoc
200
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
201
    public function afterElementSave(ElementInterface $asset, bool $isNew)
202
    {
203
        parent::afterElementSave($asset, $isNew);
204
        // Update our OptimizedImages Field data now that the Asset has been saved
205
        if ($asset !== null && $asset instanceof Asset && $asset->id !== null) {
206
            // If the scenario is Asset::SCENARIO_FILEOPS or Asset::SCENARIO_ESSENTIALS treat it as a new asset
207
            $scenario = $asset->getScenario();
208
            if ($isNew || $scenario === Asset::SCENARIO_FILEOPS || $asset->propagating) {
209
                /**
210
                 * If this is a newly uploaded/created Asset, we can save the variants
211
                 * via a queue job to prevent it from blocking
212
                 */
213
                ImageOptimize::$plugin->optimizedImages->resaveAsset($asset->id);
214
            } else {
215
                /**
216
                 * If it's not a newly uploaded/created Asset, they may have edited
217
                 * the image with the ImageEditor, so we need to update the variants
218
                 * immediately, so the AssetSelectorHud displays the new images
219
                 */
220
                try {
221
                    ImageOptimize::$plugin->optimizedImages->updateOptimizedImageFieldData($this, $asset);
222
                } catch (Exception $e) {
223
                    Craft::error($e->getMessage(), __METHOD__);
224
                }
225
            }
226
        }
227
    }
228
229
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $value should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $asset should have a doc-comment as per coding-style.
Loading history...
230
     * @inheritdoc
231
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
232
    public function normalizeValue($value, ElementInterface $asset = null)
233
    {
234
        // If we're passed in a string, assume it's JSON-encoded, and decode it
235
        if (\is_string($value) && !empty($value)) {
236
            $value = Json::decodeIfJson($value);
237
        }
238
        // If we're passed in an array, make a model from it
239
        if (\is_array($value)) {
240
            // Create a new OptimizedImage model and populate it
241
            $model = new OptimizedImage($value);
242
        } elseif ($value instanceof OptimizedImage) {
243
            $model = $value;
244
        } else {
245
            // Just create a new empty model
246
            $model = new OptimizedImage(null);
247
        }
248
249
        return $model;
250
    }
251
252
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
253
     * @inheritdoc
254
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
255
    public function getContentColumnType(): string
256
    {
257
        return Schema::TYPE_TEXT;
258
    }
259
260
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
261
     * @inheritdoc
262
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
263
    public function getSettingsHtml()
264
    {
265
        $namespace = Craft::$app->getView()->getNamespace();
266
        if (strpos($namespace, Matrix::class) !== false || strpos($namespace, SuperTableField::class) !== false) {
267
            // Render an error template, since the field only works when attached to an Asset
268
            try {
269
                return Craft::$app->getView()->renderTemplate(
270
                    'image-optimize/_components/fields/OptimizedImages_error',
271
                    [
272
                    ]
273
                );
274
            } catch (\Twig\Error\LoaderError $e) {
275
                Craft::error($e->getMessage(), __METHOD__);
276
            } catch (\yii\base\Exception $e) {
277
                Craft::error($e->getMessage(), __METHOD__);
278
            }
279
        }
280
281
        try {
282
            $reflect = new \ReflectionClass($this);
283
            $thisId = $reflect->getShortName();
284
        } catch (\ReflectionException $e) {
285
            Craft::error($e->getMessage(), __METHOD__);
286
            $thisId = 0;
287
        }
288
        $id = Craft::$app->getView()->formatInputId($thisId);
289
        $namespacedId = Craft::$app->getView()->namespaceInputId($id);
290
        $namespacePrefix = Craft::$app->getView()->namespaceInputName($thisId);
291
        Craft::$app->getView()->registerJs('new Craft.OptimizedImagesInput('.
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
            '"'.$namespacedId.'", '.
293
            '"'.$namespacePrefix.'"'.
294
            ');');
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 8 spaces, but found 12.
Loading history...
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...
295
296
        // Prep our aspect ratios
297
        $aspectRatios = [];
298
        $index = 1;
299
        foreach ($this->aspectRatios as $aspectRatio) {
300
            if ($index % 6 === 0) {
301
                $aspectRatio['break'] = true;
302
            }
303
            $aspectRatios[] = $aspectRatio;
304
            $index++;
305
        }
306
        $aspectRatio = ['x' => 2, 'y' => 2, 'custom' => true];
307
        $aspectRatios[] = $aspectRatio;
308
309
        // Render the settings template
310
        try {
311
            return Craft::$app->getView()->renderTemplate(
312
                'image-optimize/_components/fields/OptimizedImages_settings',
313
                [
314
                    'field'        => $this,
315
                    'aspectRatios' => $aspectRatios,
316
                    'id'           => $id,
317
                    'name'         => $this->handle,
318
                    'namespace'    => $namespacedId,
319
                    'fieldVolumes' => $this->getFieldVolumeInfo($this->handle),
320
                ]
321
            );
322
        } catch (\Twig\Error\LoaderError $e) {
323
            Craft::error($e->getMessage(), __METHOD__);
324
        } catch (\yii\base\Exception $e) {
325
            Craft::error($e->getMessage(), __METHOD__);
326
        }
327
328
        return '';
329
    }
330
331
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $value should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $element should have a doc-comment as per coding-style.
Loading history...
332
     * @inheritdoc
333
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
334
    public function getInputHtml($value, ElementInterface $element = null): string
335
    {
336
        if ($element !== null && $element instanceof Asset && $this->handle !== null) {
337
            /** @var Asset $element */
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...
338
            // Register our asset bundle
339
            try {
340
                Craft::$app->getView()->registerAssetBundle(OptimizedImagesFieldAsset::class);
341
            } catch (InvalidConfigException $e) {
342
                Craft::error($e->getMessage(), __METHOD__);
343
            }
344
345
            // Get our id and namespace
346
            $id = Craft::$app->getView()->formatInputId($this->handle);
347
            $nameSpaceId = Craft::$app->getView()->namespaceInputId($id);
348
349
            // Variables to pass down to our field JavaScript to let it namespace properly
350
            $jsonVars = [
351
                'id'        => $id,
352
                'name'      => $this->handle,
353
                'namespace' => $nameSpaceId,
354
                'prefix'    => Craft::$app->getView()->namespaceInputId(''),
355
            ];
356
            $jsonVars = Json::encode($jsonVars);
357
            $view = Craft::$app->getView();
358
            $view->registerJs("$('#{$nameSpaceId}-field').ImageOptimizeOptimizedImages(".$jsonVars.");");
359
360
            $settings = ImageOptimize::$plugin->getSettings();
361
362
            // Render the input template
363
            try {
364
                return Craft::$app->getView()->renderTemplate(
365
                    'image-optimize/_components/fields/OptimizedImages_input',
366
                    [
367
                        'name'        => $this->handle,
368
                        'value'       => $value,
369
                        'variants'    => $this->variants,
370
                        'field'       => $this,
371
                        'settings'    => $settings,
372
                        'elementId'   => $element->id,
373
                        'format'      => $element->getExtension(),
374
                        'id'          => $id,
375
                        'nameSpaceId' => $nameSpaceId,
376
                    ]
377
                );
378
            } catch (\Twig\Error\LoaderError $e) {
379
                Craft::error($e->getMessage(), __METHOD__);
380
            } catch (\yii\base\Exception $e) {
381
                Craft::error($e->getMessage(), __METHOD__);
382
            }
383
        }
384
385
        // Render an error template, since the field only works when attached to an Asset
386
        try {
387
            return Craft::$app->getView()->renderTemplate(
388
                'image-optimize/_components/fields/OptimizedImages_error',
389
                [
390
                ]
391
            );
392
        } catch (\Twig\Error\LoaderError $e) {
393
            Craft::error($e->getMessage(), __METHOD__);
394
        } catch (\yii\base\Exception $e) {
395
            Craft::error($e->getMessage(), __METHOD__);
396
        }
397
398
        return '';
399
    }
400
401
    // Protected Methods
402
    // =========================================================================
403
404
    /**
405
     * Returns an array of asset volumes and their sub-folders
406
     *
407
     * @param string|null $fieldHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
408
     *
409
     * @return array
410
     * @throws InvalidConfigException
411
     */
412
    protected function getFieldVolumeInfo($fieldHandle): array
413
    {
414
        $result = [];
415
        if ($fieldHandle !== null) {
416
            $volumes = Craft::$app->getVolumes()->getAllVolumes();
417
            $assets = Craft::$app->getAssets();
418
            foreach ($volumes as $volume) {
419
                if (is_subclass_of($volume, Volume::class)) {
420
                    /** @var Volume $volume */
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...
421
                    if ($this->volumeHasField($volume, $fieldHandle)) {
422
                        $tree = $assets->getFolderTreeByVolumeIds([$volume->id]);
423
                        $result[] = [
424
                            'name' => $volume->name,
425
                            'handle' => $volume->handle,
426
                            'subfolders' => $this->assembleSourceList($tree),
427
                        ];
428
                    }
429
                }
430
            }
431
        }
432
433
        return $result;
434
    }
435
436
    /**
437
     * See if the passed $volume has an OptimizedImagesField with the handle $fieldHandle
438
     *
439
     * @param Volume $volume
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
440
     *
441
     * @param string $fieldHandle
0 ignored issues
show
Coding Style introduced by
Parameter tags must be grouped together in a doc comment
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
442
     *
443
     * @return bool
444
     * @throws InvalidConfigException
445
     */
446
    protected function volumeHasField(Volume $volume, string $fieldHandle): bool
447
    {
448
        $result = false;
449
        /** @var FieldLayout $fieldLayout */
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...
450
        $fieldLayout = $volume->getFieldLayout();
451
        // Loop through the fields in the layout to see if there is an OptimizedImages field
452
        if ($fieldLayout) {
0 ignored issues
show
introduced by
$fieldLayout is of type craft\models\FieldLayout, thus it always evaluated to true.
Loading history...
453
            $fields = $fieldLayout->getFields();
454
            foreach ($fields as $field) {
455
                if ($field instanceof OptimizedImagesField && $field->handle === $fieldHandle) {
456
                    $result = true;
457
                }
458
            }
459
        }
460
461
        return $result;
462
    }
463
464
    /**
465
     * Transforms an asset folder tree into a source list.
466
     *
467
     * @param array $folders
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
468
     * @param bool  $includeNestedFolders
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
469
     *
470
     * @return array
471
     */
472
    protected function assembleSourceList(array $folders, bool $includeNestedFolders = true): array
473
    {
474
        $sources = [];
475
476
        foreach ($folders as $folder) {
477
            $children = $folder->getChildren();
478
            foreach ($children as $child) {
479
                $sources[$child->uid] = $child->name;
480
            }
481
        }
482
483
        return $sources;
484
    }
485
}
486