Passed
Push — v3 ( 1e51d3...178405 )
by Andrew
25:46
created

src/helpers/ImageTransform.php (1 issue)

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 nystudio107\seomatic\Seomatic;
15
use nystudio107\seomatic\helpers\Environment as EnvironmentHelper;
16
17
use Craft;
18
use craft\elements\Asset;
19
use craft\helpers\StringHelper;
20
use craft\models\AssetTransform;
21
use craft\volumes\Local;
22
23
use yii\base\InvalidConfigException;
24
25
/**
26
 * @author    nystudio107
27
 * @package   Seomatic
28
 * @since     3.0.0
29
 */
30
class ImageTransform
31
{
32
    // Constants
33
    // =========================================================================
34
35
    const SOCIAL_TRANSFORM_QUALITY = 82;
36
37
    const ALLOWED_SOCIAL_MIME_TYPES = [
38
        'image/jpeg',
39
        'image/png',
40
    ];
41
42
    const DEFAULT_SOCIAL_FORMAT = 'jpg';
43
44
    // Static Public Properties
45
    // =========================================================================
46
47
    static public $pendingImageTransforms = false;
48
49
    // Static Private Properties
50
    // =========================================================================
51
52
    static private $transforms = [
53
        'base' => [
54
            'format' => null,
55
            'quality' => self::SOCIAL_TRANSFORM_QUALITY,
56
            'width' => 1200,
57
            'height' => 630,
58
            'mode' => 'crop',
59
        ],
60
        'facebook' => [
61
            'width' => 1200,
62
            'height' => 630,
63
        ],
64
        'twitter-summary' => [
65
            'width' => 800,
66
            'height' => 800,
67
        ],
68
        'twitter-large' => [
69
            'width' => 800,
70
            'height' => 418,
71
        ],
72
        'schema-logo' => [
73
            'format' => 'png',
74
            'width' => 600,
75
            'height' => 60,
76
            'mode' => 'fit',
77
        ],
78
    ];
79
80
    // Static Methods
81
    // =========================================================================
82
83
    /**
84
     * Transform the $asset for social media sites in $transformName and
85
     * optional $siteId
86
     *
87
     * @param int|Asset $asset         the Asset or Asset ID
88
     * @param string    $transformName the name of the transform to apply
89
     * @param int|null  $siteId
90
     * @param string    $transformMode
91
     *
92
     * @return string URL to the transformed image
93
     */
94
    public static function socialTransform(
95
        $asset,
96
        $transformName = '',
97
        $siteId = null,
98
        $transformMode = null
99
    ): string {
100
        $url = '';
101
        $transform = self::createSocialTransform($transformName);
102
        // Let them override the mode
103
        if (empty($transformMode)) {
104
            $transformMode = $transform->mode ?? 'crop';
105
        }
106
        if ($transform !== null) {
107
            $transform->mode = $transformMode ?? $transform->mode;
108
        }
109
        $asset = self::assetFromAssetOrId($asset, $siteId);
110
        if (($asset !== null) && ($asset instanceof Asset)) {
111
            // Make sure the format is an allowed format, otherwise explicitly change it
112
            $mimeType = $asset->getMimeType();
113
            if (!\in_array($mimeType, self::ALLOWED_SOCIAL_MIME_TYPES, false)) {
114
                $transform->format = self::DEFAULT_SOCIAL_FORMAT;
115
            }
116
            // Generate a transformed image
117
            $assets = Craft::$app->getAssets();
118
            try {
119
                $volume = $asset->getVolume();
120
            } catch (InvalidConfigException $e) {
121
                $volume = null;
122
            }
123
            // If we're not in local dev, tell it to generate the transform immediately so that
124
            // urls like `actions/assets/generate-transform` don't get cached
125
            $generateNow = Seomatic::$environment === EnvironmentHelper::SEOMATIC_DEV_ENV ? null : true;
126
            if ($volume instanceof Local) {
127
                // Preflight to ensure that the source asset actually exists to avoid Craft hanging
128
                if (!$volume->fileExists($asset->getPath())) {
129
                    $generateNow = false;
130
                }
131
            } else {
132
                // If this is not a local volume, avoid a potentially long round-trip by
133
                // being paranoid, and defaulting to not generating the image now
134
                // if we're in local dev
135
                if (Seomatic::$environment === EnvironmentHelper::SEOMATIC_DEV_ENV) {
136
                    $generateNow = false;
137
                }
138
            }
139
            try {
140
                $url = $assets->getAssetUrl($asset, $transform, $generateNow);
141
            } catch (\Exception $e) {
142
                $url = $asset->getUrl();
143
            }
144
            if ($url === null) {
145
                $url = '';
146
            }
147
            // If we have a url, add an `mtime` param to cache bust
148
            if (!empty($url) && empty(parse_url($url, PHP_URL_QUERY))) {
149
                $url = UrlHelper::url($url, [
150
                    'mtime' => $asset->dateModified->getTimestamp(),
0 ignored issues
show
The method getTimestamp() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

150
                    'mtime' => $asset->dateModified->/** @scrutinizer ignore-call */ getTimestamp(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
151
                ]);
152
            }
153
        }
154
        // Check to see if the $url contains a pending image transform
155
        if (!empty($url) && StringHelper::contains($url, 'assets/generate-transform')) {
156
            self::$pendingImageTransforms = true;
157
        }
158
159
        return $url;
160
    }
161
162
    /**
163
     * @param int|Asset $asset         the Asset or Asset ID
164
     * @param string    $transformName the name of the transform to apply
165
     * @param int|null  $siteId
166
     * @param string    $transformMode
167
     *
168
     * @return string width of the transformed image
169
     */
170
    public static function socialTransformWidth(
171
        $asset,
172
        $transformName = '',
173
        $siteId = null,
174
        $transformMode = null
175
    ): string {
176
        $width = '';
177
        $transform = self::createSocialTransform($transformName);
178
        // Let them override the mode
179
        if ($transform !== null) {
180
            $transform->mode = $transformMode ?? $transform->mode;
181
        }
182
        $asset = self::assetFromAssetOrId($asset, $siteId);
183
        if (($asset !== null) && ($asset instanceof Asset)) {
184
            $width = (string)$asset->getWidth($transform);
185
            if ($width === null) {
186
                $width = '';
187
            }
188
        }
189
190
        return $width;
191
    }
192
193
    /**
194
     * @param int|Asset $asset         the Asset or Asset ID
195
     * @param string    $transformName the name of the transform to apply
196
     * @param int|null  $siteId
197
     * @param string    $transformMode
198
     *
199
     * @return string width of the transformed image
200
     */
201
    public static function socialTransformHeight(
202
        $asset,
203
        $transformName = '',
204
        $siteId = null,
205
        $transformMode = null
206
    ): string {
207
        $height = '';
208
        $transform = self::createSocialTransform($transformName);
209
        // Let them override the mode
210
        if ($transform !== null) {
211
            $transform->mode = $transformMode ?? $transform->mode;
212
        }
213
        $asset = self::assetFromAssetOrId($asset, $siteId);
214
        if (($asset !== null) && ($asset instanceof Asset)) {
215
            $height = (string)$asset->getHeight($transform);
216
            if ($height === null) {
217
                $height = '';
218
            }
219
        }
220
221
        return $height;
222
    }
223
224
    /**
225
     * Return an array of Asset elements from an array of element IDs
226
     *
227
     * @param array|string $assetIds
228
     * @param int|null     $siteId
229
     *
230
     * @return array
231
     */
232
    public static function assetElementsFromIds($assetIds, $siteId = null): array
233
    {
234
        $elements = Craft::$app->getElements();
235
        $assets = [];
236
        if (!empty($assetIds)) {
237
            if (\is_array($assetIds)) {
238
                foreach ($assetIds as $assetId) {
239
                    if (!empty($assetId)) {
240
                        $assets[] = $elements->getElementById((int)$assetId, Asset::class, $siteId);
241
                    }
242
                }
243
            } else {
244
                $assetId = $assetIds;
245
                if (!empty($assetId)) {
246
                    $assets[] = $elements->getElementById((int)$assetId, Asset::class, $siteId);
247
                }
248
            }
249
        }
250
251
        return $assets;
252
    }
253
254
    // Protected Static Methods
255
    // =========================================================================
256
257
    /**
258
     * Return an asset from either an id or an asset
259
     *
260
     * @param int|Asset $asset         the Asset or Asset ID
261
     * @param int|null  $siteId
262
     *
263
     * @return Asset|null
264
     */
265
    protected static function assetFromAssetOrId($asset, $siteId = null)
266
    {
267
        if (empty($asset)) {
268
            return null;
269
        }
270
        return ($asset instanceof Asset) ? $asset : Craft::$app->getAssets()->getAssetById($asset, $siteId);
271
    }
272
273
    /**
274
     * Create a transform from the passed in $transformName
275
     *
276
     * @param string    $transformName the name of the transform to apply
277
     *
278
     * @return AssetTransform|null
279
     */
280
    protected static function createSocialTransform($transformName = 'base')
281
    {
282
        $transform = null;
283
        if (!empty($transformName)) {
284
            $config = array_merge(
285
                self::$transforms['base'],
286
                self::$transforms[$transformName] ?? self::$transforms['base']
287
            );
288
            $transform = new AssetTransform($config);
289
        }
290
291
        return $transform;
292
    }
293
}
294