Make WordPress Core


Ignore:
Timestamp:
10/06/2015 04:58:21 AM (9 years ago)
Author:
wonderboymusic
Message:

Merge the Responsive Images feature plugin into core, initial commit. See: https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images/

New functions in media.php:

  • wp_get_attachment_image_srcset_array() - Returns an array of image candidate string data used to build a srcset value for an attachment given an $attachement_id and $size.
  • wp_get_attachment_image_srcset() - Returns the srcset value for an attachment given an $attachement_id and $size.
  • wp_get_attachment_image_sizes() - Returns the sizes value for an attachment given an $attachement_id and $size and optional arguments used to alter its output.
  • wp_make_content_images_responsive() - A display filter for adding srcset and sizes to images embedded in content.
  • wp_img_add_srcset_and_sizes() - A utility function used by wp_make_content_images_responsive() to add srcset and sizes to a single <img> element.

Modifies existing core functions:

  • Modify wp_get_attachment_image() so the HTML returned for an image includes srcset and sizes.
  • Modify get_media_embedded_in_content() (sup, 3.6 leftover) by adding <img> to the list of accepted tags that can be matched in content. This is used in wp_make_content_images_responsive() to find all of the images embedded in content before passing them off to wp_img_add_srcset_and_sizes().

Tests:

  • Add a new factory method to WP_UnitTest_Factory_For_Attachment named create_upload_object()
  • Adds unit tests
  • Updates unit tests

Props joemcgill, tevko, jaspermdegroot, mdmcginn, barryceelen, peterwilsoncc, fsylum, wonderboymusic, chriscoyier, benjaminpick, jrfnl, #12kingkool68, janhenckens, ryanmarkel, side777, ryelle, wturrell, micahmills, mattbagwell, coliff, DrewAPicture.
See #33641.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/media.php

    r34851 r34855  
    778778        $attr = wp_parse_args($attr, $default_attr);
    779779
     780        // Generate srcset and sizes if not already present.
     781        if ( empty( $attr['srcset'] ) && $srcset = wp_get_attachment_image_srcset( $attachment_id, $size ) ) {
     782            $attr['srcset'] = $srcset;
     783            $sizes_args = array(
     784                'height' => $height,
     785                'width'  => $width,
     786            );
     787            $attr['sizes'] = wp_get_attachment_image_sizes( $attachment_id, $size, $sizes_args );
     788        }
     789
    780790        /**
    781791         * Filter the list of attachment image attributes.
     
    813823    $image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
    814824    return isset( $image['0'] ) ? $image['0'] : false;
     825}
     826
     827/**
     828 * Retrieves an array of URLs and pixel widths representing sizes of an image.
     829 *
     830 * The purpose is to populate a source set when creating responsive image markup.
     831 *
     832 * @since 4.4.0
     833 *
     834 * @param  int    $attachment_id Image attachment ID.
     835 * @param  string $size          Optional. Name of image size. Default 'medium'.
     836 * @return array|bool $images {
     837 *     Array image candidate values containing a URL, descriptor type, and
     838 *     descriptor value. False if none exist.
     839 *
     840 *     @type array $values {
     841 *        @type string $url        An image URL.
     842 *        @type string $descriptor A width or density descriptor used in a srcset.
     843 *        @type int    $value      The descriptor value representing a width or
     844 *                                 or pixel density.
     845 *     }
     846 * }
     847 *
     848 */
     849function wp_get_attachment_image_srcset_array( $attachment_id, $size = 'medium' ) {
     850    // Get the intermediate size.
     851    $image = image_get_intermediate_size( $attachment_id, $size );
     852
     853    // Get the post meta.
     854    $img_meta = wp_get_attachment_metadata( $attachment_id );
     855    if ( ! is_array( $img_meta ) ) {
     856        return false;
     857    }
     858
     859    // Extract the height and width from the intermediate or the full size.
     860    $img_width  = ( $image ) ? $image['width']  : $img_meta['width'];
     861    $img_height = ( $image ) ? $image['height'] : $img_meta['height'];
     862
     863    // Bail early if the width isn't greater that zero.
     864    if ( ! $img_width > 0 ) {
     865        return false;
     866    }
     867
     868    // Use the URL from the intermediate size or build the url from the metadata.
     869    if ( ! empty( $image['url'] ) ) {
     870        $img_url = $image['url'];
     871    } else {
     872        $uploads_dir = wp_upload_dir();
     873        $img_file = ( $image ) ? path_join( dirname( $img_meta['file'] ) , $image['file'] ) : $img_meta['file'];
     874        $img_url = $uploads_dir['baseurl'] . '/' . $img_file;
     875    }
     876
     877    $img_sizes = $img_meta['sizes'];
     878
     879    // Add full size to the img_sizes array.
     880    $img_sizes['full'] = array(
     881        'width'  => $img_meta['width'],
     882        'height' => $img_meta['height'],
     883        'file'   => wp_basename( $img_meta['file'] )
     884    );
     885
     886    // Calculate the image aspect ratio.
     887    $img_ratio = $img_height / $img_width;
     888
     889    /*
     890     * Images that have been edited in WordPress after being uploaded will
     891     * contain a unique hash. Look for that hash and use it later to filter
     892     * out images that are leftovers from previous versions.
     893     */
     894    $img_edited = preg_match( '/-e[0-9]{13}/', $img_url, $img_edit_hash );
     895
     896    /*
     897     * Set up arrays to hold url candidates and matched image sources so
     898     * we can avoid duplicates without looping through the full sources array
     899     */
     900    $candidates = $sources = array();
     901
     902    /*
     903     * Loop through available images and only use images that are resized
     904     * versions of the same rendition.
     905     */
     906    foreach ( $img_sizes as $img ) {
     907
     908        // Filter out images that are leftovers from previous renditions.
     909        if ( $img_edited && ! strpos( $img['file'], $img_edit_hash[0] ) ) {
     910            continue;
     911        }
     912
     913        $candidate_url = path_join( dirname( $img_url ), $img['file'] );
     914
     915        // Calculate the new image ratio.
     916        $img_ratio_compare = $img['height'] / $img['width'];
     917
     918        // If the new ratio differs by less than 0.01, use it.
     919        if ( abs( $img_ratio - $img_ratio_compare ) < 0.01 && ! in_array( $candidate_url, $candidates ) ) {
     920            // Add the URL to our list of candidates.
     921            $candidates[] = $candidate_url;
     922
     923            // Add the url, descriptor, and value to the sources array to be returned.
     924            $sources[] = array(
     925                'url'        => $candidate_url,
     926                'descriptor' => 'w',
     927                'value'      => $img['width'],
     928            );
     929        }
     930    }
     931
     932    /**
     933     * Filter the output of wp_get_attachment_image_srcset_array().
     934     *
     935     * @since 4.4.0
     936     *
     937     * @param array        $sources       An array of image urls and widths.
     938     * @param int          $attachment_id Attachment ID for image.
     939     * @param array|string $size          Size of image, either array or string.
     940     */
     941    return apply_filters( 'wp_get_attachment_image_srcset_array', $sources, $attachment_id, $size );
     942}
     943
     944/**
     945 * Retrieves the value for an image attachment's 'srcset' attribute.
     946 *
     947 * @since 4.4.0
     948 *
     949 * @param int    $attachment_id Image attachment ID.
     950 * @param string $size          Optional. Name of image size. Default 'medium'.
     951 * @return string|bool A 'srcset' value string or false.
     952 */
     953function wp_get_attachment_image_srcset( $attachment_id, $size = 'medium' ) {
     954    $srcset_array = wp_get_attachment_image_srcset_array( $attachment_id, $size );
     955
     956    // Only return a srcset value if there is more than one source.
     957    if ( count( $srcset_array ) <= 1 ) {
     958        return false;
     959    }
     960
     961    $srcset = '';
     962    foreach ( $srcset_array as $source ) {
     963        $srcset .= $source['url'] . ' ' . $source['value'] . $source['descriptor'] . ', ';
     964    }
     965
     966    /**
     967     * Filter the output of wp_get_attachment_image_srcset().
     968     *
     969     * @since 4.4.0
     970     *
     971     * @param string       $srcset        A source set formated for a `srcset` attribute.
     972     * @param int          $attachment_id Attachment ID for image.
     973     * @param array|string $size          Size of image, either array or string.
     974     */
     975    return apply_filters( 'wp_get_attachment_image_srcset', rtrim( $srcset, ', ' ), $attachment_id, $size );
     976}
     977
     978/**
     979 * Retrieves a source size attribute for an image from an array of values.
     980 *
     981 * @since 4.4.0
     982 *
     983 * @param int    $attachment_id Image attachment ID.
     984 * @param string $size          Optional. Name of image size. Default value: 'medium'.
     985 * @param array  $args {
     986 *     Optional. Arguments to retrieve attachments.
     987 *
     988 *     @type array|string $sizes An array or string containing of size information.
     989 *     @type int          $width A single width value used in the default `sizes` string.
     990 * }
     991 * @return string|bool A valid source size value for use in a 'sizes' attribute or false.
     992 */
     993function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $args = null ) {
     994
     995    // Try to get the image width from $args before calling image_downsize().
     996    if ( is_array( $args ) && ! empty( $args['width'] ) ) {
     997        $img_width = (int) $args['width'];
     998    } elseif ( $img = image_get_intermediate_size( $attachment_id, $size ) ) {
     999        list( $img_width, $img_height ) = image_constrain_size_for_editor( $img['width'], $img['height'], $size );
     1000    }
     1001
     1002    // Bail early if $image_width isn't set.
     1003    if ( ! $img_width ) {
     1004        return false;
     1005    }
     1006
     1007    // Set the image width in pixels.
     1008    $img_width = $img_width . 'px';
     1009
     1010    // Set up our default values.
     1011    $defaults = array(
     1012        'sizes' => array(
     1013            array(
     1014                'size_value' => '100vw',
     1015                'mq_value'   => $img_width,
     1016                'mq_name'    => 'max-width'
     1017            ),
     1018            array(
     1019                'size_value' => $img_width
     1020            ),
     1021        )
     1022    );
     1023
     1024    $args = wp_parse_args( $args, $defaults );
     1025
     1026    /**
     1027     * Filter arguments used to create 'sizes' attribute.
     1028     *
     1029     * @since 4.4.0
     1030     *
     1031     * @param array   $args          An array of arguments used to create a 'sizes' attribute.
     1032     * @param int     $attachment_id Post ID of the original image.
     1033     * @param string  $size          Name of the image size being used.
     1034     */
     1035    $args = apply_filters( 'wp_image_sizes_args', $args, $attachment_id, $size );
     1036
     1037    // If sizes is passed as a string, just use the string.
     1038    if ( is_string( $args['sizes'] ) ) {
     1039        $size_list = $args['sizes'];
     1040
     1041    // Otherwise, breakdown the array and build a sizes string.
     1042    } elseif ( is_array( $args['sizes'] ) ) {
     1043
     1044        $size_list = '';
     1045
     1046        foreach ( $args['sizes'] as $size ) {
     1047
     1048            // Use 100vw as the size value unless something else is specified.
     1049            $size_value = ( $size['size_value'] ) ? $size['size_value'] : '100vw';
     1050
     1051            // If a media length is specified, build the media query.
     1052            if ( ! empty( $size['mq_value'] ) ) {
     1053
     1054                $media_length = $size['mq_value'];
     1055
     1056                // Use max-width as the media condition unless min-width is specified.
     1057                $media_condition = ( ! empty( $size['mq_name'] ) ) ? $size['mq_name'] : 'max-width';
     1058
     1059                // If a media_length was set, create the media query.
     1060                $media_query = '(' . $media_condition . ": " . $media_length . ') ';
     1061
     1062            } else {
     1063                // If no media length was set, $media_query is blank.
     1064                $media_query = '';
     1065            }
     1066
     1067            // Add to the source size list string.
     1068            $size_list .= $media_query . $size_value . ', ';
     1069        }
     1070
     1071        // Remove the trailing comma and space from the end of the string.
     1072        $size_list = substr( $size_list, 0, -2 );
     1073    }
     1074
     1075    // Return the sizes value as $size_list or false.
     1076    return ( $size_list ) ? $size_list : false;
     1077}
     1078
     1079/**
     1080 * Filters 'img' elements in post content to add 'srcset' and 'sizes' attributes.
     1081 *
     1082 * @since 4.4.0
     1083 *
     1084 * @see wp_img_add_srcset_and_sizes()
     1085 *
     1086 * @param string $content The raw post content to be filtered.
     1087 * @return string Converted content with 'srcset' and 'sizes' attributes added to images.
     1088 */
     1089function wp_make_content_images_responsive( $content ) {
     1090    $images = get_media_embedded_in_content( $content, 'img' );
     1091
     1092    $attachment_ids = array();
     1093
     1094    foreach( $images as $image ) {
     1095        if ( preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) ) {
     1096            $attachment_id = (int) $class_id[1];
     1097            if ( $attachment_id ) {
     1098                $attachment_ids[] = $attachment_id;
     1099            }
     1100        }
     1101    }
     1102
     1103    if ( 0 < count( $attachment_ids ) ) {
     1104        /*
     1105         * Warm object caches for use with wp_get_attachment_metadata.
     1106         *
     1107         * To avoid making a database call for each image, a single query
     1108         * warms the object cache with the meta information for all images.
     1109         */
     1110        _prime_post_caches( $attachment_ids, false, true );
     1111    }
     1112
     1113    foreach( $images as $image ) {
     1114        $content = str_replace( $image, wp_img_add_srcset_and_sizes( $image ), $content );
     1115    }
     1116
     1117    return $content;
     1118}
     1119
     1120/**
     1121 * Adds 'srcset' and 'sizes' attributes to an existing 'img' element.
     1122 *
     1123 * @since 4.4.0
     1124 *
     1125 * @see wp_get_attachment_image_srcset()
     1126 * @see wp_get_attachment_image_sizes()
     1127 *
     1128 * @param string $image An HTML 'img' element to be filtered.
     1129 * @return string Converted 'img' element with `srcset` and `sizes` attributes added.
     1130 */
     1131function wp_img_add_srcset_and_sizes( $image ) {
     1132    // Return early if a 'srcset' attribute already exists.
     1133    if ( false !== strpos( $image, ' srcset="' ) ) {
     1134        return $image;
     1135    }
     1136
     1137    // Parse id, size, width, and height from the `img` element.
     1138    $id     = preg_match( '/wp-image-([0-9]+)/i', $image, $match_id     ) ? (int) $match_id[1]     : false;
     1139    $size   = preg_match( '/size-([^\s|"]+)/i',   $image, $match_size   ) ? $match_size[1]         : false;
     1140    $width  = preg_match( '/ width="([0-9]+)"/',  $image, $match_width  ) ? (int) $match_width[1]  : false;
     1141    $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : false;
     1142
     1143    if ( $id && false === $size ) {
     1144        $size = array( $width, $height );
     1145    }
     1146
     1147    /*
     1148     * If attempts to parse the size value failed, attempt to use the image
     1149     * metadata to match the 'src' against the available sizes for an attachment.
     1150     */
     1151    if ( ! $size && ! empty( $id ) && is_array( $meta = wp_get_attachment_metadata( $id ) ) ) {
     1152        // Parse the image src value from the img element.
     1153        $src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : false;
     1154
     1155        // Return early if the src value is empty.
     1156        if ( ! $src ) {
     1157            return $image;
     1158        }
     1159
     1160        /*
     1161         * First, see if the file is the full size image. If not, loop through
     1162         * the intermediate sizes until we find a file that matches.
     1163         */
     1164        $image_filename = wp_basename( $src );
     1165
     1166        if ( $image_filename === basename( $meta['file'] ) ) {
     1167            $size = 'full';
     1168        } else {
     1169            foreach( $meta['sizes'] as $image_size => $image_size_data ) {
     1170                if ( $image_filename === $image_size_data['file'] ) {
     1171                    $size = $image_size;
     1172                    break;
     1173                }
     1174            }
     1175        }
     1176
     1177    }
     1178
     1179    // If ID and size, try for 'srcset' and 'sizes' and update the markup.
     1180    if ( $id && $size && $srcset = wp_get_attachment_image_srcset( $id, $size ) ) {
     1181
     1182        /*
     1183         * Pass the 'height' and 'width' to 'wp_get_attachment_image_sizes()' to avoid
     1184         * recalculating the image size.
     1185         */
     1186        $args = array(
     1187            'height' => $height,
     1188            'width'  => $width,
     1189        );
     1190
     1191        $sizes = wp_get_attachment_image_sizes( $id, $size, $args );
     1192
     1193        // Format the srcset and sizes string and escape attributes.
     1194        $srcset_and_sizes = sprintf( ' srcset="%s" sizes="%s"', esc_attr( $srcset ), esc_attr( $sizes) );
     1195
     1196        // Add srcset and sizes attributes to the image markup.
     1197        $image = preg_replace( '/<img ([^>]+)[\s?][\/?]>/', '<img $1' . $srcset_and_sizes . ' />', $image );
     1198    }
     1199
     1200    return $image;
    8151201}
    8161202
     
    29873373     *
    29883374     * @since 4.2.0
     3375     * @since 4.4.0 Added 'img' to the allowed types.
    29893376     *
    29903377     * @param array $allowed_media_types An array of allowed media types. Default media types are
    2991      *                                   'audio', 'video', 'object', 'embed', and 'iframe'.
    2992      */
    2993     $allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe' ) );
     3378     *                                   'audio', 'video', 'object', 'embed', 'iframe', and 'img'.
     3379     */
     3380    $allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe', 'img' ) );
    29943381
    29953382    if ( ! empty( $types ) ) {
Note: See TracChangeset for help on using the changeset viewer.