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