ImageTransform::socialTransform()   F
last analyzed

Complexity

Conditions 18
Paths 2056

Size

Total Lines 68
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 342

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 37
dl 0
loc 68
ccs 0
cts 56
cp 0
rs 0.7
c 1
b 0
f 0
cc 18
nc 2056
nop 4
crap 342

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