Test Setup Failed
Branch v1 (b1dea4)
by Andrew
03:56
created

OptimizedImages::getContentColumnType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
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
8
 * @copyright Copyright (c) 2017 nystudio107
9
 */
10
11
namespace nystudio107\imageoptimize\fields;
12
13
use craft\fields\Matrix;
14
use nystudio107\imageoptimize\assetbundles\optimizedimagesfield\OptimizedImagesFieldAsset;
15
use nystudio107\imageoptimize\ImageOptimize;
16
use nystudio107\imageoptimize\models\OptimizedImage;
17
18
use Craft;
19
use craft\base\ElementInterface;
20
use craft\base\Field;
21
use craft\elements\Asset;
22
use craft\helpers\Json;
23
use craft\validators\ArrayValidator;
24
25
use yii\base\InvalidConfigException;
26
use yii\db\Exception;
27
use yii\db\Schema;
28
29
/** @noinspection MissingPropertyAnnotationsInspection */
30
31
/**
32
 * @author    nystudio107
33
 * @package   ImageOptimize
34
 * @since     1.2.0
35
 */
36
class OptimizedImages extends Field
37
{
38
    // Constants
39
    // =========================================================================
40
41
    const DEFAULT_ASPECT_RATIOS = [
42
        ['x' => 16, 'y' => 9],
43
    ];
44
    const DEFAULT_IMAGE_VARIANTS = [
45
        [
46
            'width'          => 1200,
47
            'useAspectRatio' => true,
48
            'aspectRatioX'   => 16.0,
49
            'aspectRatioY'   => 9.0,
50
            'retinaSizes'    => ['1'],
51
            'quality'        => 82,
52
            'format'         => 'jpg',
53
        ],
54
    ];
55
56
    // Public Properties
57
    // =========================================================================
58
59
    /**
60
     * @var bool
61
     */
62
    public $displayOptimizedImageVariants = true;
63
64
    /**
65
     * @var bool
66
     */
67
    public $displayDominantColorPalette = true;
68
69
    /**
70
     * @var bool
71
     */
72
    public $displayLazyLoadPlaceholderImages = true;
73
74
    /**
75
     * @var array
76
     */
77
    public $variants = [];
78
79
    // Private Properties
80
    // =========================================================================
81
82
    /**
83
     * @var array
84
     */
85
    private $aspectRatios = [];
86
87
    // Static Methods
88
    // =========================================================================
89
90
    /**
91
     * @inheritdoc
92
     */
93
    public function __construct(array $config = [])
94
    {
95
        // Unset any deprecated properties
96
        if (!empty($config)) {
97
            unset($config['transformMethod'], $config['imgixDomain']);
98
        }
99
        parent::__construct($config);
100
    }
101
102
    // Public Methods
103
    // =========================================================================
104
105
    /**
106
     * @inheritdoc
107
     */
108
    public static function displayName(): string
109
    {
110
        return 'OptimizedImages';
111
    }
112
113
    /**
114
     * @inheritdoc
115
     */
116
    public function init()
117
    {
118
        parent::init();
119
120
        // Handle cases where the plugin has been uninstalled
121
        if (ImageOptimize::$plugin !== null) {
122
            $settings = ImageOptimize::$plugin->getSettings();
123
            if ($settings) {
0 ignored issues
show
introduced by
$settings is of type nystudio107\imageoptimize\models\Settings, thus it always evaluated to true.
Loading history...
124
                if (empty($this->variants)) {
125
                    $this->variants = $settings->defaultVariants;
126
                }
127
                $this->aspectRatios = $settings->defaultAspectRatios;
128
            }
129
        }
130
        // If the user has deleted all default aspect ratios, provide a fallback
131
        if (empty($this->aspectRatios)) {
132
            $this->aspectRatios = self::DEFAULT_ASPECT_RATIOS;
133
        }
134
        // If the user has deleted all default variants, provide a fallback
135
        if (empty($this->variants)) {
136
            $this->variants = self::DEFAULT_IMAGE_VARIANTS;
137
        }
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143
    public function rules()
144
    {
145
        $rules = parent::rules();
146
        $rules = array_merge($rules, [
147
            ['variants', ArrayValidator::class],
148
        ]);
149
150
        return $rules;
151
    }
152
153
    /**
154
     * @inheritdoc
155
     */
156
    public function afterElementSave(ElementInterface $asset, bool $isNew)
157
    {
158
        parent::afterElementSave($asset, $isNew);
159
        // Update our OptimizedImages Field data now that the Asset has been saved
160
        if ($asset !== null && $asset instanceof Asset && $asset->id !== null) {
161
            // If the scenario is Asset::SCENARIO_FILEOPS or Asset::SCENARIO_ESSENTIALS treat it as a new asset
162
            $scenario = $asset->getScenario();
163
            if ($isNew || $scenario === Asset::SCENARIO_FILEOPS || $asset->propagating) {
164
                /**
165
                 * If this is a newly uploaded/created Asset, we can save the variants
166
                 * via a queue job to prevent it from blocking
167
                 */
168
                ImageOptimize::$plugin->optimizedImages->resaveAsset($asset->id);
169
            } else {
170
                /**
171
                 * If it's not a newly uploaded/created Asset, they may have edited
172
                 * the image with the ImageEditor, so we need to update the variants
173
                 * immediately, so the AssetSelectorHud displays the new images
174
                 */
175
                try {
176
                    ImageOptimize::$plugin->optimizedImages->updateOptimizedImageFieldData($this, $asset);
177
                } catch (Exception $e) {
178
                    Craft::error($e->getMessage(), __METHOD__);
179
                }
180
            }
181
        }
182
    }
183
184
    /**
185
     * @inheritdoc
186
     */
187
    public function normalizeValue($value, ElementInterface $asset = null)
188
    {
189
        // If we're passed in a string, assume it's JSON-encoded, and decode it
190
        if (\is_string($value) && !empty($value)) {
191
            $value = Json::decodeIfJson($value);
192
        }
193
        // If we're passed in an array, make a model from it
194
        if (\is_array($value)) {
195
            // Create a new OptimizedImage model and populate it
196
            $model = new OptimizedImage($value);
197
        } elseif ($value instanceof OptimizedImage) {
198
            $model = $value;
199
        } else {
200
            // Just create a new empty model
201
            $model = new OptimizedImage(null);
202
        }
203
204
        return $model;
205
    }
206
207
    /**
208
     * @inheritdoc
209
     */
210
    public function getContentColumnType(): string
211
    {
212
        return Schema::TYPE_TEXT;
213
    }
214
215
    // Protected Methods
216
    // =========================================================================
217
218
    /**
219
     * @inheritdoc
220
     */
221
    public function getSettingsHtml()
222
    {
223
        $namespace = Craft::$app->getView()->getNamespace();
224
        if (strpos($namespace, Matrix::class) !== false) {
225
            // Render an error template, since the field only works when attached to an Asset
226
            try {
227
                return Craft::$app->getView()->renderTemplate(
228
                    'image-optimize/_components/fields/OptimizedImages_error',
229
                    [
230
                    ]
231
                );
232
            } catch (\Twig_Error_Loader $e) {
233
                Craft::error($e->getMessage(), __METHOD__);
234
            } catch (\yii\base\Exception $e) {
235
                Craft::error($e->getMessage(), __METHOD__);
236
            }
237
        }
238
239
        try {
240
            $reflect = new \ReflectionClass($this);
241
            $thisId = $reflect->getShortName();
242
        } catch (\ReflectionException $e) {
243
            Craft::error($e->getMessage(), __METHOD__);
244
            $thisId = 0;
245
        }
246
        $id = Craft::$app->getView()->formatInputId($thisId);
247
        $namespacedId = Craft::$app->getView()->namespaceInputId($id);
248
        $namespacePrefix = Craft::$app->getView()->namespaceInputName($thisId);
249
        Craft::$app->getView()->registerJs('new Craft.OptimizedImagesInput('.
250
            '"'.$namespacedId.'", '.
251
            '"'.$namespacePrefix.'"'.
252
            ');');
253
254
        // Prep our aspect ratios
255
        $aspectRatios = [];
256
        $index = 1;
257
        foreach ($this->aspectRatios as $aspectRatio) {
258
            if ($index % 6 === 0) {
259
                $aspectRatio['break'] = true;
260
            }
261
            $aspectRatios[] = $aspectRatio;
262
            $index++;
263
        }
264
        $aspectRatio = ['x' => 2, 'y' => 2, 'custom' => true];
265
        $aspectRatios[] = $aspectRatio;
266
267
        // Render the settings template
268
        try {
269
            return Craft::$app->getView()->renderTemplate(
270
                'image-optimize/_components/fields/OptimizedImages_settings',
271
                [
272
                    'field'        => $this,
273
                    'aspectRatios' => $aspectRatios,
274
                    'id'           => $id,
275
                    'name'         => $this->handle,
276
                    'namespace'    => $namespacedId,
277
                ]
278
            );
279
        } catch (\Twig_Error_Loader $e) {
280
            Craft::error($e->getMessage(), __METHOD__);
281
        } catch (\yii\base\Exception $e) {
282
            Craft::error($e->getMessage(), __METHOD__);
283
        }
284
285
        return '';
286
    }
287
288
    /**
289
     * @inheritdoc
290
     */
291
    public function getInputHtml($value, ElementInterface $element = null): string
292
    {
293
        if ($element !== null && $element instanceof Asset && $this->handle !== null) {
294
            /** @var Asset $element */
295
            // Register our asset bundle
296
            try {
297
                Craft::$app->getView()->registerAssetBundle(OptimizedImagesFieldAsset::class);
298
            } catch (InvalidConfigException $e) {
299
                Craft::error($e->getMessage(), __METHOD__);
300
            }
301
302
            // Get our id and namespace
303
            $id = Craft::$app->getView()->formatInputId($this->handle);
304
            $nameSpaceId = Craft::$app->getView()->namespaceInputId($id);
305
306
            // Variables to pass down to our field JavaScript to let it namespace properly
307
            $jsonVars = [
308
                'id'        => $id,
309
                'name'      => $this->handle,
310
                'namespace' => $nameSpaceId,
311
                'prefix'    => Craft::$app->getView()->namespaceInputId(''),
312
            ];
313
            $jsonVars = Json::encode($jsonVars);
314
            $view = Craft::$app->getView();
315
            $view->registerJs("$('#{$nameSpaceId}-field').ImageOptimizeOptimizedImages(".$jsonVars.");");
316
317
            $settings = ImageOptimize::$plugin->getSettings();
318
319
            // Render the input template
320
            try {
321
                return Craft::$app->getView()->renderTemplate(
322
                    'image-optimize/_components/fields/OptimizedImages_input',
323
                    [
324
                        'name'        => $this->handle,
325
                        'value'       => $value,
326
                        'variants'    => $this->variants,
327
                        'field'       => $this,
328
                        'settings'    => $settings,
329
                        'elementId'   => $element->id,
330
                        'format'      => $element->getExtension(),
331
                        'id'          => $id,
332
                        'nameSpaceId' => $nameSpaceId,
333
                    ]
334
                );
335
            } catch (\Twig_Error_Loader $e) {
336
                Craft::error($e->getMessage(), __METHOD__);
337
            } catch (\yii\base\Exception $e) {
338
                Craft::error($e->getMessage(), __METHOD__);
339
            }
340
        }
341
342
        // Render an error template, since the field only works when attached to an Asset
343
        try {
344
            return Craft::$app->getView()->renderTemplate(
345
                'image-optimize/_components/fields/OptimizedImages_error',
346
                [
347
                ]
348
            );
349
        } catch (\Twig_Error_Loader $e) {
350
            Craft::error($e->getMessage(), __METHOD__);
351
        } catch (\yii\base\Exception $e) {
352
            Craft::error($e->getMessage(), __METHOD__);
353
        }
354
355
        return '';
356
    }
357
}
358