ImageTransform::socialTransformHeight()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 22
ccs 0
cts 11
cp 0
rs 9.9
cc 4
nc 6
nop 4
crap 20
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
                if ($asset->getHasFocalPoint()) {
152
                    $transform->position = $asset->getFocalPoint(true);
153
                }
154
                $url = $asset->getUrl($transform, $generateNow);
155
            } catch (InvalidConfigException $e) {
156
                $url = null;
157
            }
158
            if ($url === null) {
159
                $url = '';
160
            }
161
            // If we have a url, add an `mtime` param to cache bust
162
            if (!empty($url) && empty(parse_url($url, PHP_URL_QUERY))) {
163
                $now = new DateTime();
0 ignored issues
show
Unused Code introduced by
The assignment to $now is dead and can be removed.
Loading history...
164
                $newestChange = max($asset->dateModified, $asset->dateUpdated);
165
                $url = UrlHelper::url($url, [
166
                    'mtime' => $newestChange->getTimestamp(),
167
                ]);
168
            }
169
        }
170
        // Check to see if the $url contains a pending image transform
171
        if (!empty($url) && StringHelper::contains($url, 'assets/generate-transform')) {
172
            self::$pendingImageTransforms = true;
173
        }
174
175
        return $url;
176
    }
177
178
    /**
179
     * @param int|Asset $asset the Asset or Asset ID
180
     * @param string $transformName the name of the transform to apply
181
     * @param int|null $siteId
182
     * @param string $transformMode
183
     *
184
     * @return string width of the transformed image
185
     */
186
    public static function socialTransformWidth(
187
        $asset,
188
        $transformName = '',
189
        $siteId = null,
190
        $transformMode = null,
191
    ): string {
192
        $width = '';
193
        $transform = self::createSocialTransform($transformName);
194
        // Let them override the mode
195
        if ($transform !== null) {
196
            $transform->mode = $transformMode ?? $transform->mode;
197
        }
198
        $asset = self::assetFromAssetOrIdOrQuery($asset, $siteId);
199
        if ($asset instanceof Asset) {
200
            $width = $asset->getWidth($transform);
201
            if ($width === null) {
202
                $width = '';
203
            }
204
            $width = (string)$width;
205
        }
206
207
        return $width;
208
    }
209
210
    /**
211
     * @param int|Asset $asset the Asset or Asset ID
212
     * @param string $transformName the name of the transform to apply
213
     * @param int|null $siteId
214
     * @param string $transformMode
215
     *
216
     * @return string width of the transformed image
217
     */
218
    public static function socialTransformHeight(
219
        $asset,
220
        $transformName = '',
221
        $siteId = null,
222
        $transformMode = null,
223
    ): string {
224
        $height = '';
225
        $transform = self::createSocialTransform($transformName);
226
        // Let them override the mode
227
        if ($transform !== null) {
228
            $transform->mode = $transformMode ?? $transform->mode;
229
        }
230
        $asset = self::assetFromAssetOrIdOrQuery($asset, $siteId);
231
        if ($asset instanceof Asset) {
232
            $height = $asset->getHeight($transform);
233
            if ($height === null) {
234
                $height = '';
235
            }
236
            $height = (string)$height;
237
        }
238
239
        return $height;
240
    }
241
242
    /**
243
     * Return an array of Asset elements from an array of element IDs
244
     *
245
     * @param array|string $assetIds
246
     * @param int|null $siteId
247
     *
248
     * @return array
249
     */
250
    public static function assetElementsFromIds($assetIds, $siteId = null): array
251
    {
252
        $elements = Craft::$app->getElements();
253
        $assets = [];
254
        if (!empty($assetIds)) {
255
            if (is_array($assetIds)) {
256
                foreach ($assetIds as $assetId) {
257
                    if (!empty($assetId)) {
258
                        $assets[] = $elements->getElementById((int)$assetId, Asset::class, $siteId);
259
                    }
260
                }
261
            } else {
262
                $assetId = $assetIds;
263
                $assets[] = $elements->getElementById((int)$assetId, Asset::class, $siteId);
264
            }
265
        }
266
267
        return array_filter($assets);
268
    }
269
270
    // Protected Static Methods
271
    // =========================================================================
272
273
    /**
274
     * Return an asset from either an id or an asset
275
     *
276
     * @param int|array|ElementCollection|Asset|ElementQuery $asset the Asset or Asset ID or ElementQuery
277
     * @param int|null $siteId
278
     *
279
     * @return Asset|array|ElementInterface|null
280
     */
281
    protected static function assetFromAssetOrIdOrQuery($asset, $siteId = null)
282
    {
283
        if (empty($asset)) {
284
            return null;
285
        }
286
        // If it's an array (eager loaded Element query), return the first element
287
        if (is_array($asset)) {
288
            return reset($asset);
289
        }
290
        // If it's an asset already, just return it
291
        if ($asset instanceof Asset) {
292
            return $asset;
293
        }
294
        // If it is a Collection, resolve that to an asset
295
        if ($asset instanceof ElementCollection) {
296
            return $asset->first();
297
        }
298
        // If it is an ElementQuery, resolve that to an asset
299
        if ($asset instanceof ElementQuery) {
300
            return $asset->one();
301
        }
302
303
        $resolvedAssetId = (int)$asset;
304
        $resolvedSiteId = $siteId ?? 0;
305
        if (isset(self::$cachedAssetsElements[$resolvedAssetId][$resolvedSiteId])) {
306
            return self::$cachedAssetsElements[$resolvedAssetId][$resolvedSiteId];
307
        }
308
309
        $asset = Craft::$app->getAssets()->getAssetById($resolvedAssetId, $siteId);
310
        self::$cachedAssetsElements[$resolvedAssetId][$resolvedSiteId] = $asset;
311
312
        return $asset;
313
    }
314
315
    /**
316
     * Create a transform from the passed in $transformName
317
     *
318
     * @param string $transformName the name of the transform to apply
319
     *
320
     * @return ImageTransformModel|null
321
     */
322
    protected static function createSocialTransform($transformName = 'base')
323
    {
324
        $transform = null;
325
        if (!empty($transformName)) {
326
            $config = array_merge(
327
                self::$transforms['base'],
328
                self::$transforms[$transformName] ?? self::$transforms['base']
329
            );
330
            $transform = new ImageTransformModel($config);
331
        }
332
333
        return $transform;
334
    }
335
}
336