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.