PluginProbe ʕ •ᴥ•ʔ
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 25.5
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v25.5
27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 19.1 19.10 19.11 19.12 19.13 19.14 19.2 19.3 19.4 19.5 19.5.1 19.6 19.6.1 19.7 19.7.1 19.7.2 19.8 19.9 20.0 20.1 20.10 20.11 20.12 20.13 20.2 20.2.1 20.3 20.4 20.5 20.6 20.7 20.8 20.9 21.0 21.1 21.2 21.3 21.4 21.5 21.6 21.7 21.8 21.8.1 21.9 21.9.1 22.0 22.1 22.2 22.3 22.4 22.5 22.6 22.7 22.8 22.9 23.0 23.1 23.2 23.3 23.4 23.5 23.6 23.7 23.8 23.9 24.0 24.1 24.2 24.3 24.4 24.5 24.6 24.7 24.8 24.8.1 24.9 25.0 25.1 25.2 25.3 25.3.1 25.4 25.5 25.6 25.7 25.8 25.9 26.0 26.1 26.1.1 26.2 26.3 26.4 26.5 26.6 26.7 26.8 26.9 27.0 27.1 27.1.1 27.2 27.3 27.4
wordpress-seo / inc / class-wpseo-image-utils.php
wordpress-seo / inc Last commit date
exceptions 5 years ago options 1 year ago sitemaps 1 year ago class-addon-manager.php 11 months ago class-my-yoast-api-request.php 1 year ago class-post-type.php 1 year ago class-rewrite.php 1 year ago class-upgrade-history.php 1 year ago class-upgrade.php 1 year ago class-wpseo-admin-bar-menu.php 1 year ago class-wpseo-content-images.php 1 year ago class-wpseo-custom-fields.php 1 year ago class-wpseo-custom-taxonomies.php 1 year ago class-wpseo-image-utils.php 1 year ago class-wpseo-installation.php 2 years ago class-wpseo-meta.php 1 year ago class-wpseo-primary-term.php 2 years ago class-wpseo-rank.php 1 year ago class-wpseo-replace-vars.php 1 year ago class-wpseo-replacement-variable.php 5 years ago class-wpseo-shortlinker.php 2 years ago class-wpseo-statistics.php 5 years ago class-wpseo-utils.php 11 months ago class-yoast-dynamic-rewrites.php 2 years ago date-helper.php 5 years ago index.php 10 years ago interface-wpseo-wordpress-ajax-integration.php 7 years ago interface-wpseo-wordpress-integration.php 7 years ago language-utils.php 2 years ago wpseo-functions-deprecated.php 2 years ago wpseo-functions.php 2 years ago wpseo-non-ajax-functions.php 5 years ago
class-wpseo-image-utils.php
538 lines
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO
6 */
7
8 /**
9 * WPSEO_Image_Utils.
10 */
11 class WPSEO_Image_Utils {
12
13 /**
14 * Find an attachment ID for a given URL.
15 *
16 * @param string $url The URL to find the attachment for.
17 *
18 * @return int The found attachment ID, or 0 if none was found.
19 */
20 public static function get_attachment_by_url( $url ) {
21 /*
22 * As get_attachment_by_url won't work on resized versions of images,
23 * we strip out the size part of an image URL.
24 */
25 $url = preg_replace( '/(.*)-\d+x\d+\.(jpg|png|gif)$/', '$1.$2', $url );
26
27 static $uploads;
28
29 if ( $uploads === null ) {
30 $uploads = wp_get_upload_dir();
31 }
32
33 // Don't try to do this for external URLs.
34 if ( strpos( $url, $uploads['baseurl'] ) !== 0 ) {
35 return 0;
36 }
37
38 if ( function_exists( 'wpcom_vip_attachment_url_to_postid' ) ) {
39 // @codeCoverageIgnoreStart -- We can't test this properly.
40 return (int) wpcom_vip_attachment_url_to_postid( $url );
41 // @codeCoverageIgnoreEnd -- The rest we _can_ test.
42 }
43
44 return self::attachment_url_to_postid( $url );
45 }
46
47 /**
48 * Implements the attachment_url_to_postid with use of WP Cache.
49 *
50 * @param string $url The attachment URL for which we want to know the Post ID.
51 *
52 * @return int The Post ID belonging to the attachment, 0 if not found.
53 */
54 protected static function attachment_url_to_postid( $url ) {
55 $cache_key = sprintf( 'yoast_attachment_url_post_id_%s', md5( $url ) );
56
57 // Set the ID based on the hashed URL in the cache.
58 $id = wp_cache_get( $cache_key );
59
60 if ( $id === 'not_found' ) {
61 return 0;
62 }
63
64 // ID is found in cache, return.
65 if ( $id !== false ) {
66 return $id;
67 }
68
69 // Note: We use the WP COM version if we can, see above.
70 $id = attachment_url_to_postid( $url );
71
72 if ( empty( $id ) ) {
73 /**
74 * If no ID was found, maybe we're dealing with a scaled big image. So, let's try that.
75 *
76 * @see https://core.trac.wordpress.org/ticket/51058
77 */
78 $id = self::get_scaled_image_id( $url );
79 }
80
81 if ( empty( $id ) ) {
82 wp_cache_set( $cache_key, 'not_found', '', ( 12 * HOUR_IN_SECONDS + wp_rand( 0, ( 4 * HOUR_IN_SECONDS ) ) ) );
83 return 0;
84 }
85
86 // We have the Post ID, but it's not in the cache yet. We do that here and return.
87 wp_cache_set( $cache_key, $id, '', ( 24 * HOUR_IN_SECONDS + wp_rand( 0, ( 12 * HOUR_IN_SECONDS ) ) ) );
88 return $id;
89 }
90
91 /**
92 * Tries getting the ID of a potentially scaled image.
93 *
94 * @param string $url The URL of the image.
95 *
96 * @return int|false The ID of the image or false for failure.
97 */
98 protected static function get_scaled_image_id( $url ) {
99 $path_parts = pathinfo( $url );
100 if ( isset( $path_parts['dirname'], $path_parts['filename'], $path_parts['extension'] ) ) {
101 $scaled_url = trailingslashit( $path_parts['dirname'] ) . $path_parts['filename'] . '-scaled.' . $path_parts['extension'];
102
103 return attachment_url_to_postid( $scaled_url );
104 }
105
106 return false;
107 }
108
109 /**
110 * Retrieves the image data.
111 *
112 * @param array $image Image array with URL and metadata.
113 * @param int $attachment_id Attachment ID.
114 *
115 * @return array|false {
116 * Array of image data
117 *
118 * @type string $alt Image's alt text.
119 * @type string $path Path of image.
120 * @type int $width Width of image.
121 * @type int $height Height of image.
122 * @type string $type Image's MIME type.
123 * @type string $size Image's size.
124 * @type string $url Image's URL.
125 * @type int $filesize The file size in bytes, if already set.
126 * }
127 */
128 public static function get_data( $image, $attachment_id ) {
129 if ( ! is_array( $image ) ) {
130 return false;
131 }
132
133 // Deals with non-set keys and values being null or false.
134 if ( empty( $image['width'] ) || empty( $image['height'] ) ) {
135 return false;
136 }
137
138 $image['id'] = $attachment_id;
139 $image['alt'] = self::get_alt_tag( $attachment_id );
140 $image['pixels'] = ( (int) $image['width'] * (int) $image['height'] );
141
142 if ( ! isset( $image['type'] ) ) {
143 $image['type'] = get_post_mime_type( $attachment_id );
144 }
145
146 /**
147 * Filter: 'wpseo_image_data' - Filter image data.
148 *
149 * Elements with keys not listed in the section will be discarded.
150 *
151 * @param array $image_data {
152 * Array of image data
153 *
154 * @type int id Image's ID as an attachment.
155 * @type string alt Image's alt text.
156 * @type string path Image's path.
157 * @type int width Width of image.
158 * @type int height Height of image.
159 * @type int pixels Number of pixels in the image.
160 * @type string type Image's MIME type.
161 * @type string size Image's size.
162 * @type string url Image's URL.
163 * @type int filesize The file size in bytes, if already set.
164 * }
165 * @param int $attachment_id Attachment ID.
166 */
167 $image = apply_filters( 'wpseo_image_data', $image, $attachment_id );
168
169 // Keep only the keys we need, and nothing else.
170 return array_intersect_key( $image, array_flip( [ 'id', 'alt', 'path', 'width', 'height', 'pixels', 'type', 'size', 'url', 'filesize' ] ) );
171 }
172
173 /**
174 * Checks a size version of an image to see if it's not too heavy.
175 *
176 * @param array $image Image to check the file size of.
177 *
178 * @return bool True when the image is within limits, false if not.
179 */
180 public static function has_usable_file_size( $image ) {
181 if ( ! is_array( $image ) || $image === [] ) {
182 return false;
183 }
184
185 /**
186 * Filter: 'wpseo_image_image_weight_limit' - Determines what the maximum weight
187 * (in bytes) of an image is allowed to be, default is 2 MB.
188 *
189 * @param int $max_bytes The maximum weight (in bytes) of an image.
190 */
191 $max_size = apply_filters( 'wpseo_image_image_weight_limit', 2097152 );
192
193 // We cannot check without a path, so assume it's fine.
194 if ( ! isset( $image['path'] ) ) {
195 return true;
196 }
197
198 return ( self::get_file_size( $image ) <= $max_size );
199 }
200
201 /**
202 * Find the right version of an image based on size.
203 *
204 * @param int $attachment_id Attachment ID.
205 * @param string|array $size Size name, or array of width and height in pixels (e.g [800,400]).
206 *
207 * @return array|false Returns an array with image data on success, false on failure.
208 */
209 public static function get_image( $attachment_id, $size ) {
210 $image = false;
211 if ( $size === 'full' ) {
212 $image = self::get_full_size_image_data( $attachment_id );
213 }
214
215 if ( ! $image ) {
216 $image = image_get_intermediate_size( $attachment_id, $size );
217 }
218
219 if ( ! is_array( $image ) ) {
220 $image_src = wp_get_attachment_image_src( $attachment_id, $size );
221 if ( is_array( $image_src ) && isset( $image_src[1] ) && isset( $image_src[2] ) ) {
222 $image = [];
223 $image['url'] = $image_src[0];
224 $image['width'] = $image_src[1];
225 $image['height'] = $image_src[2];
226 $image['size'] = 'full';
227 }
228 }
229
230 if ( ! $image ) {
231 return false;
232 }
233
234 if ( ! isset( $image['size'] ) ) {
235 $image['size'] = $size;
236 }
237
238 return self::get_data( $image, $attachment_id );
239 }
240
241 /**
242 * Returns the image data for the full size image.
243 *
244 * @param int $attachment_id Attachment ID.
245 *
246 * @return array|false Array when there is a full size image. False if not.
247 */
248 protected static function get_full_size_image_data( $attachment_id ) {
249 $image = wp_get_attachment_metadata( $attachment_id );
250 if ( ! is_array( $image ) ) {
251 return false;
252 }
253
254 $image['url'] = wp_get_attachment_image_url( $attachment_id, 'full' );
255 $image['path'] = get_attached_file( $attachment_id );
256 $image['size'] = 'full';
257
258 return $image;
259 }
260
261 /**
262 * Finds the full file path for a given image file.
263 *
264 * @param string $path The relative file path.
265 *
266 * @return string The full file path.
267 */
268 public static function get_absolute_path( $path ) {
269 static $uploads;
270
271 if ( $uploads === null ) {
272 $uploads = wp_get_upload_dir();
273 }
274
275 // Add the uploads basedir if the path does not start with it.
276 if ( empty( $uploads['error'] ) && strpos( $path, $uploads['basedir'] ) !== 0 ) {
277 return $uploads['basedir'] . DIRECTORY_SEPARATOR . ltrim( $path, DIRECTORY_SEPARATOR );
278 }
279
280 return $path;
281 }
282
283 /**
284 * Get the relative path of the image.
285 *
286 * @param string $img Image URL.
287 *
288 * @return string The expanded image URL.
289 */
290 public static function get_relative_path( $img ) {
291 if ( $img[0] !== '/' ) {
292 return $img;
293 }
294
295 // If it's a relative URL, it's relative to the domain, not necessarily to the WordPress install, we
296 // want to preserve domain name and URL scheme (http / https) though.
297 $parsed_url = wp_parse_url( home_url() );
298 $img = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $img;
299
300 return $img;
301 }
302
303 /**
304 * Get the image file size.
305 *
306 * @param array $image An image array object.
307 *
308 * @return int The file size in bytes.
309 */
310 public static function get_file_size( $image ) {
311 if ( isset( $image['filesize'] ) ) {
312 return $image['filesize'];
313 }
314
315 if ( ! isset( $image['path'] ) ) {
316 return 0;
317 }
318
319 // If the file size for the file is over our limit, we're going to go for a smaller version.
320 if ( function_exists( 'wp_filesize' ) ) {
321 return wp_filesize( self::get_absolute_path( $image['path'] ) );
322 }
323
324 return file_exists( $image['path'] ) ? (int) filesize( $image['path'] ) : 0;
325 }
326
327 /**
328 * Returns the different image variations for consideration.
329 *
330 * @param int $attachment_id The attachment to return the variations for.
331 *
332 * @return array The different variations possible for this attachment ID.
333 */
334 public static function get_variations( $attachment_id ) {
335 $variations = [];
336
337 foreach ( self::get_sizes() as $size ) {
338 $variation = self::get_image( $attachment_id, $size );
339
340 // The get_image function returns false if the size doesn't exist for this attachment.
341 if ( $variation ) {
342 $variations[] = $variation;
343 }
344 }
345
346 return $variations;
347 }
348
349 /**
350 * Check original size of image. If original image is too small, return false, else return true.
351 *
352 * Filters a list of variations by a certain set of usable dimensions.
353 *
354 * @param array $usable_dimensions {
355 * The parameters to check against.
356 *
357 * @type int $min_width Minimum width of image.
358 * @type int $max_width Maximum width of image.
359 * @type int $min_height Minimum height of image.
360 * @type int $max_height Maximum height of image.
361 * }
362 * @param array $variations The variations that should be considered.
363 *
364 * @return array Whether a variation is fit for display or not.
365 */
366 public static function filter_usable_dimensions( $usable_dimensions, $variations ) {
367 $filtered = [];
368
369 foreach ( $variations as $variation ) {
370 $dimensions = $variation;
371
372 if ( self::has_usable_dimensions( $dimensions, $usable_dimensions ) ) {
373 $filtered[] = $variation;
374 }
375 }
376
377 return $filtered;
378 }
379
380 /**
381 * Filters a list of variations by (disk) file size.
382 *
383 * @param array $variations The variations to consider.
384 *
385 * @return array The validations that pass the required file size limits.
386 */
387 public static function filter_usable_file_size( $variations ) {
388 foreach ( $variations as $variation ) {
389 // We return early to prevent measuring the file size of all the variations.
390 if ( self::has_usable_file_size( $variation ) ) {
391 return [ $variation ];
392 }
393 }
394
395 return [];
396 }
397
398 /**
399 * Retrieve the internal WP image file sizes.
400 *
401 * @return array An array of image sizes.
402 */
403 public static function get_sizes() {
404 /**
405 * Filter: 'wpseo_image_sizes' - Determines which image sizes we'll loop through to get an appropriate image.
406 *
407 * @param array<string> $sizes The array of image sizes to loop through.
408 */
409 return apply_filters( 'wpseo_image_sizes', [ 'full', 'large', 'medium_large' ] );
410 }
411
412 /**
413 * Grabs an image alt text.
414 *
415 * @param int $attachment_id The attachment ID.
416 *
417 * @return string The image alt text.
418 */
419 public static function get_alt_tag( $attachment_id ) {
420 return (string) get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
421 }
422
423 /**
424 * Checks whether an img sizes up to the parameters.
425 *
426 * @param array $dimensions The image values.
427 * @param array $usable_dimensions The parameters to check against.
428 *
429 * @return bool True if the image has usable measurements, false if not.
430 */
431 private static function has_usable_dimensions( $dimensions, $usable_dimensions ) {
432 foreach ( [ 'width', 'height' ] as $param ) {
433 $minimum = $usable_dimensions[ 'min_' . $param ];
434 $maximum = $usable_dimensions[ 'max_' . $param ];
435
436 $current = $dimensions[ $param ];
437 if ( ( $current < $minimum ) || ( $current > $maximum ) ) {
438 return false;
439 }
440 }
441
442 return true;
443 }
444
445 /**
446 * Gets the post's first usable content image. Null if none is available.
447 *
448 * @param int|null $post_id The post id.
449 *
450 * @return string|null The image URL.
451 */
452 public static function get_first_usable_content_image_for_post( $post_id = null ) {
453 $post = get_post( $post_id );
454
455 // We know get_post() returns the post or null.
456 if ( ! $post ) {
457 return null;
458 }
459
460 $image_finder = new WPSEO_Content_Images();
461 $images = $image_finder->get_images( $post->ID, $post );
462
463 return self::get_first_image( $images );
464 }
465
466 /**
467 * Gets the term's first usable content image. Null if none is available.
468 *
469 * @param int $term_id The term id.
470 *
471 * @return string|null The image URL.
472 */
473 public static function get_first_content_image_for_term( $term_id ) {
474 $term_description = term_description( $term_id );
475
476 // We know term_description() returns a string which may be empty.
477 if ( $term_description === '' ) {
478 return null;
479 }
480
481 $image_finder = new WPSEO_Content_Images();
482 $images = $image_finder->get_images_from_content( $term_description );
483
484 return self::get_first_image( $images );
485 }
486
487 /**
488 * Retrieves an attachment ID for an image uploaded in the settings.
489 *
490 * Due to self::get_attachment_by_url returning 0 instead of false.
491 * 0 is also a possibility when no ID is available.
492 *
493 * @param string $setting The setting the image is stored in.
494 *
495 * @return int|bool The attachment id, or false or 0 if no ID is available.
496 */
497 public static function get_attachment_id_from_settings( $setting ) {
498 $image_id = WPSEO_Options::get( $setting . '_id', false );
499 if ( $image_id ) {
500 return $image_id;
501 }
502
503 $image = WPSEO_Options::get( $setting, false );
504 if ( $image ) {
505 // There is not an option to put a URL in an image field in the settings anymore, only to upload it through the media manager.
506 // This means an attachment always exists, so doing this is only needed once.
507 $image_id = self::get_attachment_by_url( $image );
508 }
509
510 // Only store a new ID if it is not 0, to prevent an update loop.
511 if ( $image_id ) {
512 WPSEO_Options::set( $setting . '_id', $image_id );
513 }
514
515 return $image_id;
516 }
517
518 /**
519 * Retrieves the first possible image url from an array of images.
520 *
521 * @param array $images The array to extract image url from.
522 *
523 * @return string|null The extracted image url when found, null when not found.
524 */
525 protected static function get_first_image( $images ) {
526 if ( ! is_array( $images ) ) {
527 return null;
528 }
529
530 $images = array_filter( $images );
531 if ( empty( $images ) ) {
532 return null;
533 }
534
535 return reset( $images );
536 }
537 }
538