ImageTransform::socialTransform()   F
last analyzed

Complexity

Conditions 18
Paths 2056

Size

Total Lines 68
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 37
c 0
b 0
f 0
dl 0
loc 68
rs 0.7
cc 18
nc 2056
nop 4

How to fix   Long Method    Complexity   

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
 * SEOmatic plugin for Craft CMS
4
 *
5
 * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
6
 * and flexible
7
 *
8
 * @link      https://nystudio107.com
9
 * @copyright Copyright (c) 2017 nystudio107
10
 */
11
12
namespace nystudio107\seomatic\helpers;
13
14
use Craft;
15
use craft\base\ElementInterface;
0 ignored issues
show
Bug introduced by
The type craft\base\ElementInterface 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...
16
use craft\elements\Asset;
17
use craft\elements\db\ElementQuery;
18
use craft\elements\ElementCollection;
19
use craft\fs\Local;
20
use craft\helpers\StringHelper;
21
use craft\models\ImageTransform as ImageTransformModel;
22
use DateTime;
23
use nystudio107\seomatic\helpers\Environment as EnvironmentHelper;
24
use nystudio107\seomatic\Seomatic;
25
use yii\base\InvalidConfigException;
26
use function in_array;
27
use function is_array;
28
29
/**
30
 * @author    nystudio107
31
 * @package   Seomatic
32
 * @since     3.0.0
33
 */
34
class ImageTransform
35
{
36
    // Constants
37
    // =========================================================================
38
39
    public const SOCIAL_TRANSFORM_QUALITY = 82;
40
41
    public const ALLOWED_SOCIAL_MIME_TYPES = [
42
        'image/jpeg',
43
        'image/png',
44
        'image/webp',
45
        'image/gif',
46
    ];
47
48
    public const DEFAULT_SOCIAL_FORMAT = 'jpg';
49
50
    // Static Public Properties
51
    // =========================================================================
52
53
    /**
54
     * @var bool
55
     */
56
    public static $pendingImageTransforms = false;
57
58
    // Static Private Properties
59
    // =========================================================================
60
61
    private static $transforms = [
62
        'base' => [
63
            'format' => null,
64
            'quality' => self::SOCIAL_TRANSFORM_QUALITY,
65
            'width' => 1200,
66
            'height' => 630,
67
            'mode' => 'crop',
68
        ],
69
        'facebook' => [
70
            'width' => 1200,
71
            'height' => 630,
72
        ],
73
        'twitter-summary' => [
74
            'width' => 800,
75
            'height' => 800,
76
        ],
77
        'twitter-large' => [
78
            'width' => 800,
79
            'height' => 418,
80
        ],
81
        'schema-logo' => [
82
            'format' => 'png',
83
            'width' => 600,
84
            'height' => 60,
85
            'mode' => 'fit',
86
        ],
87
    ];
88
89
    private static $cachedAssetsElements = [];
90
91
    // Static Methods
92
    // =========================================================================
93
94
    /**
95
     * Transform the $asset for social media sites in $transformName and
96
     * optional $siteId
97
     *
98
     * @param int|Asset $asset the Asset or Asset ID
99
     * @param string $transformName the name of the transform to apply
100
     * @param int|null $siteId
101
     * @param string $transformMode
102
     *
103
     * @return string URL to the transformed image
104
     */
105
    public static function socialTransform(
106
        $asset,
107
        $transformName = '',
108
        $siteId = null,
109
        $transformMode = null,
110
    ): string {
111
        $url = '';
112
        $transform = self::createSocialTransform($transformName);
113
        // Let them override the mode
114
        if (empty($transformMode)) {
115
            $transformMode = $transform->mode ?? 'crop';
116
        }
117
        if ($transform !== null) {
118
            $transform->mode = $transformMode;
119
        }
120
        $asset = self::assetFromAssetOrIdOrQuery($asset, $siteId);
121
        if (($asset !== null) && ($asset instanceof Asset)) {
122
            // Make sure the format is an allowed format, otherwise explicitly change it
123
            $mimeType = $asset->getMimeType();
124
            if ($transform !== null && !in_array($mimeType, self::ALLOWED_SOCIAL_MIME_TYPES, false)) {
125
                $transform->format = self::DEFAULT_SOCIAL_FORMAT;
126
            }
127
            // Generate a transformed image
128
            $assets = Craft::$app->getAssets();
0 ignored issues
show
Unused Code introduced by
The assignment to $assets is dead and can be removed.
Loading history...
129
            try {
130
                $volume = $asset->getVolume();
131
            } catch (InvalidConfigException $e) {
132
                $volume = null;
133
            }
134
            // If we're not in local dev, tell it to generate the transform immediately so that
135
            // urls like `actions/assets/generate-transform` don't get cached
136
            $generateNow = Seomatic::$environment === EnvironmentHelper::SEOMATIC_DEV_ENV ? null : true;
137
            if ($volume->getFs() instanceof Local) {
138
                // Preflight to ensure that the source asset actually exists to avoid Craft hanging
139
                if (!$volume->fileExists($asset->getPath())) {
140
                    $generateNow = false;
141
                }
142
            } else {
143
                // If this is not a local volume, avoid a potentially long round-trip by
144
                // being paranoid, and defaulting to not generating the image now
145
                // if we're in local dev
146
                if (Seomatic::$environment === EnvironmentHelper::SEOMATIC_DEV_ENV) {
147
                    $generateNow = false;
148
                }
149
            }
150
            try {
151
                $url = $asset->getUrl($transform, $generateNow);
152
            } catch (InvalidConfigException $e) {
153
                $url = null;
154
            }
155
            if ($url === null) {
156
                $url = '';
157
            }
158
            // If we have a url, add an `mtime` param to cache bust
159
            if (!empty($url) && empty(parse_url($url, PHP_URL_QUERY))) {
160
                $now = new DateTime();
0 ignored issues
show
Unused Code introduced by
The assignment to $now is dead and can be removed.
Loading history...
161
                $newestChange = max($asset->dateModified, $asset->dateUpdated);
162
                $url = UrlHelper::url($url, [
163
                    'mtime' => $newestChange->getTimestamp(),
164
                ]);
165
            }
166
        }
167
        // Check to see if the $url contains a pending image transform
168
        if (!empty($url) && StringHelper::contains($url, 'assets/generate-transform')) {
169
            self::$pendingImageTransforms = true;
170
        }
171
172
        return $url;
173
    }
174
175
    /**
176
     * @param int|Asset $asset the Asset or Asset ID
177
     * @param string $transformName the name of the transform to apply
178
     * @param int|null $siteId
179
     * @param string $transformMode
180
     *
181
     * @return string width of the transformed image
182
     */
183
    public static function socialTransformWidth(
184
        $asset,
185
        $transformName = '',
186
        $siteId = null,
187
        $transformMode = null,
188
    ): string {
189
        $width = '';
190
        $transform = self::createSocialTransform($transformName);
191
        // Let them override the mode
192
        if ($transform !== null) {
193
            $transform->mode = $transformMode ?? $transform->mode;
194
        }
195
        $asset = self::assetFromAssetOrIdOrQuery($asset, $siteId);
196
        if ($asset instanceof Asset) {
197
            $width = $asset->getWidth($transform);
198
            if ($width === null) {
199
                $width = '';
200
            }
201
            $width = (string)$width;
202
        }
203
204
        return $width;
205
    }
206
207
    /**
208
     * @param int|Asset $asset the Asset or Asset ID
209
     * @param string $transformName the name of the transform to apply
210
     * @param int|null $siteId
211
     * @param string $transformMode
212
     *
213
     * @return string width of the transformed image
214
     */
215
    public static function socialTransformHeight(
216
        $asset,
217
        $transformName = '',
218
        $siteId = null,
219
        $transformMode = null,
220
    ): string {
221
        $height = '';
222
        $transform = self::createSocialTransform($transformName);
223
        // Let them override the mode
224
        if ($transform !== null) {
225
            $transform->mode = $transformMode ?? $transform->mode;
226
        }
227
        $asset = self::assetFromAssetOrIdOrQuery($asset, $siteId);
228
        if ($asset instanceof Asset) {
229
            $height = $asset->getHeight($transform);
230
            if ($height === null) {
231
                $height = '';
232
            }
233
            $height = (string)$height;
234
        }
235
236
        return $height;
237
    }
238
239
    /**
240
     * Return an array of Asset elements from an array of element IDs
241
     *
242
     * @param array|string $assetIds
243
     * @param int|null $siteId
244
     *
245
     * @return array
246
     */
247
    public static function assetElementsFromIds($assetIds, $siteId = null): array
248
    {
249
        $elements = Craft::$app->getElements();
250
        $assets = [];
251
        if (!empty($assetIds)) {
252
            if (is_array($assetIds)) {
253
                foreach ($assetIds as $assetId) {
254
                    if (!empty($assetId)) {
255
                        $assets[] = $elements->getElementById((int)$assetId, Asset::class, $siteId);
256
                    }
257
                }
258
            } else {
259
                $assetId = $assetIds;
260
                $assets[] = $elements->getElementById((int)$assetId, Asset::class, $siteId);
261
            }
262
        }
263
264
        return array_filter($assets);
265
    }
266
267
    // Protected Static Methods
268
    // =========================================================================
269
270
    /**
271
     * Return an asset from either an id or an asset
272
     *
273
     * @param int|array|ElementCollection|Asset|ElementQuery $asset the Asset or Asset ID or ElementQuery
274
     * @param int|null $siteId
275
     *
276
     * @return Asset|array|ElementInterface|null
277
     */
278
    protected static function assetFromAssetOrIdOrQuery($asset, $siteId = null)
279
    {
280
        if (empty($asset)) {
281
            return null;
282
        }
283
        // If it's an array (eager loaded Element query), return the first element
284
        if (is_array($asset)) {
285
            return reset($asset);
286
        }
287
        // If it's an asset already, just return it
288
        if ($asset instanceof Asset) {
289
            return $asset;
290
        }
291
        // If it is a Collection, resolve that to an asset
292
        if ($asset instanceof ElementCollection) {
293
            return $asset->first();
294
        }
295
        // If it is an ElementQuery, resolve that to an asset
296
        if ($asset instanceof ElementQuery) {
297
            return $asset->one();
298
        }
299
300
        $resolvedAssetId = (int)$asset;
301
        $resolvedSiteId = $siteId ?? 0;
302
        if (isset(self::$cachedAssetsElements[$resolvedAssetId][$resolvedSiteId])) {
303
            return self::$cachedAssetsElements[$resolvedAssetId][$resolvedSiteId];
304
        }
305
306
        $asset = Craft::$app->getAssets()->getAssetById($resolvedAssetId, $siteId);
307
        self::$cachedAssetsElements[$resolvedAssetId][$resolvedSiteId] = $asset;
308
309
        return $asset;
310
    }
311
312
    /**
313
     * Create a transform from the passed in $transformName
314
     *
315
     * @param string $transformName the name of the transform to apply
316
     *
317
     * @return ImageTransformModel|null
318
     */
319
    protected static function createSocialTransform($transformName = 'base')
320
    {
321
        $transform = null;
322
        if (!empty($transformName)) {
323
            $config = array_merge(
324
                self::$transforms['base'],
325
                self::$transforms[$transformName] ?? self::$transforms['base']
326
            );
327
            $transform = new ImageTransformModel($config);
328
        }
329
330
        return $transform;
331
    }
332
}
333