diff --git a/wp-includes/media.php b/wp-includes/media.php
index 30df3226ce..fc69bdcdc8 100644
--- a/wp-includes/media.php
+++ b/wp-includes/media.php
@@ -813,13 +813,14 @@ function wp_get_attachment_image($attachment_id, $size = 'thumbnail', $icon = fa
// Generate srcset and sizes if not already present.
if ( empty( $attr['srcset'] ) ) {
- $srcset = wp_get_attachment_image_srcset( $attachment_id, $size );
- $sizes = wp_get_attachment_image_sizes( $attachment_id, $size, $width );
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
+ $size_array = array( absint( $width ), absint( $height ) );
+ $sources = wp_calculate_image_srcset( $src, $size_array, $image_meta, $attachment_id );
- if ( $srcset && $sizes ) {
- $attr['srcset'] = $srcset;
+ if ( count( $sources ) > 1 ) {
+ $attr['srcset'] = wp_image_srcset_attr( $sources, $size_array, $image_meta, $attachment_id );
- if ( empty( $attr['sizes'] ) ) {
+ if ( empty( $attr['sizes'] ) && ( $sizes = wp_get_attachment_image_sizes( $size_array, $image_meta, $attachment_id ) ) ) {
$attr['sizes'] = $sizes;
}
}
@@ -864,15 +865,118 @@ function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon
}
/**
- * Retrieves an array of URLs and pixel widths representing sizes of an image.
- *
- * The purpose is to populate a source set when creating responsive image markup.
+ * Private, do not use
+ */
+function _wp_upload_dir_baseurl() {
+ static $baseurl = null;
+
+ if ( ! $baseurl ) {
+ $uploads_dir = wp_upload_dir();
+ $baseurl = $uploads_dir['baseurl'];
+ }
+
+ return $baseurl;
+}
+
+/**
+ * Private, do not use
+ */
+function _wp_get_image_size_from_meta( $size, $image_meta ) {
+ if ( $size === 'full' ) {
+ return array(
+ absint( $image_meta['width'] ),
+ absint( $image_meta['height'] ),
+ );
+ } elseif ( ! empty( $image_meta['sizes'][$size] ) ) {
+ return array(
+ absint( $image_meta['sizes'][$size]['width'] ),
+ absint( $image_meta['sizes'][$size]['height'] ),
+ );
+ }
+
+ return false;
+}
+
+/**
+ * Retrieves the value for an image attachment's 'srcset' attribute.
*
* @since 4.4.0
*
- * @param int $attachment_id Image attachment ID.
+
+ * @param int $attachment_id Optional. Image attachment ID.
* @param array|string $size Image size. Accepts any valid image size, or an array of width and height
* values in pixels (in that order). Default 'medium'.
+ * @param array $image_meta Optional. The image meta data.
+ * @return string|bool A 'srcset' value string or false.
+ */
+function wp_get_attachment_image_srcset( $attachment_id, $size = 'medium', $image_meta = null ) {
+ if ( ! $image = wp_get_attachment_image_src( $attachment_id, $size ) ) {
+ return false;
+ }
+
+ if ( ! is_array( $image_meta ) ) {
+ $image_meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
+ }
+
+ $image_url = $image[0];
+ $size_array = array(
+ absint( $image[1] ),
+ absint( $image[2] )
+ );
+
+ // Calculate the sources for the srcset.
+ $sources = wp_calculate_image_srcset( $image_url, $size_array, $image_meta, $attachment_id );
+
+ // Only return a srcset value if there is more than one source.
+ if ( count( $sources ) < 2 ) {
+ return false;
+ }
+
+ return wp_image_srcset_attr( $sources, $size_array, $image_meta, $attachment_id );
+}
+
+
+/**
+ * A helper function to concatenate and filter the srcset attribute value.
+ *
+ * @since 4.4.0
+ *
+ * @param array $sources The array containing image sizes data as returned by wp_calculate_image_srcset().
+ * @param array $size_array Array of width and height values in pixels (in that order).
+ * @param array $image_meta The image meta data.
+ * @param int $attachment_id The image attachment post id to pass to the filter.
+ * @return string The srcset attribute value.
+ */
+function wp_image_srcset_attr( $sources, $size_array, $image_meta, $attachment_id ) {
+ $srcset = '';
+
+ foreach ( $sources as $source ) {
+ $srcset .= $source['url'] . ' ' . $source['value'] . $source['descriptor'] . ', ';
+ }
+
+ /**
+ * Filter the output of wp_get_attachment_image_srcset().
+ *
+ * @since 4.4.0
+ *
+ * @param string $srcset A source set formatted for a `srcset` attribute.
+ * @param int $attachment_id Image attachment ID.
+ * @param array|string $size Image size. Accepts any valid image size, or an array of width and height
+ * values in pixels (in that order). Default 'medium'.
+ * @param array $image_meta The image meta data.
+ */
+ return apply_filters( 'wp_get_attachment_image_srcset', rtrim( $srcset, ', ' ), $attachment_id, $size_array, $image_meta );
+}
+
+/**
+ * A helper function to caclulate the image sources to include in a srcset attribute.
+ *
+ * @since 4.4.0
+ *
+ * @param string $image_name The file name, path, URL or partial path or URL of the image being matched.
+ * @param array $size_array Array of width and height values in pixels (in that order).
+ * @param array $image_meta The image meta data.
+ * @param int $attachment_id Optional. The image attachment post id to pass to the filter.
* @return array|bool $sources {
* Array image candidate values containing a URL, descriptor type, and
* descriptor value. False if none exist.
@@ -886,103 +990,91 @@ function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon
* }
*
*/
-function wp_get_attachment_image_srcset_array( $attachment_id, $size = 'medium' ) {
- // Get the intermediate size.
- $image = image_get_intermediate_size( $attachment_id, $size );
-
- // Get the post meta.
- $img_meta = wp_get_attachment_metadata( $attachment_id );
- if ( ! is_array( $img_meta ) ) {
+function wp_calculate_image_srcset( $image_name, $size_array, $image_meta, $attachment_id = 0 ) {
+ if ( empty( $image_meta['sizes'] ) ) {
return false;
}
- // Extract the height and width from the intermediate or the full size.
- $img_width = ( $image ) ? $image['width'] : $img_meta['width'];
- $img_height = ( $image ) ? $image['height'] : $img_meta['height'];
+ $image_sizes = $image_meta['sizes'];
- // Bail early if the width isn't greater than zero.
- if ( ! $img_width > 0 ) {
+ // Get the height and width for 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;
}
- // Use the URL from the intermediate size or build the url from the metadata.
- if ( ! empty( $image['url'] ) ) {
- $img_url = $image['url'];
- } else {
- $uploads_dir = wp_upload_dir();
- $img_file = ( $image ) ? path_join( dirname( $img_meta['file'] ) , $image['file'] ) : $img_meta['file'];
- $img_url = $uploads_dir['baseurl'] . '/' . $img_file;
- }
-
- $img_sizes = $img_meta['sizes'];
-
// Add full size to the img_sizes array.
- $img_sizes['full'] = array(
- 'width' => $img_meta['width'],
- 'height' => $img_meta['height'],
- 'file' => wp_basename( $img_meta['file'] )
+ $image_sizes['full'] = array(
+ 'width' => $image_meta['width'],
+ 'height' => $image_meta['height'],
+ 'file' => wp_basename( $image_meta['file'] ),
);
+ $image_baseurl = _wp_upload_dir_baseurl();
+ $dirname = dirname( $image_meta['file'] );
+
+ if ( $dirname !== '.' ) {
+ $image_baseurl = path_join( $image_baseurl, $dirname );
+ }
+
// Calculate the image aspect ratio.
- $img_ratio = $img_height / $img_width;
+ $image_ratio = $image_height / $image_width;
/*
* 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.
*/
- $img_edited = preg_match( '/-e[0-9]{13}/', $img_url, $img_edit_hash );
+ $image_edited = preg_match( '/-e[0-9]{13}/', $image_name, $image_edit_hash );
/**
- * Filter the maximum width included in a srcset attribute.
+ * Filter the maximum width included in a 'srcset' attribute.
*
* @since 4.4.0
*
- * @param array|string $size Size of image, either array or string.
+ * @param int $max_width The maximum width to include in the 'srcset'. Default '1600'.
+ * @param array|string $size_array Array of width and height values in pixels (in that order).
*/
- $max_srcset_width = apply_filters( 'max_srcset_image_width', 1600, $size );
+ $max_srcset_width = apply_filters( 'max_srcset_image_width', 1600, $size_array );
+
+ // Array to hold URL candidates.
+ $sources = array();
/*
- * Set up arrays to hold url candidates and matched image sources so
- * we can avoid duplicates without looping through the full sources array
+ * Loop through available images. Only use images that are resized
+ * versions of the same edit.
*/
- $candidates = $sources = array();
+ foreach ( $image_sizes as $image ) {
- /*
- * Loop through available images and only use images that are resized
- * versions of the same rendition.
- */
- foreach ( $img_sizes as $img ) {
-
- // Filter out images that are leftovers from previous renditions.
- if ( $img_edited && ! strpos( $img['file'], $img_edit_hash[0] ) ) {
+ // Filter out images that are from previous edits.
+ if ( $image_edited && ! strpos( $image['file'], $image_edit_hash[0] ) ) {
continue;
}
// Filter out images that are wider than $max_srcset_width.
- if ( $max_srcset_width && $img['width'] > $max_srcset_width ) {
+ if ( $max_srcset_width && $image['width'] > $max_srcset_width ) {
continue;
}
- $candidate_url = path_join( dirname( $img_url ), $img['file'] );
+ $candidate_url = $image['file'];
// Calculate the new image ratio.
- if ( $img['width'] ) {
- $img_ratio_compare = $img['height'] / $img['width'];
+ if ( $image['width'] ) {
+ $image_ratio_compare = $image['height'] / $image['width'];
} else {
- $img_ratio_compare = 0;
+ $image_ratio_compare = 0;
}
// If the new ratio differs by less than 0.01, use it.
- if ( abs( $img_ratio - $img_ratio_compare ) < 0.01 && ! in_array( $candidate_url, $candidates ) ) {
- // Add the URL to our list of candidates.
- $candidates[] = $candidate_url;
-
- // Add the url, descriptor, and value to the sources array to be returned.
- $sources[] = array(
- 'url' => $candidate_url,
+ if ( abs( $image_ratio - $image_ratio_compare ) < 0.01 && ! array_key_exists( $candidate_url, $sources ) ) {
+ // Add the URL, descriptor, and value to the sources array to be returned.
+ $sources[ $image['width'] ] = array(
+ 'url' => path_join( $image_baseurl, $candidate_url ),
'descriptor' => 'w',
- 'value' => $img['width'],
+ 'value' => $image['width'],
);
}
}
@@ -992,81 +1084,55 @@ function wp_get_attachment_image_srcset_array( $attachment_id, $size = 'medium'
*
* @since 4.4.0
*
- * @param array $sources An array of image urls and widths.
+ * @param array $sources An array of image URLs and widths.
* @param int $attachment_id Attachment ID for image.
* @param array|string $size Image size. Accepts any valid image size, or an array of width and height
* values in pixels (in that order). Default 'medium'.
+ * @param array $image_meta The image meta data.
+
*/
- return apply_filters( 'wp_get_attachment_image_srcset_array', $sources, $attachment_id, $size );
+ return apply_filters( 'wp_get_attachment_image_srcset_array', array_values( $sources ), $attachment_id, $size_array, $image_meta );
}
/**
- * Retrieves the value for an image attachment's 'srcset' attribute.
+ * Create `sizes` attribute value for an image.
*
* @since 4.4.0
*
- * @param int $attachment_id Image attachment ID.
- * @param array|string $size Image size. Accepts any valid image size, or an array of width and height
- * values in pixels (in that order). Default 'medium'.
- * @return string|bool A 'srcset' value string or false.
- */
-function wp_get_attachment_image_srcset( $attachment_id, $size = 'medium' ) {
- $srcset_array = wp_get_attachment_image_srcset_array( $attachment_id, $size );
-
- // Only return a srcset value if there is more than one source.
- if ( count( $srcset_array ) <= 1 ) {
- return false;
- }
-
- $srcset = '';
- foreach ( $srcset_array as $source ) {
- $srcset .= $source['url'] . ' ' . $source['value'] . $source['descriptor'] . ', ';
- }
-
- /**
- * Filter the output of wp_get_attachment_image_srcset().
- *
- * @since 4.4.0
- *
- * @param string $srcset A source set formated for a `srcset` attribute.
- * @param int $attachment_id Attachment ID for image.
- * @param array|string $size Image size. Accepts any valid image size, or an array of width and height
- * values in pixels (in that order). Default 'medium'.
- */
- return apply_filters( 'wp_get_attachment_image_srcset', rtrim( $srcset, ', ' ), $attachment_id, $size );
-}
-
-/**
- * Retrieves a source size attribute for an image from an array of values.
+ * @param array|string $size Image size. Accepts any valid image size name (thumbnail, medium, etc.),
+ * or an array of width and height values in pixels (in that order).
+ * @param array $image_meta Optional. The image meta data.
+ * @param int $attachment_id Optional. Image attachment ID. Either $image_meta or $attachment_id is needed
+ * when using image size name.
*
- * @since 4.4.0
- *
- * @param int $attachment_id Image attachment ID.
- * @param array|string $size Image size. Accepts any valid image size, or an array of width and height
- * values in pixels (in that order). Default 'medium'.
- * @param int $width Optional. Display width of the image.
* @return string|bool A valid source size value for use in a 'sizes' attribute or false.
*/
-function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $width = null ) {
- // Try to get the image width from the $width parameter.
- if ( is_numeric( $width ) ) {
- $img_width = (int) $width;
- // Next, see if a width value was passed in the $size parameter.
+function wp_get_attachment_image_sizes( $size, $image_meta = null, $attachment_id = 0 ) {
+ $width = 0;
+
+ if ( is_numeric( $size ) ) {
+ $width = absint( $size );
} elseif ( is_array( $size ) ) {
- $img_width = $size[0];
- // Finally, use the $size name to return the width of the image.
- } else {
- $image = image_get_intermediate_size( $attachment_id, $size );
- $img_width = $image ? $image['width'] : false;
+ $width = absint( $size[0] );
+ } elseif ( is_string( $size ) ) {
+ if ( ! $image_meta && $attachment_id ) {
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
+ }
+
+ if ( $image_meta ) {
+ $width = _wp_get_image_size_from_meta( $size, $image_meta );
+ if ( $width ) {
+ $width = $width[0];
+ }
+ }
}
- // Bail early if $img_width isn't set.
- if ( ! $img_width ) {
+ if ( ! $width ) {
return false;
}
// Setup the default sizes attribute.
- $sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $img_width );
+ $sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', (int) $width );
/**
* Filter the output of wp_get_attachment_image_sizes().
@@ -1074,12 +1140,12 @@ function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $width
* @since 4.4.0
*
* @param string $sizes A source size value for use in a 'sizes' attribute.
- * @param int $attachment_id Post ID of the original image.
* @param array|string $size Image size. Accepts any valid image size, or an array of width and height
* values in pixels (in that order). Default 'medium'.
- * @param int $width Display width of the image.
+ * @param array $image_meta The image meta data.
+ * @param int $attachment_id Post ID of the original image.
*/
- return apply_filters( 'wp_get_attachment_image_sizes', $sizes, $attachment_id, $size, $width );
+ return apply_filters( 'wp_get_attachment_image_sizes', $sizes, $size, $image_meta, $attachment_id );
}
/**
@@ -1087,7 +1153,7 @@ function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $width
*
* @since 4.4.0
*
- * @see wp_img_add_srcset_and_sizes()
+ * @see wp_image_add_srcset_and_sizes()
*
* @param string $content The raw post content to be filtered.
* @return string Converted content with 'srcset' and 'sizes' attributes added to images.
@@ -1095,29 +1161,33 @@ function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $width
function wp_make_content_images_responsive( $content ) {
$images = get_media_embedded_in_content( $content, 'img' );
- $attachment_ids = array();
+ $selected_images = $attachment_ids = array();
foreach( $images as $image ) {
- if ( preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) ) {
- $attachment_id = (int) $class_id[1];
- if ( $attachment_id ) {
- $attachment_ids[] = $attachment_id;
- }
+ if ( false === strpos( $image, ' srcset="' ) && preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) &&
+ ( $attachment_id = absint( $class_id[1] ) ) ) {
+
+ // If exactly the same image tag is used more than once, overwrite it.
+ // All identical tags will be replaced later with str_replace().
+ $selected_images[ $image ] = $attachment_id;
+ // Overwrite the ID when the same image is included more than once.
+ $attachment_ids[ $attachment_id ] = true;
}
}
- if ( 0 < count( $attachment_ids ) ) {
+ if ( count( $attachment_ids ) > 1 ) {
/*
- * Warm object caches for use with wp_get_attachment_metadata.
+ * Warm object cache for use with get_post_meta().
*
* To avoid making a database call for each image, a single query
* warms the object cache with the meta information for all images.
*/
- _prime_post_caches( $attachment_ids, false, true );
+ update_meta_cache( 'post', array_keys( $attachment_ids ) );
}
- foreach( $images as $image ) {
- $content = str_replace( $image, wp_img_add_srcset_and_sizes( $image ), $content );
+ foreach ( $selected_images as $image => $attachment_id ) {
+ $image_meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
+ $content = str_replace( $image, wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ), $content );
}
return $content;
@@ -1131,74 +1201,76 @@ function wp_make_content_images_responsive( $content ) {
* @see wp_get_attachment_image_srcset()
* @see wp_get_attachment_image_sizes()
*
- * @param string $image An HTML 'img' element to be filtered.
+ * @param string $image An HTML 'img' element to be filtered.
+ * @param array $image_meta The image meta data.
+ * @param int $attachment_id Image attachment ID.
* @return string Converted 'img' element with `srcset` and `sizes` attributes added.
*/
-function wp_img_add_srcset_and_sizes( $image ) {
- // Return early if a 'srcset' attribute already exists.
- if ( false !== strpos( $image, ' srcset="' ) ) {
+function wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ) {
+ // Ensure the image meta exists
+ if ( empty( $image_meta['sizes'] ) ) {
return $image;
}
- // Parse id, size, width, and height from the `img` element.
- $id = preg_match( '/wp-image-([0-9]+)/i', $image, $match_id ) ? (int) $match_id[1] : false;
- $size = preg_match( '/size-([^\s|"]+)/i', $image, $match_size ) ? $match_size[1] : false;
- $width = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : false;
+ $src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
+ list( $src ) = explode( '?', $src );
- if ( $id && ! $size ) {
- $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : false;
-
- if ( $width && $height ) {
- $size = array( $width, $height );
- }
+ // Return early if we coudn't get the image source.
+ if ( ! $src ) {
+ return $image;
}
- /*
- * If attempts to parse the size value failed, attempt to use the image
- * metadata to match the 'src' against the available sizes for an attachment.
- */
- if ( $id && ! $size ) {
- $meta = wp_get_attachment_metadata( $id );
+ // Bail early when an image has been inserted and later edited.
+ if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) &&
+ strpos( wp_basename( $src ), $img_edit_hash[0] ) === false ) {
- // Parse the image src value from the img element.
- $src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : false;
+ return $image;
+ }
- // Return early if the metadata does not exist or the src value is empty.
- if ( ! $meta || ! $src ) {
- 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 ) {
/*
- * First, see if the file is the full size image. If not, loop through
- * the intermediate sizes until we find a file that matches.
+ * If attempts to parse the size value failed, attempt to use the image
+ * metadata to match the image file name from 'src' against the available sizes for an attachment.
*/
$image_filename = wp_basename( $src );
- if ( $image_filename === basename( $meta['file'] ) ) {
- $size = 'full';
+ if ( $image_filename === wp_basename( $image_meta['file'] ) ) {
+ $width = (int) $image_meta['width'];
+ $height = (int) $image_meta['height'];
} else {
- foreach( $meta['sizes'] as $image_size => $image_size_data ) {
+ foreach( $image_meta['sizes'] as $image_size_data ) {
if ( $image_filename === $image_size_data['file'] ) {
- $size = $image_size;
+ $width = (int) $image_size_data['width'];
+ $height = (int) $image_size_data['height'];
break;
}
}
}
-
}
- // If ID and size exist, try for 'srcset' and 'sizes' and update the markup.
- if ( $id && $size ) {
- $srcset = wp_get_attachment_image_srcset( $id, $size );
- $sizes = wp_get_attachment_image_sizes( $id, $size, $width );
+ if ( ! $width || ! $height ) {
+ return $image;
+ }
- if ( $srcset && $sizes ) {
- // Format the srcset and sizes string and escape attributes.
- $srcset_and_sizes = sprintf( ' srcset="%s" sizes="%s"', esc_attr( $srcset ), esc_attr( $sizes) );
+ $size_array = array( $width, $height );
+ $sources = wp_calculate_image_srcset( $src, $size_array, $image_meta, $attachment_id );
- // Add srcset and sizes attributes to the image markup.
- $image = preg_replace( '/]+)[\s?][\/?]>/', '', $image );
- }
+ $srcset = $sizes = '';
+ // Only calculate srcset and sizes values if there is more than one source.
+ if ( count( $sources ) > 1 ) {
+ $srcset = wp_image_srcset_attr( $sources, $size_array, $image_meta, $attachment_id );
+ $sizes = wp_get_attachment_image_sizes( $size_array, $image_meta, $attachment_id );
+ }
+
+ if ( $srcset && $sizes ) {
+ // Format the srcset and sizes string and escape attributes.
+ $srcset_and_sizes = sprintf( ' srcset="%s" sizes="%s"', esc_attr( $srcset ), esc_attr( $sizes ) );
+
+ // Add srcset and sizes attributes to the image markup.
+ $image = preg_replace( '/]+?)[\/ ]*>/', '', $image );
}
return $image;
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 4bdcdd1248..cd88b8f650 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -4,7 +4,7 @@
*
* @global string $wp_version
*/
-$wp_version = '4.4-beta1-35411';
+$wp_version = '4.4-beta1-35412';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.