0 ) {
$max_width = min( (int) $content_width, $max_width );
}
} elseif ( 'large' === $size ) {
/*
* We're inserting a large size image into the editor. If it's a really
* big image we'll scale it down to fit reasonably within the editor
* itself, and within the theme's content width if it's known. The user
* can resize it in the editor if they wish.
*/
$max_width = (int) get_option( 'large_size_w' );
$max_height = (int) get_option( 'large_size_h' );
if ( (int) $content_width > 0 ) {
$max_width = min( (int) $content_width, $max_width );
}
} elseif ( ! empty( $_wp_additional_image_sizes ) && in_array( $size, array_keys( $_wp_additional_image_sizes ), true ) ) {
$max_width = (int) $_wp_additional_image_sizes[ $size ]['width'];
$max_height = (int) $_wp_additional_image_sizes[ $size ]['height'];
// Only in admin. Assume that theme authors know what they're doing.
if ( (int) $content_width > 0 && 'edit' === $context ) {
$max_width = min( (int) $content_width, $max_width );
}
} else { // $size === 'full' has no constraint.
$max_width = $width;
$max_height = $height;
}
/**
* Filters the maximum image size dimensions for the editor.
*
* @since 2.5.0
*
* @param int[] $max_image_size {
* An array of width and height values.
*
* @type int $0 The maximum width in pixels.
* @type int $1 The maximum height in pixels.
* }
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
* @param string $context The context the image is being resized for.
* Possible values are 'display' (like in a theme)
* or 'edit' (like inserting into an editor).
*/
list( $max_width, $max_height ) = apply_filters( 'editor_max_image_size', array( $max_width, $max_height ), $size, $context );
return wp_constrain_dimensions( $width, $height, $max_width, $max_height );
}
/**
* Retrieves width and height attributes using given width and height values.
*
* Both attributes are required in the sense that both parameters must have a
* value, but are optional in that if you set them to false or null, then they
* will not be added to the returned string.
*
* You can set the value using a string, but it will only take numeric values.
* If you wish to put 'px' after the numbers, then it will be stripped out of
* the return.
*
* @since 2.5.0
*
* @param int|string $width Image width in pixels.
* @param int|string $height Image height in pixels.
* @return string HTML attributes for width and, or height.
*/
function image_hwstring( $width, $height ) {
$out = '';
if ( $width ) {
$out .= 'width="' . (int) $width . '" ';
}
if ( $height ) {
$out .= 'height="' . (int) $height . '" ';
}
return $out;
}
/**
* Scales an image to fit a particular size (such as 'thumb' or 'medium').
*
* The URL might be the original image, or it might be a resized version. This
* function won't create a new resized copy, it will just return an already
* resized one if it exists.
*
* A plugin may use the {@see 'image_downsize'} filter to hook into and offer image
* resizing services for images. The hook must return an array with the same
* elements that are normally returned from the function.
*
* @since 2.5.0
*
* @param int $id Attachment ID for image.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
* of width and height values in pixels (in that order). Default 'medium'.
* @return array|false {
* Array of image data, or boolean false if no image is available.
*
* @type string $0 Image source URL.
* @type int $1 Image width in pixels.
* @type int $2 Image height in pixels.
* @type bool $3 Whether the image is a resized image.
* }
*/
function image_downsize( $id, $size = 'medium' ) {
$is_image = wp_attachment_is_image( $id );
/**
* Filters whether to preempt the output of image_downsize().
*
* Returning a truthy value from the filter will effectively short-circuit
* down-sizing the image, returning that value instead.
*
* @since 2.5.0
*
* @param bool|array $downsize Whether to short-circuit the image downsize.
* @param int $id Attachment ID for image.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
*/
$out = apply_filters( 'image_downsize', false, $id, $size );
if ( $out ) {
return $out;
}
$img_url = wp_get_attachment_url( $id );
$meta = wp_get_attachment_metadata( $id );
$width = 0;
$height = 0;
$is_intermediate = false;
$img_url_basename = wp_basename( $img_url );
/*
* If the file isn't an image, attempt to replace its URL with a rendered image from its meta.
* Otherwise, a non-image type could be returned.
*/
if ( ! $is_image ) {
if ( ! empty( $meta['sizes']['full'] ) ) {
$img_url = str_replace( $img_url_basename, $meta['sizes']['full']['file'], $img_url );
$img_url_basename = $meta['sizes']['full']['file'];
$width = $meta['sizes']['full']['width'];
$height = $meta['sizes']['full']['height'];
} else {
return false;
}
}
// Try for a new style intermediate size.
$intermediate = image_get_intermediate_size( $id, $size );
if ( $intermediate ) {
$img_url = str_replace( $img_url_basename, $intermediate['file'], $img_url );
$width = $intermediate['width'];
$height = $intermediate['height'];
$is_intermediate = true;
} elseif ( 'thumbnail' === $size && ! empty( $meta['thumb'] ) && is_string( $meta['thumb'] ) ) {
// Fall back to the old thumbnail.
$imagefile = get_attached_file( $id );
$thumbfile = str_replace( wp_basename( $imagefile ), wp_basename( $meta['thumb'] ), $imagefile );
if ( file_exists( $thumbfile ) ) {
$info = wp_getimagesize( $thumbfile );
if ( $info ) {
$img_url = str_replace( $img_url_basename, wp_basename( $thumbfile ), $img_url );
$width = $info[0];
$height = $info[1];
$is_intermediate = true;
}
}
}
if ( ! $width && ! $height && isset( $meta['width'], $meta['height'] ) ) {
// Any other type: use the real image.
$width = $meta['width'];
$height = $meta['height'];
}
if ( $img_url ) {
// We have the actual image size, but might need to further constrain it if content_width is narrower.
list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
return array( $img_url, $width, $height, $is_intermediate );
}
return false;
}
/**
* Registers a new image size.
*
* @since 2.9.0
*
* @global array $_wp_additional_image_sizes Associative array of additional image sizes.
*
* @param string $name Image size identifier.
* @param int $width Optional. Image width in pixels. Default 0.
* @param int $height Optional. Image height in pixels. Default 0.
* @param bool|array $crop {
* Optional. Image cropping behavior. If false, the image will be scaled (default).
* If true, image will be cropped to the specified dimensions using center positions.
* If an array, the image will be cropped using the array to specify the crop location:
*
* @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
* @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
* }
*/
function add_image_size( $name, $width = 0, $height = 0, $crop = false ) {
global $_wp_additional_image_sizes;
$_wp_additional_image_sizes[ $name ] = array(
'width' => absint( $width ),
'height' => absint( $height ),
'crop' => $crop,
);
}
/**
* Checks if an image size exists.
*
* @since 3.9.0
*
* @param string $name The image size to check.
* @return bool True if the image size exists, false if not.
*/
function has_image_size( $name ) {
$sizes = wp_get_additional_image_sizes();
return isset( $sizes[ $name ] );
}
/**
* Removes a new image size.
*
* @since 3.9.0
*
* @global array $_wp_additional_image_sizes
*
* @param string $name The image size to remove.
* @return bool True if the image size was successfully removed, false on failure.
*/
function remove_image_size( $name ) {
global $_wp_additional_image_sizes;
if ( isset( $_wp_additional_image_sizes[ $name ] ) ) {
unset( $_wp_additional_image_sizes[ $name ] );
return true;
}
return false;
}
/**
* Registers an image size for the post thumbnail.
*
* @since 2.9.0
*
* @see add_image_size() for details on cropping behavior.
*
* @param int $width Image width in pixels.
* @param int $height Image height in pixels.
* @param bool|array $crop {
* Optional. Image cropping behavior. If false, the image will be scaled (default).
* If true, image will be cropped to the specified dimensions using center positions.
* If an array, the image will be cropped using the array to specify the crop location:
*
* @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
* @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
* }
*/
function set_post_thumbnail_size( $width = 0, $height = 0, $crop = false ) {
add_image_size( 'post-thumbnail', $width, $height, $crop );
}
/**
* Gets an img tag for an image attachment, scaling it down if requested.
*
* The {@see 'get_image_tag_class'} filter allows for changing the class name for the
* image without having to use regular expressions on the HTML content. The
* parameters are: what WordPress will use for the class, the Attachment ID,
* image align value, and the size the image should be.
*
* The second filter, {@see 'get_image_tag'}, has the HTML content, which can then be
* further manipulated by a plugin to change all attribute values and even HTML
* content.
*
* @since 2.5.0
*
* @param int $id Attachment ID.
* @param string $alt Image description for the alt attribute.
* @param string $title Image description for the title attribute.
* @param string $align Part of the class name for aligning the image.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of
* width and height values in pixels (in that order). Default 'medium'.
* @return string HTML IMG element for given image attachment.
*/
function get_image_tag( $id, $alt, $title, $align, $size = 'medium' ) {
list( $img_src, $width, $height ) = image_downsize( $id, $size );
$hwstring = image_hwstring( $width, $height );
$title = $title ? 'title="' . esc_attr( $title ) . '" ' : '';
$size_class = is_array( $size ) ? implode( 'x', $size ) : $size;
$class = 'align' . esc_attr( $align ) . ' size-' . esc_attr( $size_class ) . ' wp-image-' . $id;
/**
* Filters the value of the attachment's image tag class attribute.
*
* @since 2.6.0
*
* @param string $class CSS class name or space-separated list of classes.
* @param int $id Attachment ID.
* @param string $align Part of the class name for aligning the image.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
*/
$class = apply_filters( 'get_image_tag_class', $class, $id, $align, $size );
$html = '';
/**
* Filters the HTML content for the image tag.
*
* @since 2.6.0
*
* @param string $html HTML content for the image.
* @param int $id Attachment ID.
* @param string $alt Image description for the alt attribute.
* @param string $title Image description for the title attribute.
* @param string $align Part of the class name for aligning the image.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
*/
return apply_filters( 'get_image_tag', $html, $id, $alt, $title, $align, $size );
}
/**
* Calculates the new dimensions for a down-sampled image.
*
* If either width or height are empty, no constraint is applied on
* that dimension.
*
* @since 2.5.0
*
* @param int $current_width Current width of the image.
* @param int $current_height Current height of the image.
* @param int $max_width Optional. Max width in pixels to constrain to. Default 0.
* @param int $max_height Optional. Max height in pixels to constrain to. Default 0.
* @return int[] {
* An array of width and height values.
*
* @type int $0 The width in pixels.
* @type int $1 The height in pixels.
* }
*/
function wp_constrain_dimensions( $current_width, $current_height, $max_width = 0, $max_height = 0 ) {
if ( ! $max_width && ! $max_height ) {
return array( $current_width, $current_height );
}
$width_ratio = 1.0;
$height_ratio = 1.0;
$did_width = false;
$did_height = false;
if ( $max_width > 0 && $current_width > 0 && $current_width > $max_width ) {
$width_ratio = $max_width / $current_width;
$did_width = true;
}
if ( $max_height > 0 && $current_height > 0 && $current_height > $max_height ) {
$height_ratio = $max_height / $current_height;
$did_height = true;
}
// Calculate the larger/smaller ratios.
$smaller_ratio = min( $width_ratio, $height_ratio );
$larger_ratio = max( $width_ratio, $height_ratio );
if ( (int) round( $current_width * $larger_ratio ) > $max_width || (int) round( $current_height * $larger_ratio ) > $max_height ) {
// The larger ratio is too big. It would result in an overflow.
$ratio = $smaller_ratio;
} else {
// The larger ratio fits, and is likely to be a more "snug" fit.
$ratio = $larger_ratio;
}
// Very small dimensions may result in 0, 1 should be the minimum.
$w = max( 1, (int) round( $current_width * $ratio ) );
$h = max( 1, (int) round( $current_height * $ratio ) );
/*
* Sometimes, due to rounding, we'll end up with a result like this:
* 465x700 in a 177x177 box is 117x176... a pixel short.
* We also have issues with recursive calls resulting in an ever-changing result.
* Constraining to the result of a constraint should yield the original result.
* Thus we look for dimensions that are one pixel shy of the max value and bump them up.
*/
// Note: $did_width means it is possible $smaller_ratio == $width_ratio.
if ( $did_width && $w === $max_width - 1 ) {
$w = $max_width; // Round it up.
}
// Note: $did_height means it is possible $smaller_ratio == $height_ratio.
if ( $did_height && $h === $max_height - 1 ) {
$h = $max_height; // Round it up.
}
/**
* Filters dimensions to constrain down-sampled images to.
*
* @since 4.1.0
*
* @param int[] $dimensions {
* An array of width and height values.
*
* @type int $0 The width in pixels.
* @type int $1 The height in pixels.
* }
* @param int $current_width The current width of the image.
* @param int $current_height The current height of the image.
* @param int $max_width The maximum width permitted.
* @param int $max_height The maximum height permitted.
*/
return apply_filters( 'wp_constrain_dimensions', array( $w, $h ), $current_width, $current_height, $max_width, $max_height );
}
/**
* Retrieves calculated resize dimensions for use in WP_Image_Editor.
*
* Calculates dimensions and coordinates for a resized image that fits
* within a specified width and height.
*
* @since 2.5.0
*
* @param int $orig_w Original width in pixels.
* @param int $orig_h Original height in pixels.
* @param int $dest_w New width in pixels.
* @param int $dest_h New height in pixels.
* @param bool|array $crop {
* Optional. Image cropping behavior. If false, the image will be scaled (default).
* If true, image will be cropped to the specified dimensions using center positions.
* If an array, the image will be cropped using the array to specify the crop location:
*
* @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
* @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
* }
* @return array|false Returned array matches parameters for `imagecopyresampled()`. False on failure.
*/
function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = false ) {
if ( $orig_w <= 0 || $orig_h <= 0 ) {
return false;
}
// At least one of $dest_w or $dest_h must be specific.
if ( $dest_w <= 0 && $dest_h <= 0 ) {
return false;
}
/**
* Filters whether to preempt calculating the image resize dimensions.
*
* Returning a non-null value from the filter will effectively short-circuit
* image_resize_dimensions(), returning that value instead.
*
* @since 3.4.0
*
* @param null|mixed $null Whether to preempt output of the resize dimensions.
* @param int $orig_w Original width in pixels.
* @param int $orig_h Original height in pixels.
* @param int $dest_w New width in pixels.
* @param int $dest_h New height in pixels.
* @param bool|array $crop Whether to crop image to specified width and height or resize.
* An array can specify positioning of the crop area. Default false.
*/
$output = apply_filters( 'image_resize_dimensions', null, $orig_w, $orig_h, $dest_w, $dest_h, $crop );
if ( null !== $output ) {
return $output;
}
// Stop if the destination size is larger than the original image dimensions.
if ( empty( $dest_h ) ) {
if ( $orig_w < $dest_w ) {
return false;
}
} elseif ( empty( $dest_w ) ) {
if ( $orig_h < $dest_h ) {
return false;
}
} else {
if ( $orig_w < $dest_w && $orig_h < $dest_h ) {
return false;
}
}
if ( $crop ) {
/*
* Crop the largest possible portion of the original image that we can size to $dest_w x $dest_h.
* Note that the requested crop dimensions are used as a maximum bounding box for the original image.
* If the original image's width or height is less than the requested width or height
* only the greater one will be cropped.
* For example when the original image is 600x300, and the requested crop dimensions are 400x400,
* the resulting image will be 400x300.
*/
$aspect_ratio = $orig_w / $orig_h;
$new_w = min( $dest_w, $orig_w );
$new_h = min( $dest_h, $orig_h );
if ( ! $new_w ) {
$new_w = (int) round( $new_h * $aspect_ratio );
}
if ( ! $new_h ) {
$new_h = (int) round( $new_w / $aspect_ratio );
}
$size_ratio = max( $new_w / $orig_w, $new_h / $orig_h );
$crop_w = round( $new_w / $size_ratio );
$crop_h = round( $new_h / $size_ratio );
if ( ! is_array( $crop ) || count( $crop ) !== 2 ) {
$crop = array( 'center', 'center' );
}
list( $x, $y ) = $crop;
if ( 'left' === $x ) {
$s_x = 0;
} elseif ( 'right' === $x ) {
$s_x = $orig_w - $crop_w;
} else {
$s_x = floor( ( $orig_w - $crop_w ) / 2 );
}
if ( 'top' === $y ) {
$s_y = 0;
} elseif ( 'bottom' === $y ) {
$s_y = $orig_h - $crop_h;
} else {
$s_y = floor( ( $orig_h - $crop_h ) / 2 );
}
} else {
// Resize using $dest_w x $dest_h as a maximum bounding box.
$crop_w = $orig_w;
$crop_h = $orig_h;
$s_x = 0;
$s_y = 0;
list( $new_w, $new_h ) = wp_constrain_dimensions( $orig_w, $orig_h, $dest_w, $dest_h );
}
if ( wp_fuzzy_number_match( $new_w, $orig_w ) && wp_fuzzy_number_match( $new_h, $orig_h ) ) {
// The new size has virtually the same dimensions as the original image.
/**
* Filters whether to proceed with making an image sub-size with identical dimensions
* with the original/source image. Differences of 1px may be due to rounding and are ignored.
*
* @since 5.3.0
*
* @param bool $proceed The filtered value.
* @param int $orig_w Original image width.
* @param int $orig_h Original image height.
*/
$proceed = (bool) apply_filters( 'wp_image_resize_identical_dimensions', false, $orig_w, $orig_h );
if ( ! $proceed ) {
return false;
}
}
/*
* The return array matches the parameters to imagecopyresampled().
* int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h
*/
return array( 0, 0, (int) $s_x, (int) $s_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h );
}
/**
* Resizes an image to make a thumbnail or intermediate size.
*
* The returned array has the file size, the image width, and image height. The
* {@see 'image_make_intermediate_size'} filter can be used to hook in and change the
* values of the returned array. The only parameter is the resized file path.
*
* @since 2.5.0
*
* @param string $file File path.
* @param int $width Image width.
* @param int $height Image height.
* @param bool|array $crop {
* Optional. Image cropping behavior. If false, the image will be scaled (default).
* If true, image will be cropped to the specified dimensions using center positions.
* If an array, the image will be cropped using the array to specify the crop location:
*
* @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
* @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
* }
* @return array|false Metadata array on success. False if no image was created.
*/
function image_make_intermediate_size( $file, $width, $height, $crop = false ) {
if ( $width || $height ) {
$editor = wp_get_image_editor( $file );
if ( is_wp_error( $editor ) || is_wp_error( $editor->resize( $width, $height, $crop ) ) ) {
return false;
}
$resized_file = $editor->save();
if ( ! is_wp_error( $resized_file ) && $resized_file ) {
unset( $resized_file['path'] );
return $resized_file;
}
}
return false;
}
/**
* Helper function to test if aspect ratios for two images match.
*
* @since 4.6.0
*
* @param int $source_width Width of the first image in pixels.
* @param int $source_height Height of the first image in pixels.
* @param int $target_width Width of the second image in pixels.
* @param int $target_height Height of the second image in pixels.
* @return bool True if aspect ratios match within 1px. False if not.
*/
function wp_image_matches_ratio( $source_width, $source_height, $target_width, $target_height ) {
/*
* To test for varying crops, we constrain the dimensions of the larger image
* to the dimensions of the smaller image and see if they match.
*/
if ( $source_width > $target_width ) {
$constrained_size = wp_constrain_dimensions( $source_width, $source_height, $target_width );
$expected_size = array( $target_width, $target_height );
} else {
$constrained_size = wp_constrain_dimensions( $target_width, $target_height, $source_width );
$expected_size = array( $source_width, $source_height );
}
// If the image dimensions are within 1px of the expected size, we consider it a match.
$matched = ( wp_fuzzy_number_match( $constrained_size[0], $expected_size[0] ) && wp_fuzzy_number_match( $constrained_size[1], $expected_size[1] ) );
return $matched;
}
/**
* Retrieves the image's intermediate size (resized) path, width, and height.
*
* The $size parameter can be an array with the width and height respectively.
* If the size matches the 'sizes' metadata array for width and height, then it
* will be used. If there is no direct match, then the nearest image size larger
* than the specified size will be used. If nothing is found, then the function
* will break out and return false.
*
* The metadata 'sizes' is used for compatible sizes that can be used for the
* parameter $size value.
*
* The url path will be given, when the $size parameter is a string.
*
* If you are passing an array for the $size, you should consider using
* add_image_size() so that a cropped version is generated. It's much more
* efficient than having to find the closest-sized image and then having the
* browser scale down the image.
*
* @since 2.5.0
*
* @param int $post_id Attachment ID.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
* of width and height values in pixels (in that order). Default 'thumbnail'.
* @return array|false {
* Array of file relative path, width, and height on success. Additionally includes absolute
* path and URL if registered size is passed to `$size` parameter. False on failure.
*
* @type string $file Filename of image.
* @type int $width Width of image in pixels.
* @type int $height Height of image in pixels.
* @type string $path Path of image relative to uploads directory.
* @type string $url URL of image.
* }
*/
function image_get_intermediate_size( $post_id, $size = 'thumbnail' ) {
$imagedata = wp_get_attachment_metadata( $post_id );
if ( ! $size || ! is_array( $imagedata ) || empty( $imagedata['sizes'] ) ) {
return false;
}
$data = array();
// Find the best match when '$size' is an array.
if ( is_array( $size ) ) {
$candidates = array();
if ( ! isset( $imagedata['file'] ) && isset( $imagedata['sizes']['full'] ) ) {
$imagedata['height'] = $imagedata['sizes']['full']['height'];
$imagedata['width'] = $imagedata['sizes']['full']['width'];
}
foreach ( $imagedata['sizes'] as $_size => $data ) {
// If there's an exact match to an existing image size, short circuit.
if ( (int) $data['width'] === (int) $size[0] && (int) $data['height'] === (int) $size[1] ) {
$candidates[ $data['width'] * $data['height'] ] = $data;
break;
}
// If it's not an exact match, consider larger sizes with the same aspect ratio.
if ( $data['width'] >= $size[0] && $data['height'] >= $size[1] ) {
// If '0' is passed to either size, we test ratios against the original file.
if ( 0 === $size[0] || 0 === $size[1] ) {
$same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $imagedata['width'], $imagedata['height'] );
} else {
$same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $size[0], $size[1] );
}
if ( $same_ratio ) {
$candidates[ $data['width'] * $data['height'] ] = $data;
}
}
}
if ( ! empty( $candidates ) ) {
// Sort the array by size if we have more than one candidate.
if ( 1 < count( $candidates ) ) {
ksort( $candidates );
}
$data = array_shift( $candidates );
/*
* When the size requested is smaller than the thumbnail dimensions, we
* fall back to the thumbnail size to maintain backward compatibility with
* pre 4.6 versions of WordPress.
*/
} elseif ( ! empty( $imagedata['sizes']['thumbnail'] ) && $imagedata['sizes']['thumbnail']['width'] >= $size[0] && $imagedata['sizes']['thumbnail']['width'] >= $size[1] ) {
$data = $imagedata['sizes']['thumbnail'];
} else {
return false;
}
// Constrain the width and height attributes to the requested values.
list( $data['width'], $data['height'] ) = image_constrain_size_for_editor( $data['width'], $data['height'], $size );
} elseif ( ! empty( $imagedata['sizes'][ $size ] ) ) {
$data = $imagedata['sizes'][ $size ];
}
// If we still don't have a match at this point, return false.
if ( empty( $data ) ) {
return false;
}
// Include the full filesystem path of the intermediate file.
if ( empty( $data['path'] ) && ! empty( $data['file'] ) && ! empty( $imagedata['file'] ) ) {
$file_url = wp_get_attachment_url( $post_id );
$data['path'] = path_join( dirname( $imagedata['file'] ), $data['file'] );
$data['url'] = path_join( dirname( $file_url ), $data['file'] );
}
/**
* Filters the output of image_get_intermediate_size()
*
* @since 4.4.0
*
* @see image_get_intermediate_size()
*
* @param array $data Array of file relative path, width, and height on success. May also include
* file absolute path and URL.
* @param int $post_id The ID of the image attachment.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
*/
return apply_filters( 'image_get_intermediate_size', $data, $post_id, $size );
}
/**
* Gets the available intermediate image size names.
*
* @since 3.0.0
*
* @return string[] An array of image size names.
*/
function get_intermediate_image_sizes() {
$default_sizes = array( 'thumbnail', 'medium', 'medium_large', 'large' );
$additional_sizes = wp_get_additional_image_sizes();
if ( ! empty( $additional_sizes ) ) {
$default_sizes = array_merge( $default_sizes, array_keys( $additional_sizes ) );
}
/**
* Filters the list of intermediate image sizes.
*
* @since 2.5.0
*
* @param string[] $default_sizes An array of intermediate image size names. Defaults
* are 'thumbnail', 'medium', 'medium_large', 'large'.
*/
return apply_filters( 'intermediate_image_sizes', $default_sizes );
}
/**
* Returns a normalized list of all currently registered image sub-sizes.
*
* @since 5.3.0
* @uses wp_get_additional_image_sizes()
* @uses get_intermediate_image_sizes()
*
* @return array[] Associative array of arrays of image sub-size information,
* keyed by image size name.
*/
function wp_get_registered_image_subsizes() {
$additional_sizes = wp_get_additional_image_sizes();
$all_sizes = array();
foreach ( get_intermediate_image_sizes() as $size_name ) {
$size_data = array(
'width' => 0,
'height' => 0,
'crop' => false,
);
if ( isset( $additional_sizes[ $size_name ]['width'] ) ) {
// For sizes added by plugins and themes.
$size_data['width'] = (int) $additional_sizes[ $size_name ]['width'];
} else {
// For default sizes set in options.
$size_data['width'] = (int) get_option( "{$size_name}_size_w" );
}
if ( isset( $additional_sizes[ $size_name ]['height'] ) ) {
$size_data['height'] = (int) $additional_sizes[ $size_name ]['height'];
} else {
$size_data['height'] = (int) get_option( "{$size_name}_size_h" );
}
if ( empty( $size_data['width'] ) && empty( $size_data['height'] ) ) {
// This size isn't set.
continue;
}
if ( isset( $additional_sizes[ $size_name ]['crop'] ) ) {
$size_data['crop'] = $additional_sizes[ $size_name ]['crop'];
} else {
$size_data['crop'] = get_option( "{$size_name}_crop" );
}
if ( ! is_array( $size_data['crop'] ) || empty( $size_data['crop'] ) ) {
$size_data['crop'] = (bool) $size_data['crop'];
}
$all_sizes[ $size_name ] = $size_data;
}
return $all_sizes;
}
/**
* Retrieves an image to represent an attachment.
*
* @since 2.5.0
*
* @param int $attachment_id Image attachment ID.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of
* width and height values in pixels (in that order). Default 'thumbnail'.
* @param bool $icon Optional. Whether the image should fall back to a mime type icon. Default false.
* @return array|false {
* Array of image data, or boolean false if no image is available.
*
* @type string $0 Image source URL.
* @type int $1 Image width in pixels.
* @type int $2 Image height in pixels.
* @type bool $3 Whether the image is a resized image.
* }
*/
function wp_get_attachment_image_src( $attachment_id, $size = 'thumbnail', $icon = false ) {
// Get a thumbnail or intermediate image if there is one.
$image = image_downsize( $attachment_id, $size );
if ( ! $image ) {
$src = false;
if ( $icon ) {
$src = wp_mime_type_icon( $attachment_id, '.svg' );
if ( $src ) {
/** This filter is documented in wp-includes/post.php */
$icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
$src_file = $icon_dir . '/' . wp_basename( $src );
list( $width, $height ) = wp_getimagesize( $src_file );
$ext = strtolower( substr( $src_file, -4 ) );
if ( '.svg' === $ext ) {
// SVG does not have true dimensions, so this assigns width and height directly.
$width = 48;
$height = 64;
} else {
list( $width, $height ) = wp_getimagesize( $src_file );
}
}
}
if ( $src && $width && $height ) {
$image = array( $src, $width, $height, false );
}
}
/**
* Filters the attachment image source result.
*
* @since 4.3.0
*
* @param array|false $image {
* Array of image data, or boolean false if no image is available.
*
* @type string $0 Image source URL.
* @type int $1 Image width in pixels.
* @type int $2 Image height in pixels.
* @type bool $3 Whether the image is a resized image.
* }
* @param int $attachment_id Image attachment ID.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
* @param bool $icon Whether the image should be treated as an icon.
*/
return apply_filters( 'wp_get_attachment_image_src', $image, $attachment_id, $size, $icon );
}
/**
* Gets an HTML img element representing an image attachment.
*
* While `$size` will accept an array, it is better to register a size with
* add_image_size() so that a cropped version is generated. It's much more
* efficient than having to find the closest-sized image and then having the
* browser scale down the image.
*
* @since 2.5.0
* @since 4.4.0 The `$srcset` and `$sizes` attributes were added.
* @since 5.5.0 The `$loading` attribute was added.
* @since 6.1.0 The `$decoding` attribute was added.
*
* @param int $attachment_id Image attachment ID.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
* of width and height values in pixels (in that order). Default 'thumbnail'.
* @param bool $icon Optional. Whether the image should be treated as an icon. Default false.
* @param string|array $attr {
* Optional. Attributes for the image markup.
*
* @type string $src Image attachment URL.
* @type string $class CSS class name or space-separated list of classes.
* Default `attachment-$size_class size-$size_class`,
* where `$size_class` is the image size being requested.
* @type string $alt Image description for the alt attribute.
* @type string $srcset The 'srcset' attribute value.
* @type string $sizes The 'sizes' attribute value.
* @type string|false $loading The 'loading' attribute value. Passing a value of false
* will result in the attribute being omitted for the image.
* Default determined by {@see wp_get_loading_optimization_attributes()}.
* @type string $decoding The 'decoding' attribute value. Possible values are
* 'async' (default), 'sync', or 'auto'. Passing false or an empty
* string will result in the attribute being omitted.
* @type string $fetchpriority The 'fetchpriority' attribute value, whether `high`, `low`, or `auto`.
* Default determined by {@see wp_get_loading_optimization_attributes()}.
* }
* @return string HTML img element or empty string on failure.
*/
function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = '' ) {
$html = '';
$image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
if ( $image ) {
list( $src, $width, $height ) = $image;
$attachment = get_post( $attachment_id );
$hwstring = image_hwstring( $width, $height );
$size_class = $size;
if ( is_array( $size_class ) ) {
$size_class = implode( 'x', $size_class );
}
$default_attr = array(
'src' => $src,
'class' => "attachment-$size_class size-$size_class",
'alt' => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ),
);
/**
* Filters the context in which wp_get_attachment_image() is used.
*
* @since 6.3.0
*
* @param string $context The context. Default 'wp_get_attachment_image'.
*/
$context = apply_filters( 'wp_get_attachment_image_context', 'wp_get_attachment_image' );
$attr = wp_parse_args( $attr, $default_attr );
$loading_attr = $attr;
$loading_attr['width'] = $width;
$loading_attr['height'] = $height;
$loading_optimization_attr = wp_get_loading_optimization_attributes(
'img',
$loading_attr,
$context
);
// Add loading optimization attributes if not available.
$attr = array_merge( $attr, $loading_optimization_attr );
// Omit the `decoding` attribute if the value is invalid according to the spec.
if ( empty( $attr['decoding'] ) || ! in_array( $attr['decoding'], array( 'async', 'sync', 'auto' ), true ) ) {
unset( $attr['decoding'] );
}
/*
* If the default value of `lazy` for the `loading` attribute is overridden
* to omit the attribute for this image, ensure it is not included.
*/
if ( isset( $attr['loading'] ) && ! $attr['loading'] ) {
unset( $attr['loading'] );
}
// If the `fetchpriority` attribute is overridden and set to false or an empty string.
if ( isset( $attr['fetchpriority'] ) && ! $attr['fetchpriority'] ) {
unset( $attr['fetchpriority'] );
}
// Generate 'srcset' and 'sizes' if not already present.
if ( empty( $attr['srcset'] ) ) {
$image_meta = wp_get_attachment_metadata( $attachment_id );
if ( is_array( $image_meta ) ) {
$size_array = array( absint( $width ), absint( $height ) );
$srcset = wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id );
$sizes = wp_calculate_image_sizes( $size_array, $src, $image_meta, $attachment_id );
if ( $srcset && ( $sizes || ! empty( $attr['sizes'] ) ) ) {
$attr['srcset'] = $srcset;
if ( empty( $attr['sizes'] ) ) {
$attr['sizes'] = $sizes;
}
}
}
}
// Adds 'auto' to the sizes attribute if applicable.
if (
isset( $attr['loading'] ) &&
'lazy' === $attr['loading'] &&
isset( $attr['sizes'] ) &&
! wp_sizes_attribute_includes_valid_auto( $attr['sizes'] )
) {
$attr['sizes'] = 'auto, ' . $attr['sizes'];
}
/**
* Filters the list of attachment image attributes.
*
* @since 2.8.0
*
* @param string[] $attr Array of attribute values for the image markup, keyed by attribute name.
* See wp_get_attachment_image().
* @param WP_Post $attachment Image attachment post.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
*/
$attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $size );
$attr = array_map( 'esc_attr', $attr );
$html = rtrim( " $value ) {
$html .= " $name=" . '"' . $value . '"';
}
$html .= ' />';
}
/**
* Filters the HTML img element representing an image attachment.
*
* @since 5.6.0
*
* @param string $html HTML img element or empty string on failure.
* @param int $attachment_id Image attachment ID.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
* @param bool $icon Whether the image should be treated as an icon.
* @param string[] $attr Array of attribute values for the image markup, keyed by attribute name.
* See wp_get_attachment_image().
*/
return apply_filters( 'wp_get_attachment_image', $html, $attachment_id, $size, $icon, $attr );
}
/**
* Gets the URL of an image attachment.
*
* @since 4.4.0
*
* @param int $attachment_id Image attachment ID.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of
* width and height values in pixels (in that order). Default 'thumbnail'.
* @param bool $icon Optional. Whether the image should be treated as an icon. Default false.
* @return string|false Attachment URL or false if no image is available. If `$size` does not match
* any registered image size, the original image URL will be returned.
*/
function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon = false ) {
$image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
return isset( $image[0] ) ? $image[0] : false;
}
/**
* Gets the attachment path relative to the upload directory.
*
* @since 4.4.1
* @access private
*
* @param string $file Attachment file name.
* @return string Attachment path relative to the upload directory.
*/
function _wp_get_attachment_relative_path( $file ) {
$dirname = dirname( $file );
if ( '.' === $dirname ) {
return '';
}
if ( str_contains( $dirname, 'wp-content/uploads' ) ) {
// Get the directory name relative to the upload directory (back compat for pre-2.7 uploads).
$dirname = substr( $dirname, strpos( $dirname, 'wp-content/uploads' ) + 18 );
$dirname = ltrim( $dirname, '/' );
}
return $dirname;
}
/**
* Gets the image size as array from its meta data.
*
* Used for responsive images.
*
* @since 4.4.0
* @access private
*
* @param string $size_name Image size. Accepts any registered image size name.
* @param array $image_meta The image meta data.
* @return array|false {
* Array of width and height or false if the size isn't present in the meta data.
*
* @type int $0 Image width.
* @type int $1 Image height.
* }
*/
function _wp_get_image_size_from_meta( $size_name, $image_meta ) {
if ( 'full' === $size_name ) {
return array(
absint( $image_meta['width'] ),
absint( $image_meta['height'] ),
);
} elseif ( ! empty( $image_meta['sizes'][ $size_name ] ) ) {
return array(
absint( $image_meta['sizes'][ $size_name ]['width'] ),
absint( $image_meta['sizes'][ $size_name ]['height'] ),
);
}
return false;
}
/**
* Retrieves the value for an image attachment's 'srcset' attribute.
*
* @since 4.4.0
*
* @see wp_calculate_image_srcset()
*
* @param int $attachment_id Image attachment ID.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of
* width and height values in pixels (in that order). Default 'medium'.
* @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
* Default null.
* @return string|false A 'srcset' value string or false.
*/
function wp_get_attachment_image_srcset( $attachment_id, $size = 'medium', $image_meta = null ) {
$image = wp_get_attachment_image_src( $attachment_id, $size );
if ( ! $image ) {
return false;
}
if ( ! is_array( $image_meta ) ) {
$image_meta = wp_get_attachment_metadata( $attachment_id );
}
$image_src = $image[0];
$size_array = array(
absint( $image[1] ),
absint( $image[2] ),
);
return wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );
}
/**
* A helper function to calculate the image sources to include in a 'srcset' attribute.
*
* @since 4.4.0
*
* @param int[] $size_array {
* An array of width and height values.
*
* @type int $0 The width in pixels.
* @type int $1 The height in pixels.
* }
* @param string $image_src The 'src' of the image.
* @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
* @param int $attachment_id Optional. The image attachment ID. Default 0.
* @return string|false The 'srcset' attribute value. False on error or when only one source exists.
*/
function wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id = 0 ) {
/**
* Pre-filters the image meta to be able to fix inconsistencies in the stored data.
*
* @since 4.5.0
*
* @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
* @param int[] $size_array {
* An array of requested width and height values.
*
* @type int $0 The width in pixels.
* @type int $1 The height in pixels.
* }
* @param string $image_src The 'src' of the image.
* @param int $attachment_id The image attachment ID or 0 if not supplied.
*/
$image_meta = apply_filters( 'wp_calculate_image_srcset_meta', $image_meta, $size_array, $image_src, $attachment_id );
if ( empty( $image_meta['sizes'] ) || ! isset( $image_meta['file'] ) || strlen( $image_meta['file'] ) < 4 ) {
return false;
}
$image_sizes = $image_meta['sizes'];
// Get the width and height of the image.
$image_width = (int) $size_array[0];
$image_height = (int) $size_array[1];
// Bail early if error/no width.
if ( $image_width < 1 ) {
return false;
}
$image_basename = wp_basename( $image_meta['file'] );
/*
* WordPress flattens animated GIFs into one frame when generating intermediate sizes.
* To avoid hiding animation in user content, if src is a full size GIF, a srcset attribute is not generated.
* If src is an intermediate size GIF, the full size is excluded from srcset to keep a flattened GIF from becoming animated.
*/
if ( ! isset( $image_sizes['thumbnail']['mime-type'] ) || 'image/gif' !== $image_sizes['thumbnail']['mime-type'] ) {
$image_sizes[] = array(
'width' => $image_meta['width'],
'height' => $image_meta['height'],
'file' => $image_basename,
);
} elseif ( str_contains( $image_src, $image_meta['file'] ) ) {
return false;
}
// Retrieve the uploads sub-directory from the full size image.
$dirname = _wp_get_attachment_relative_path( $image_meta['file'] );
if ( $dirname ) {
$dirname = trailingslashit( $dirname );
}
$upload_dir = wp_get_upload_dir();
$image_baseurl = trailingslashit( $upload_dir['baseurl'] ) . $dirname;
/*
* If currently on HTTPS, prefer HTTPS URLs when we know they're supported by the domain
* (which is to say, when they share the domain name of the current request).
*/
if ( is_ssl() && ! str_starts_with( $image_baseurl, 'https' ) ) {
/*
* Since the `Host:` header might contain a port, it should
* be compared against the image URL using the same port.
*/
$parsed = parse_url( $image_baseurl );
$domain = isset( $parsed['host'] ) ? $parsed['host'] : '';
if ( isset( $parsed['port'] ) ) {
$domain .= ':' . $parsed['port'];
}
if ( $_SERVER['HTTP_HOST'] === $domain ) {
$image_baseurl = set_url_scheme( $image_baseurl, 'https' );
}
}
/*
* Images that have been edited in WordPress after being uploaded will
* contain a unique hash. Look for that hash and use it later to filter
* out images that are leftovers from previous versions.
*/
$image_edited = preg_match( '/-e[0-9]{13}/', wp_basename( $image_src ), $image_edit_hash );
/**
* Filters the maximum image width to be included in a 'srcset' attribute.
*
* @since 4.4.0
*
* @param int $max_width The maximum image width to be included in the 'srcset'. Default '2048'.
* @param int[] $size_array {
* An array of requested width and height values.
*
* @type int $0 The width in pixels.
* @type int $1 The height in pixels.
* }
*/
$max_srcset_image_width = apply_filters( 'max_srcset_image_width', 2048, $size_array );
// Array to hold URL candidates.
$sources = array();
/**
* To make sure the ID matches our image src, we will check to see if any sizes in our attachment
* meta match our $image_src. If no matches are found we don't return a srcset to avoid serving
* an incorrect image. See #35045.
*/
$src_matched = false;
/*
* Loop through available images. Only use images that are resized
* versions of the same edit.
*/
foreach ( $image_sizes as $image ) {
$is_src = false;
// Check if image meta isn't corrupted.
if ( ! is_array( $image ) ) {
continue;
}
// If the file name is part of the `src`, we've confirmed a match.
if ( ! $src_matched && str_contains( $image_src, $dirname . $image['file'] ) ) {
$src_matched = true;
$is_src = true;
}
// Filter out images that are from previous edits.
if ( $image_edited && ! strpos( $image['file'], $image_edit_hash[0] ) ) {
continue;
}
/*
* Filters out images that are wider than '$max_srcset_image_width' unless
* that file is in the 'src' attribute.
*/
if ( $max_srcset_image_width && $image['width'] > $max_srcset_image_width && ! $is_src ) {
continue;
}
// If the image dimensions are within 1px of the expected size, use it.
if ( wp_image_matches_ratio( $image_width, $image_height, $image['width'], $image['height'] ) ) {
// Add the URL, descriptor, and value to the sources array to be returned.
$source = array(
'url' => $image_baseurl . $image['file'],
'descriptor' => 'w',
'value' => $image['width'],
);
// The 'src' image has to be the first in the 'srcset', because of a bug in iOS8. See #35030.
if ( $is_src ) {
$sources = array( $image['width'] => $source ) + $sources;
} else {
$sources[ $image['width'] ] = $source;
}
}
}
/**
* Filters an image's 'srcset' sources.
*
* @since 4.4.0
*
* @param array $sources {
* One or more arrays of source data to include in the 'srcset'.
*
* @type array $width {
* @type string $url The URL of an image source.
* @type string $descriptor The descriptor type used in the image candidate string,
* either 'w' or 'x'.
* @type int $value The source width if paired with a 'w' descriptor, or a
* pixel density value if paired with an 'x' descriptor.
* }
* }
* @param array $size_array {
* An array of requested width and height values.
*
* @type int $0 The width in pixels.
* @type int $1 The height in pixels.
* }
* @param string $image_src The 'src' of the image.
* @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
* @param int $attachment_id Image attachment ID or 0.
*/
$sources = apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id );
// Only return a 'srcset' value if there is more than one source.
if ( ! $src_matched || ! is_array( $sources ) || count( $sources ) < 2 ) {
return false;
}
$srcset = '';
foreach ( $sources as $source ) {
$srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', ';
}
return rtrim( $srcset, ', ' );
}
/**
* Retrieves the value for an image attachment's 'sizes' attribute.
*
* @since 4.4.0
*
* @see wp_calculate_image_sizes()
*
* @param int $attachment_id Image attachment ID.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of
* width and height values in pixels (in that order). Default 'medium'.
* @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
* Default null.
* @return string|false A valid source size value for use in a 'sizes' attribute or false.
*/
function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $image_meta = null ) {
$image = wp_get_attachment_image_src( $attachment_id, $size );
if ( ! $image ) {
return false;
}
if ( ! is_array( $image_meta ) ) {
$image_meta = wp_get_attachment_metadata( $attachment_id );
}
$image_src = $image[0];
$size_array = array(
absint( $image[1] ),
absint( $image[2] ),
);
return wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
}
/**
* Creates a 'sizes' attribute value for an image.
*
* @since 4.4.0
*
* @param string|int[] $size Image size. Accepts any registered image size name, or an array of
* width and height values in pixels (in that order).
* @param string|null $image_src Optional. The URL to the image file. Default null.
* @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
* Default null.
* @param int $attachment_id Optional. Image attachment ID. Either `$image_meta` or `$attachment_id`
* is needed when using the image size name as argument for `$size`. Default 0.
* @return string|false A valid source size value for use in a 'sizes' attribute or false.
*/
function wp_calculate_image_sizes( $size, $image_src = null, $image_meta = null, $attachment_id = 0 ) {
$width = 0;
if ( is_array( $size ) ) {
$width = absint( $size[0] );
} elseif ( is_string( $size ) ) {
if ( ! $image_meta && $attachment_id ) {
$image_meta = wp_get_attachment_metadata( $attachment_id );
}
if ( is_array( $image_meta ) ) {
$size_array = _wp_get_image_size_from_meta( $size, $image_meta );
if ( $size_array ) {
$width = absint( $size_array[0] );
}
}
}
if ( ! $width ) {
return false;
}
// Setup the default 'sizes' attribute.
$sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width );
/**
* Filters the output of 'wp_calculate_image_sizes()'.
*
* @since 4.4.0
*
* @param string $sizes A source size value for use in a 'sizes' attribute.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
* @param string|null $image_src The URL to the image file or null.
* @param array|null $image_meta The image meta data as returned by wp_get_attachment_metadata() or null.
* @param int $attachment_id Image attachment ID of the original image or 0.
*/
return apply_filters( 'wp_calculate_image_sizes', $sizes, $size, $image_src, $image_meta, $attachment_id );
}
/**
* Determines if the image meta data is for the image source file.
*
* The image meta data is retrieved by attachment post ID. In some cases the post IDs may change.
* For example when the website is exported and imported at another website. Then the
* attachment post IDs that are in post_content for the exported website may not match
* the same attachments at the new website.
*
* @since 5.5.0
*
* @param string $image_location The full path or URI to the image file.
* @param array $image_meta The attachment meta data as returned by 'wp_get_attachment_metadata()'.
* @param int $attachment_id Optional. The image attachment ID. Default 0.
* @return bool Whether the image meta is for this image file.
*/
function wp_image_file_matches_image_meta( $image_location, $image_meta, $attachment_id = 0 ) {
$match = false;
// Ensure the $image_meta is valid.
if ( isset( $image_meta['file'] ) && strlen( $image_meta['file'] ) > 4 ) {
// Remove query args in image URI.
list( $image_location ) = explode( '?', $image_location );
// Check if the relative image path from the image meta is at the end of $image_location.
if ( strrpos( $image_location, $image_meta['file'] ) === strlen( $image_location ) - strlen( $image_meta['file'] ) ) {
$match = true;
} else {
// Retrieve the uploads sub-directory from the full size image.
$dirname = _wp_get_attachment_relative_path( $image_meta['file'] );
if ( $dirname ) {
$dirname = trailingslashit( $dirname );
}
if ( ! empty( $image_meta['original_image'] ) ) {
$relative_path = $dirname . $image_meta['original_image'];
if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) {
$match = true;
}
}
if ( ! $match && ! empty( $image_meta['sizes'] ) ) {
foreach ( $image_meta['sizes'] as $image_size_data ) {
$relative_path = $dirname . $image_size_data['file'];
if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) {
$match = true;
break;
}
}
}
}
}
/**
* Filters whether an image path or URI matches image meta.
*
* @since 5.5.0
*
* @param bool $match Whether the image relative path from the image meta
* matches the end of the URI or path to the image file.
* @param string $image_location Full path or URI to the tested image file.
* @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
* @param int $attachment_id The image attachment ID or 0 if not supplied.
*/
return apply_filters( 'wp_image_file_matches_image_meta', $match, $image_location, $image_meta, $attachment_id );
}
/**
* Determines an image's width and height dimensions based on the source file.
*
* @since 5.5.0
*
* @param string $image_src The image source file.
* @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
* @param int $attachment_id Optional. The image attachment ID. Default 0.
* @return array|false Array with first element being the width and second element being the height,
* or false if dimensions cannot be determined.
*/
function wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id = 0 ) {
$dimensions = false;
// Is it a full size image?
if (
isset( $image_meta['file'] ) &&
str_contains( $image_src, wp_basename( $image_meta['file'] ) )
) {
$dimensions = array(
(int) $image_meta['width'],
(int) $image_meta['height'],
);
}
if ( ! $dimensions && ! empty( $image_meta['sizes'] ) ) {
$src_filename = wp_basename( $image_src );
foreach ( $image_meta['sizes'] as $image_size_data ) {
if ( $src_filename === $image_size_data['file'] ) {
$dimensions = array(
(int) $image_size_data['width'],
(int) $image_size_data['height'],
);
break;
}
}
}
/**
* Filters the 'wp_image_src_get_dimensions' value.
*
* @since 5.7.0
*
* @param array|false $dimensions Array with first element being the width
* and second element being the height, or
* false if dimensions could not be determined.
* @param string $image_src The image source file.
* @param array $image_meta The image meta data as returned by
* 'wp_get_attachment_metadata()'.
* @param int $attachment_id The image attachment ID. Default 0.
*/
return apply_filters( 'wp_image_src_get_dimensions', $dimensions, $image_src, $image_meta, $attachment_id );
}
/**
* Adds 'srcset' and 'sizes' attributes to an existing 'img' element.
*
* @since 4.4.0
*
* @see wp_calculate_image_srcset()
* @see wp_calculate_image_sizes()
*
* @param string $image An HTML 'img' element to be filtered.
* @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
* @param int $attachment_id Image attachment ID.
* @return string Converted 'img' element with 'srcset' and 'sizes' attributes added.
*/
function wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ) {
// Ensure the image meta exists.
if ( empty( $image_meta['sizes'] ) ) {
return $image;
}
$image_src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
list( $image_src ) = explode( '?', $image_src );
// Return early if we couldn't get the image source.
if ( ! $image_src ) {
return $image;
}
// Bail early if an image has been inserted and later edited.
if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash )
&& ! str_contains( wp_basename( $image_src ), $img_edit_hash[0] )
) {
return $image;
}
$width = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0;
$height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0;
if ( $width && $height ) {
$size_array = array( $width, $height );
} else {
$size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id );
if ( ! $size_array ) {
return $image;
}
}
$srcset = wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );
if ( $srcset ) {
// Check if there is already a 'sizes' attribute.
$sizes = strpos( $image, ' sizes=' );
if ( ! $sizes ) {
$sizes = wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
}
}
if ( $srcset && $sizes ) {
// Format the 'srcset' and 'sizes' string and escape attributes.
$attr = sprintf( ' srcset="%s"', esc_attr( $srcset ) );
if ( is_string( $sizes ) ) {
$attr .= sprintf( ' sizes="%s"', esc_attr( $sizes ) );
}
// Add the srcset and sizes attributes to the image markup.
return preg_replace( '/]+?)[\/ ]*>/', '', $image );
}
return $image;
}
/**
* Determines whether to add the `loading` attribute to the specified tag in the specified context.
*
* @since 5.5.0
* @since 5.7.0 Now returns `true` by default for `iframe` tags.
*
* @param string $tag_name The tag name.
* @param string $context Additional context, like the current filter name
* or the function name from where this was called.
* @return bool Whether to add the attribute.
*/
function wp_lazy_loading_enabled( $tag_name, $context ) {
/*
* By default add to all 'img' and 'iframe' tags.
* See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading
* See https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-loading
*/
$default = ( 'img' === $tag_name || 'iframe' === $tag_name );
/**
* Filters whether to add the `loading` attribute to the specified tag in the specified context.
*
* @since 5.5.0
*
* @param bool $default Default value.
* @param string $tag_name The tag name.
* @param string $context Additional context, like the current filter name
* or the function name from where this was called.
*/
return (bool) apply_filters( 'wp_lazy_loading_enabled', $default, $tag_name, $context );
}
/**
* Filters specific tags in post content and modifies their markup.
*
* Modifies HTML tags in post content to include new browser and HTML technologies
* that may not have existed at the time of post creation. These modifications currently
* include adding `srcset`, `sizes`, and `loading` attributes to `img` HTML tags, as well
* as adding `loading` attributes to `iframe` HTML tags.
* Future similar optimizations should be added/expected here.
*
* @since 5.5.0
* @since 5.7.0 Now supports adding `loading` attributes to `iframe` tags.
*
* @see wp_img_tag_add_width_and_height_attr()
* @see wp_img_tag_add_srcset_and_sizes_attr()
* @see wp_img_tag_add_loading_optimization_attrs()
* @see wp_iframe_tag_add_loading_attr()
*
* @param string $content The HTML content to be filtered.
* @param string $context Optional. Additional context to pass to the filters.
* Defaults to `current_filter()` when not set.
* @return string Converted content with images modified.
*/
function wp_filter_content_tags( $content, $context = null ) {
if ( null === $context ) {
$context = current_filter();
}
$add_iframe_loading_attr = wp_lazy_loading_enabled( 'iframe', $context );
if ( ! preg_match_all( '/<(img|iframe)\s[^>]+>/', $content, $matches, PREG_SET_ORDER ) ) {
return $content;
}
// List of the unique `img` tags found in $content.
$images = array();
// List of the unique `iframe` tags found in $content.
$iframes = array();
foreach ( $matches as $match ) {
list( $tag, $tag_name ) = $match;
switch ( $tag_name ) {
case 'img':
if ( preg_match( '/wp-image-([0-9]+)/i', $tag, $class_id ) ) {
$attachment_id = absint( $class_id[1] );
if ( $attachment_id ) {
/*
* If exactly the same image tag is used more than once, overwrite it.
* All identical tags will be replaced later with 'str_replace()'.
*/
$images[ $tag ] = $attachment_id;
break;
}
}
$images[ $tag ] = 0;
break;
case 'iframe':
$iframes[ $tag ] = 0;
break;
}
}
// Reduce the array to unique attachment IDs.
$attachment_ids = array_unique( array_filter( array_values( $images ) ) );
if ( count( $attachment_ids ) > 1 ) {
/*
* Warm the object cache with post and meta information for all found
* images to avoid making individual database calls.
*/
_prime_post_caches( $attachment_ids, false, true );
}
// Iterate through the matches in order of occurrence as it is relevant for whether or not to lazy-load.
foreach ( $matches as $match ) {
// Filter an image match.
if ( isset( $images[ $match[0] ] ) ) {
$filtered_image = $match[0];
$attachment_id = $images[ $match[0] ];
// Add 'width' and 'height' attributes if applicable.
if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' width=' ) && ! str_contains( $filtered_image, ' height=' ) ) {
$filtered_image = wp_img_tag_add_width_and_height_attr( $filtered_image, $context, $attachment_id );
}
// Add 'srcset' and 'sizes' attributes if applicable.
if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' srcset=' ) ) {
$filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id );
}
// Add loading optimization attributes if applicable.
$filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context );
// Adds 'auto' to the sizes attribute if applicable.
$filtered_image = wp_img_tag_add_auto_sizes( $filtered_image );
/**
* Filters an img tag within the content for a given context.
*
* @since 6.0.0
*
* @param string $filtered_image Full img tag with attributes that will replace the source img tag.
* @param string $context Additional context, like the current filter name or the function name from where this was called.
* @param int $attachment_id The image attachment ID. May be 0 in case the image is not an attachment.
*/
$filtered_image = apply_filters( 'wp_content_img_tag', $filtered_image, $context, $attachment_id );
if ( $filtered_image !== $match[0] ) {
$content = str_replace( $match[0], $filtered_image, $content );
}
/*
* Unset image lookup to not run the same logic again unnecessarily if the same image tag is used more than
* once in the same blob of content.
*/
unset( $images[ $match[0] ] );
}
// Filter an iframe match.
if ( isset( $iframes[ $match[0] ] ) ) {
$filtered_iframe = $match[0];
// Add 'loading' attribute if applicable.
if ( $add_iframe_loading_attr && ! str_contains( $filtered_iframe, ' loading=' ) ) {
$filtered_iframe = wp_iframe_tag_add_loading_attr( $filtered_iframe, $context );
}
if ( $filtered_iframe !== $match[0] ) {
$content = str_replace( $match[0], $filtered_iframe, $content );
}
/*
* Unset iframe lookup to not run the same logic again unnecessarily if the same iframe tag is used more
* than once in the same blob of content.
*/
unset( $iframes[ $match[0] ] );
}
}
return $content;
}
/**
* Adds 'auto' to the sizes attribute to the image, if the image is lazy loaded and does not already include it.
*
* @since 6.7.0
*
* @param string $image The image tag markup being filtered.
* @return string The filtered image tag markup.
*/
function wp_img_tag_add_auto_sizes( string $image ): string {
$processor = new WP_HTML_Tag_Processor( $image );
// Bail if there is no IMG tag.
if ( ! $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) {
return $image;
}
// Bail early if the image is not lazy-loaded.
$value = $processor->get_attribute( 'loading' );
if ( ! is_string( $value ) || 'lazy' !== strtolower( trim( $value, " \t\f\r\n" ) ) ) {
return $image;
}
$sizes = $processor->get_attribute( 'sizes' );
// Bail early if the image is not responsive.
if ( ! is_string( $sizes ) ) {
return $image;
}
// Don't add 'auto' to the sizes attribute if it already exists.
if ( wp_sizes_attribute_includes_valid_auto( $sizes ) ) {
return $image;
}
$processor->set_attribute( 'sizes', "auto, $sizes" );
return $processor->get_updated_html();
}
/**
* Checks whether the given 'sizes' attribute includes the 'auto' keyword as the first item in the list.
*
* Per the HTML spec, if present it must be the first entry.
*
* @since 6.7.0
*
* @param string $sizes_attr The 'sizes' attribute value.
* @return bool True if the 'auto' keyword is present, false otherwise.
*/
function wp_sizes_attribute_includes_valid_auto( string $sizes_attr ): bool {
list( $first_size ) = explode( ',', $sizes_attr, 2 );
return 'auto' === strtolower( trim( $first_size, " \t\f\r\n" ) );
}
/**
* Adds optimization attributes to an `img` HTML tag.
*
* @since 6.3.0
*
* @param string $image The HTML `img` tag where the attribute should be added.
* @param string $context Additional context to pass to the filters.
* @return string Converted `img` tag with optimization attributes added.
*/
function wp_img_tag_add_loading_optimization_attrs( $image, $context ) {
$src = preg_match( '/ src=["\']?([^"\']*)/i', $image, $matche_src ) ? $matche_src[1] : null;
$width = preg_match( '/ width=["\']([0-9]+)["\']/', $image, $match_width ) ? (int) $match_width[1] : null;
$height = preg_match( '/ height=["\']([0-9]+)["\']/', $image, $match_height ) ? (int) $match_height[1] : null;
$loading_val = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading ) ? $match_loading[1] : null;
$fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority ) ? $match_fetchpriority[1] : null;
$decoding_val = preg_match( '/ decoding=["\']([A-Za-z]+)["\']/', $image, $match_decoding ) ? $match_decoding[1] : null;
/*
* Get loading optimization attributes to use.
* This must occur before the conditional check below so that even images
* that are ineligible for being lazy-loaded are considered.
*/
$optimization_attrs = wp_get_loading_optimization_attributes(
'img',
array(
'src' => $src,
'width' => $width,
'height' => $height,
'loading' => $loading_val,
'fetchpriority' => $fetchpriority_val,
'decoding' => $decoding_val,
),
$context
);
// Images should have source for the loading optimization attributes to be added.
if ( ! str_contains( $image, ' src="' ) ) {
return $image;
}
if ( empty( $decoding_val ) ) {
/**
* Filters the `decoding` attribute value to add to an image. Default `async`.
*
* Returning a falsey value will omit the attribute.
*
* @since 6.1.0
*
* @param string|false|null $value The `decoding` attribute value. Returning a falsey value
* will result in the attribute being omitted for the image.
* Otherwise, it may be: 'async', 'sync', or 'auto'. Defaults to false.
* @param string $image The HTML `img` tag to be filtered.
* @param string $context Additional context about how the function was called
* or where the img tag is.
*/
$filtered_decoding_attr = apply_filters(
'wp_img_tag_add_decoding_attr',
isset( $optimization_attrs['decoding'] ) ? $optimization_attrs['decoding'] : false,
$image,
$context
);
// Validate the values after filtering.
if ( isset( $optimization_attrs['decoding'] ) && ! $filtered_decoding_attr ) {
// Unset `decoding` attribute if `$filtered_decoding_attr` is set to `false`.
unset( $optimization_attrs['decoding'] );
} elseif ( in_array( $filtered_decoding_attr, array( 'async', 'sync', 'auto' ), true ) ) {
$optimization_attrs['decoding'] = $filtered_decoding_attr;
}
if ( ! empty( $optimization_attrs['decoding'] ) ) {
$image = str_replace( ' str_contains( $iframe, ' width="' ) ? 100 : null,
'height' => str_contains( $iframe, ' height="' ) ? 100 : null,
// This function is never called when a 'loading' attribute is already present.
'loading' => null,
),
$context
);
// Iframes should have source and dimension attributes for the `loading` attribute to be added.
if ( ! str_contains( $iframe, ' src="' ) || ! str_contains( $iframe, ' width="' ) || ! str_contains( $iframe, ' height="' ) ) {
return $iframe;
}
$value = isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false;
/**
* Filters the `loading` attribute value to add to an iframe. Default `lazy`.
*
* Returning `false` or an empty string will not add the attribute.
* Returning `true` will add the default value.
*
* @since 5.7.0
*
* @param string|bool $value The `loading` attribute value. Returning a falsey value will result in
* the attribute being omitted for the iframe.
* @param string $iframe The HTML `iframe` tag to be filtered.
* @param string $context Additional context about how the function was called or where the iframe tag is.
*/
$value = apply_filters( 'wp_iframe_tag_add_loading_attr', $value, $iframe, $context );
if ( $value ) {
if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
$value = 'lazy';
}
return str_replace( '