diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 30974c2cae..c9eb06b133 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -129,12 +129,13 @@ add_filter( 'the_title', 'wptexturize' ); add_filter( 'the_title', 'convert_chars' ); add_filter( 'the_title', 'trim' ); -add_filter( 'the_content', 'wptexturize' ); -add_filter( 'the_content', 'convert_smilies' ); -add_filter( 'the_content', 'convert_chars' ); -add_filter( 'the_content', 'wpautop' ); -add_filter( 'the_content', 'shortcode_unautop' ); -add_filter( 'the_content', 'prepend_attachment' ); +add_filter( 'the_content', 'wptexturize' ); +add_filter( 'the_content', 'convert_smilies' ); +add_filter( 'the_content', 'convert_chars' ); +add_filter( 'the_content', 'wpautop' ); +add_filter( 'the_content', 'shortcode_unautop' ); +add_filter( 'the_content', 'prepend_attachment' ); +add_filter( 'the_content', 'wp_make_content_images_responsive' ); add_filter( 'the_excerpt', 'wptexturize' ); add_filter( 'the_excerpt', 'convert_smilies' ); diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 53503e92a5..56c7f93ec7 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -777,6 +777,16 @@ function wp_get_attachment_image($attachment_id, $size = 'thumbnail', $icon = fa $attr = wp_parse_args($attr, $default_attr); + // Generate srcset and sizes if not already present. + if ( empty( $attr['srcset'] ) && $srcset = wp_get_attachment_image_srcset( $attachment_id, $size ) ) { + $attr['srcset'] = $srcset; + $sizes_args = array( + 'height' => $height, + 'width' => $width, + ); + $attr['sizes'] = wp_get_attachment_image_sizes( $attachment_id, $size, $sizes_args ); + } + /** * Filter the list of attachment image attributes. * @@ -814,6 +824,382 @@ function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon return isset( $image['0'] ) ? $image['0'] : false; } +/** + * 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. + * + * @since 4.4.0 + * + * @param int $attachment_id Image attachment ID. + * @param string $size Optional. Name of image size. Default 'medium'. + * @return array|bool $images { + * Array image candidate values containing a URL, descriptor type, and + * descriptor value. False if none exist. + * + * @type array $values { + * @type string $url An image URL. + * @type string $descriptor A width or density descriptor used in a srcset. + * @type int $value The descriptor value representing a width or + * or pixel density. + * } + * } + * + */ +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 ) ) { + 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']; + + // Bail early if the width isn't greater that zero. + if ( ! $img_width > 0 ) { + 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'] ) + ); + + // Calculate the image aspect ratio. + $img_ratio = $img_height / $img_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 ); + + /* + * Set up arrays to hold url candidates and matched image sources so + * we can avoid duplicates without looping through the full sources array + */ + $candidates = $sources = array(); + + /* + * 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] ) ) { + continue; + } + + $candidate_url = path_join( dirname( $img_url ), $img['file'] ); + + // Calculate the new image ratio. + $img_ratio_compare = $img['height'] / $img['width']; + + // 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, + 'descriptor' => 'w', + 'value' => $img['width'], + ); + } + } + + /** + * Filter the output of wp_get_attachment_image_srcset_array(). + * + * @since 4.4.0 + * + * @param array $sources An array of image urls and widths. + * @param int $attachment_id Attachment ID for image. + * @param array|string $size Size of image, either array or string. + */ + return apply_filters( 'wp_get_attachment_image_srcset_array', $sources, $attachment_id, $size ); +} + +/** + * Retrieves the value for an image attachment's 'srcset' attribute. + * + * @since 4.4.0 + * + * @param int $attachment_id Image attachment ID. + * @param string $size Optional. Name of image size. 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 Size of image, either array or string. + */ + 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. + * + * @since 4.4.0 + * + * @param int $attachment_id Image attachment ID. + * @param string $size Optional. Name of image size. Default value: 'medium'. + * @param array $args { + * Optional. Arguments to retrieve attachments. + * + * @type array|string $sizes An array or string containing of size information. + * @type int $width A single width value used in the default `sizes` string. + * } + * @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', $args = null ) { + + // Try to get the image width from $args before calling image_downsize(). + if ( is_array( $args ) && ! empty( $args['width'] ) ) { + $img_width = (int) $args['width']; + } elseif ( $img = image_get_intermediate_size( $attachment_id, $size ) ) { + list( $img_width, $img_height ) = image_constrain_size_for_editor( $img['width'], $img['height'], $size ); + } + + // Bail early if $image_width isn't set. + if ( ! $img_width ) { + return false; + } + + // Set the image width in pixels. + $img_width = $img_width . 'px'; + + // Set up our default values. + $defaults = array( + 'sizes' => array( + array( + 'size_value' => '100vw', + 'mq_value' => $img_width, + 'mq_name' => 'max-width' + ), + array( + 'size_value' => $img_width + ), + ) + ); + + $args = wp_parse_args( $args, $defaults ); + + /** + * Filter arguments used to create 'sizes' attribute. + * + * @since 4.4.0 + * + * @param array $args An array of arguments used to create a 'sizes' attribute. + * @param int $attachment_id Post ID of the original image. + * @param string $size Name of the image size being used. + */ + $args = apply_filters( 'wp_image_sizes_args', $args, $attachment_id, $size ); + + // If sizes is passed as a string, just use the string. + if ( is_string( $args['sizes'] ) ) { + $size_list = $args['sizes']; + + // Otherwise, breakdown the array and build a sizes string. + } elseif ( is_array( $args['sizes'] ) ) { + + $size_list = ''; + + foreach ( $args['sizes'] as $size ) { + + // Use 100vw as the size value unless something else is specified. + $size_value = ( $size['size_value'] ) ? $size['size_value'] : '100vw'; + + // If a media length is specified, build the media query. + if ( ! empty( $size['mq_value'] ) ) { + + $media_length = $size['mq_value']; + + // Use max-width as the media condition unless min-width is specified. + $media_condition = ( ! empty( $size['mq_name'] ) ) ? $size['mq_name'] : 'max-width'; + + // If a media_length was set, create the media query. + $media_query = '(' . $media_condition . ": " . $media_length . ') '; + + } else { + // If no media length was set, $media_query is blank. + $media_query = ''; + } + + // Add to the source size list string. + $size_list .= $media_query . $size_value . ', '; + } + + // Remove the trailing comma and space from the end of the string. + $size_list = substr( $size_list, 0, -2 ); + } + + // Return the sizes value as $size_list or false. + return ( $size_list ) ? $size_list : false; +} + +/** + * Filters 'img' elements in post content to add 'srcset' and 'sizes' attributes. + * + * @since 4.4.0 + * + * @see wp_img_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. + */ +function wp_make_content_images_responsive( $content ) { + $images = get_media_embedded_in_content( $content, 'img' ); + + $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 ( 0 < count( $attachment_ids ) ) { + /* + * Warm object caches for use with wp_get_attachment_metadata. + * + * 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 ); + } + + foreach( $images as $image ) { + $content = str_replace( $image, wp_img_add_srcset_and_sizes( $image ), $content ); + } + + return $content; +} + +/** + * Adds 'srcset' and 'sizes' attributes to an existing 'img' element. + * + * @since 4.4.0 + * + * @see wp_get_attachment_image_srcset() + * @see wp_get_attachment_image_sizes() + * + * @param string $image An HTML 'img' element to be filtered. + * @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="' ) ) { + 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; + $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : false; + + if ( $id && false === $size ) { + $size = array( $width, $height ); + } + + /* + * 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 ( ! $size && ! empty( $id ) && is_array( $meta = wp_get_attachment_metadata( $id ) ) ) { + // Parse the image src value from the img element. + $src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : false; + + // Return early if the src value is empty. + if ( ! $src ) { + return $image; + } + + /* + * First, see if the file is the full size image. If not, loop through + * the intermediate sizes until we find a file that matches. + */ + $image_filename = wp_basename( $src ); + + if ( $image_filename === basename( $meta['file'] ) ) { + $size = 'full'; + } else { + foreach( $meta['sizes'] as $image_size => $image_size_data ) { + if ( $image_filename === $image_size_data['file'] ) { + $size = $image_size; + break; + } + } + } + + } + + // If ID and size, try for 'srcset' and 'sizes' and update the markup. + if ( $id && $size && $srcset = wp_get_attachment_image_srcset( $id, $size ) ) { + + /* + * Pass the 'height' and 'width' to 'wp_get_attachment_image_sizes()' to avoid + * recalculating the image size. + */ + $args = array( + 'height' => $height, + 'width' => $width, + ); + + $sizes = wp_get_attachment_image_sizes( $id, $size, $args ); + + // 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( '/]+)[\s?][\/?]>/', '', $image ); + } + + return $image; +} + /** * Adds a 'wp-post-image' class to post thumbnails. Internal use only. * @@ -2986,11 +3372,12 @@ function get_media_embedded_in_content( $content, $types = null ) { * Filter the embedded media types that are allowed to be returned from the content blob. * * @since 4.2.0 + * @since 4.4.0 Added 'img' to the allowed types. * * @param array $allowed_media_types An array of allowed media types. Default media types are - * 'audio', 'video', 'object', 'embed', and 'iframe'. + * 'audio', 'video', 'object', 'embed', 'iframe', and 'img'. */ - $allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe' ) ); + $allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe', 'img' ) ); if ( ! empty( $types ) ) { if ( ! is_array( $types ) ) { diff --git a/tests/phpunit/data/images/test-image-large.png b/tests/phpunit/data/images/test-image-large.png new file mode 100644 index 0000000000..828c4a6bc3 Binary files /dev/null and b/tests/phpunit/data/images/test-image-large.png differ diff --git a/tests/phpunit/includes/factory.php b/tests/phpunit/includes/factory.php index bc8ea09b7d..40a5ab7417 100644 --- a/tests/phpunit/includes/factory.php +++ b/tests/phpunit/includes/factory.php @@ -94,6 +94,35 @@ class WP_UnitTest_Factory_For_Attachment extends WP_UnitTest_Factory_For_Post { function create_object( $file, $parent = 0, $args = array() ) { return wp_insert_attachment( $args, $file, $parent ); } + + function create_upload_object( $file, $parent = 0 ) { + $contents = file_get_contents($file); + $upload = wp_upload_bits(basename($file), null, $contents); + + $type = ''; + if ( ! empty($upload['type']) ) { + $type = $upload['type']; + } else { + $mime = wp_check_filetype( $upload['file'] ); + if ($mime) + $type = $mime['type']; + } + + $attachment = array( + 'post_title' => basename( $upload['file'] ), + 'post_content' => '', + 'post_type' => 'attachment', + 'post_parent' => $parent, + 'post_mime_type' => $type, + 'guid' => $upload[ 'url' ], + ); + + // Save the data + $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $parent ); + wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) ); + + return $id; + } } class WP_UnitTest_Factory_For_User extends WP_UnitTest_Factory_For_Thing { diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index d51eb92b92..f4888af191 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -18,7 +18,7 @@ CAP; $this->img_name = 'image.jpg'; $this->img_url = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . $this->img_name; $this->img_html = ''; - $this->img_dimensions = array( 'width' => 100, 'height' => 100 ); + $this->img_meta = array( 'width' => 100, 'height' => 100, 'sizes' => '' ); } function test_img_caption_shortcode_added() { @@ -288,7 +288,8 @@ EOF; 'post_mime_type' => 'image/jpeg', 'post_type' => 'attachment' ) ); - wp_update_attachment_metadata( $attachment_id, $this->img_dimensions ); + $metadata = array_merge( array( "file" => "image$i.jpg" ), $this->img_meta ); + wp_update_attachment_metadata( $attachment_id, $metadata ); $ids1[] = $attachment_id; $ids1_srcs[] = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . "image$i.jpg"; } @@ -300,7 +301,8 @@ EOF; 'post_mime_type' => 'image/jpeg', 'post_type' => 'attachment' ) ); - wp_update_attachment_metadata( $attachment_id, $this->img_dimensions ); + $metadata = array_merge( array( "file" => "image$i.jpg" ), $this->img_meta ); + wp_update_attachment_metadata( $attachment_id, $metadata ); $ids2[] = $attachment_id; $ids2_srcs[] = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . "image$i.jpg"; } @@ -329,7 +331,8 @@ BLOB; 'post_mime_type' => 'image/jpeg', 'post_type' => 'attachment' ) ); - wp_update_attachment_metadata( $attachment_id, $this->img_dimensions ); + $metadata = array_merge( array( "file" => "image$i.jpg" ), $this->img_meta ); + wp_update_attachment_metadata( $attachment_id, $metadata ); $ids1[] = $attachment_id; $ids1_srcs[] = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . "image$i.jpg"; } @@ -341,7 +344,8 @@ BLOB; 'post_mime_type' => 'image/jpeg', 'post_type' => 'attachment' ) ); - wp_update_attachment_metadata( $attachment_id, $this->img_dimensions ); + $metadata = array_merge( array( "file" => "image$i.jpg" ), $this->img_meta ); + wp_update_attachment_metadata( $attachment_id, $metadata ); $ids2[] = $attachment_id; $ids2_srcs[] = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . "image$i.jpg"; } @@ -501,6 +505,8 @@ VIDEO; $this->assertArrayHasKey( 'test-size', $_wp_additional_image_sizes ); $this->assertEquals( 200, $_wp_additional_image_sizes['test-size']['width'] ); $this->assertEquals( 600, $_wp_additional_image_sizes['test-size']['height'] ); + + // Clean up remove_image_size( 'test-size' ); } @@ -520,6 +526,8 @@ VIDEO; function test_has_image_size() { add_image_size( 'test-size', 200, 600 ); $this->assertTrue( has_image_size( 'test-size' ) ); + + // Clean up remove_image_size( 'test-size' ); } @@ -737,4 +745,356 @@ EOF; $this->assertEquals( $image[0], wp_get_attachment_image_url( $attachment_id ) ); } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_srcset_array() { + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + + $year_month = date('Y/m'); + $image = wp_get_attachment_metadata( $id ); + + $expected = array( + array( + 'url' => 'http://example.org/wp-content/uploads/' . $year_month . '/' . $image['sizes']['medium']['file'], + 'descriptor' => 'w', + 'value' => $image['sizes']['medium']['width'], + ), + array( + 'url' => 'http://example.org/wp-content/uploads/' . $year_month . '/' . $image['sizes']['large']['file'], + 'descriptor' => 'w', + 'value' => $image['sizes']['large']['width'], + ), + array( + 'url' => 'http://example.org/wp-content/uploads/' . $image['file'], + 'descriptor' => 'w', + 'value' => $image['width'], + ), + ); + + // Set up test cases for all expected size names and a random one. + $sizes = array( 'medium', 'large', 'full', 'yoav' ); + + foreach ( $sizes as $size ) { + $this->assertSame( $expected, wp_get_attachment_image_srcset_array( $id, $size ) ); + } + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_srcset_array_no_date_upoads() { + // Save the current setting for uploads folders + $uploads_use_yearmonth_folders = get_option( 'uploads_use_yearmonth_folders' ); + + // Disable date organized uploads + update_option( 'uploads_use_yearmonth_folders', 0 ); + + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + + $image = wp_get_attachment_metadata( $id ); + + $expected = array( + array( + 'url' => 'http://example.org/wp-content/uploads/' . $image['sizes']['medium']['file'], + 'descriptor' => 'w', + 'value' => $image['sizes']['medium']['width'], + ), + array( + 'url' => 'http://example.org/wp-content/uploads/' . $image['sizes']['large']['file'], + 'descriptor' => 'w', + 'value' => $image['sizes']['large']['width'], + ), + array( + 'url' => 'http://example.org/wp-content/uploads/' . $image['file'], + 'descriptor' => 'w', + 'value' => $image['width'], + ), + ); + + // Set up test cases for all expected size names and a random one. + $sizes = array( 'medium', 'large', 'full', 'yoav' ); + + foreach ( $sizes as $size ) { + $this->assertSame( $expected, wp_get_attachment_image_srcset_array( $id, $size ) ); + } + + // Leave the uploads option the way you found it. + update_option( 'uploads_use_yearmonth_folders', $uploads_use_yearmonth_folders ); + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_srcset_array_with_edits() { + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + // For this test we're going to mock metadata changes from an edit. + // Start by getting the attachment metadata. + $meta = wp_get_attachment_metadata( $id ); + + // Copy hash generation method used in wp_save_image(). + $hash = 'e' . time() . rand(100, 999); + + // Replace file paths for full and medium sizes with hashed versions. + $filename_base = basename( $meta['file'], '.png' ); + $meta['file'] = str_replace( $filename_base, $filename_base . '-' . $hash, $meta['file'] ); + $meta['sizes']['medium']['file'] = str_replace( $filename_base, $filename_base . '-' . $hash, $meta['sizes']['medium']['file'] ); + + // Save edited metadata. + wp_update_attachment_metadata( $id, $meta ); + + // Get the edited image and observe that a hash was created. + $img_url = wp_get_attachment_url( $id ); + + // Calculate a srcset array. + $sizes = wp_get_attachment_image_srcset_array( $id, 'medium' ); + + // Test to confirm all sources in the array include the same edit hash. + foreach ( $sizes as $size ) { + $this->assertTrue( false !== strpos( $size['url'], $hash ) ); + } + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_srcset_array_false() { + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + $sizes = wp_get_attachment_image_srcset_array( 99999, 'foo' ); + + // For canola.jpg we should return + $this->assertFalse( $sizes ); + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_srcset_array_no_width() { + // Filter image_downsize() output. + add_filter( 'wp_generate_attachment_metadata', array( $this, '_test_wp_get_attachment_image_srcset_array_no_width_filter' ) ); + + // Make our attachment. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + $srcset = wp_get_attachment_image_srcset_array( $id, 'medium' ); + + // The srcset should be false. + $this->assertFalse( $srcset ); + + // Remove filter. + remove_filter( 'wp_generate_attachment_metadata', array( $this, '_test_wp_get_attachment_image_srcset_array_no_width_filter' ) ); + } + + /** + * Helper function to filter image_downsize and return zero values for width and height. + */ + public function _test_wp_get_attachment_image_srcset_array_no_width_filter( $meta ) { + $meta['sizes']['medium']['width'] = 0; + $meta['sizes']['medium']['height'] = 0; + return $meta; + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_srcset() { + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + $sizes = wp_get_attachment_image_srcset( $id, 'full-size' ); + + $image = wp_get_attachment_metadata( $id ); + $year_month = date('Y/m'); + + $expected = 'http://example.org/wp-content/uploads/' . $year_month = date('Y/m') . '/' + . $image['sizes']['medium']['file'] . ' ' . $image['sizes']['medium']['width'] . 'w, '; + $expected .= 'http://example.org/wp-content/uploads/' . $year_month = date('Y/m') . '/' + . $image['sizes']['large']['file'] . ' ' . $image['sizes']['large']['width'] . 'w, '; + $expected .= 'http://example.org/wp-content/uploads/' . $image['file'] . ' ' . $image['width'] .'w'; + + $this->assertSame( $expected, $sizes ); + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_srcset_single_srcset() { + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + /* + * In our tests, thumbnails will only return a single srcset candidate, + * so we shouldn't return a srcset value in order to avoid unneeded markup. + */ + $sizes = wp_get_attachment_image_srcset( $id, 'thumbnail' ); + + $this->assertFalse( $sizes ); + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_sizes() { + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + + + global $content_width; + + // Test sizes against the default WP sizes. + $intermediates = array('thumbnail', 'medium', 'large'); + + foreach( $intermediates as $int ) { + $width = get_option( $int . '_size_w' ); + + // The sizes width gets constrained to $content_width by default. + if ( $content_width > 0 ) { + $width = ( $width > $content_width ) ? $content_width : $width; + } + + $expected = '(max-width: ' . $width . 'px) 100vw, ' . $width . 'px'; + $sizes = wp_get_attachment_image_sizes( $id, $int ); + + $this->assertSame($expected, $sizes); + } + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_sizes_with_args() { + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + + + $args = array( + 'sizes' => array( + array( + 'size_value' => '10em', + 'mq_value' => '60em', + 'mq_name' => 'min-width' + ), + array( + 'size_value' => '20em', + 'mq_value' => '30em', + 'mq_name' => 'min-width' + ), + array( + 'size_value' => 'calc(100vm - 30px)' + ), + ) + ); + + $expected = '(min-width: 60em) 10em, (min-width: 30em) 20em, calc(100vm - 30px)'; + $sizes = wp_get_attachment_image_sizes( $id, 'medium', $args ); + + $this->assertSame($expected, $sizes); + } + + /** + * @ticket 33641 + */ + function test_wp_get_attachment_image_sizes_with_filtered_args() { + // Add our test filter. + add_filter( 'wp_image_sizes_args', array( $this, '_test_wp_image_sizes_args' ) ); + + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + + $sizes = wp_get_attachment_image_sizes($id, 'medium'); + + // Evaluate that the sizes returned is what we expected. + $this->assertSame( $sizes, '100vm'); + + remove_filter( 'wp_image_sizes_args', array( $this, '_test_wp_image_sizes_args' ) ); + } + + /** + * A simple test filter for wp_get_attachment_image_sizes(). + */ + function _test_wp_image_sizes_args( $args ) { + $args['sizes'] = "100vm"; + return $args; + } + + /** + * @ticket 33641 + */ + function test_wp_make_content_images_responsive() { + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + + $srcset = sprintf( 'srcset="%s"', wp_get_attachment_image_srcset( $id, 'medium' ) ); + $sizes = sprintf( 'sizes="%s"', wp_get_attachment_image_sizes( $id, 'medium' ) ); + + // Function used to build HTML for the editor. + $img = get_image_tag( $id, '', '', '', 'medium' ); + $img_no_size = str_replace( 'size-', '', $img ); + $img_no_size_id = str_replace( 'wp-image-', 'id-', $img_no_size ); + + // Manually add srcset and sizes to the markup from get_image_tag(); + $respimg = preg_replace('|]+) />|', '', $img); + $respimg_no_size = preg_replace('|]+) />|', '', $img_no_size); + + $content = '

Welcome to WordPress! This post contains important information. After you read it, you can make it private to hide it from visitors but still have the information handy for future reference.

+

First things first:

+ + %1$s + + + + %2$s + +

As a subscriber, you will receive an email every time an update is available (and only then). This will make it easier to keep your site up to date, and secure from evildoers.
+ When a new version is released, log in to the Dashboard and follow the instructions.
+ Upgrading is a couple of clicks!

+ + %3$s + +

Then you can start enjoying the WordPress experience:

+ '; + + $content_unfiltered = sprintf( $content, $img, $img_no_size, $img_no_size_id ); + $content_filtered = sprintf( $content, $respimg, $respimg_no_size, $img_no_size_id ); + + $this->assertSame( $content_filtered, wp_make_content_images_responsive( $content_unfiltered ) ); + } + + /** + * @ticket 33641 + */ + function test_wp_make_content_images_responsive_with_preexisting_srcset() { + // Make an image. + $filename = DIR_TESTDATA . '/images/test-image-large.png'; + $id = $this->factory->attachment->create_upload_object( $filename ); + + // Generate HTML and add a dummy srcset attribute. + $image_html = get_image_tag( $id, '', '', '', 'medium' ); + $image_html = preg_replace('|]+) />|', '', $image_html ); + + // The content filter should return the image unchanged. + $this->assertSame( $image_html, wp_make_content_images_responsive( $image_html ) ); + } } diff --git a/tests/phpunit/tests/post/thumbnails.php b/tests/phpunit/tests/post/thumbnails.php index ee6a2dc333..cc8f61fa22 100644 --- a/tests/phpunit/tests/post/thumbnails.php +++ b/tests/phpunit/tests/post/thumbnails.php @@ -13,7 +13,7 @@ class Tests_Post_Thumbnail_Template extends WP_UnitTestCase { $this->post = $this->factory->post->create_and_get(); $file = DIR_TESTDATA . '/images/canola.jpg'; - $this->attachment_id = $this->factory->attachment->create_object( $file, $this->post->ID, array( + $this->attachment_id = $this->factory->attachment->create_upload_object( $file, $this->post->ID, array( 'post_mime_type' => 'image/jpeg', ) ); } diff --git a/tests/phpunit/tests/xmlrpc/mw/editPost.php b/tests/phpunit/tests/xmlrpc/mw/editPost.php index 1cadb0caf0..acafda3b6c 100644 --- a/tests/phpunit/tests/xmlrpc/mw/editPost.php +++ b/tests/phpunit/tests/xmlrpc/mw/editPost.php @@ -126,17 +126,7 @@ class Tests_XMLRPC_mw_editPost extends WP_XMLRPC_UnitTestCase { // create attachment $filename = ( DIR_TESTDATA.'/images/a2-small.jpg' ); - $contents = file_get_contents( $filename ); - $upload = wp_upload_bits( $filename, null, $contents ); - $this->assertTrue( empty( $upload['error'] ) ); - - $attachment = array( - 'post_title' => 'Post Thumbnail', - 'post_type' => 'attachment', - 'post_mime_type' => 'image/jpeg', - 'guid' => $upload['url'] - ); - $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $post_id ); + $attachment_id = $this->factory->attachment->create_upload_object( $filename, $post_id ); // add post thumbnail to post that does not have one $post2 = array( 'wp_post_thumbnail' => $attachment_id ); @@ -151,8 +141,7 @@ class Tests_XMLRPC_mw_editPost extends WP_XMLRPC_UnitTestCase { $this->assertEquals( $attachment_id, get_post_meta( $post_id, '_thumbnail_id', true ) ); // create another attachment - $attachment2 = array_merge( $attachment, array( 'title' => 'Post Thumbnail 2' ) ); - $attachment2_id = wp_insert_attachment( $attachment2, $upload['file'], $post_id ); + $attachment2_id = $this->factory->attachment->create_upload_object( $filename, $post_id ); // change the post's post_thumbnail $post4 = array( 'wp_post_thumbnail' => $attachment2_id ); diff --git a/tests/phpunit/tests/xmlrpc/mw/getPost.php b/tests/phpunit/tests/xmlrpc/mw/getPost.php index 60797a09cd..b5d80f8d32 100644 --- a/tests/phpunit/tests/xmlrpc/mw/getPost.php +++ b/tests/phpunit/tests/xmlrpc/mw/getPost.php @@ -95,17 +95,7 @@ class Tests_XMLRPC_mw_getPost extends WP_XMLRPC_UnitTestCase { // create attachment $filename = ( DIR_TESTDATA.'/images/a2-small.jpg' ); - $contents = file_get_contents( $filename ); - $upload = wp_upload_bits( $filename, null, $contents ); - $this->assertTrue( empty( $upload['error'] ) ); - - $attachment = array( - 'post_title' => 'Post Thumbnail', - 'post_type' => 'attachment', - 'post_mime_type' => 'image/jpeg', - 'guid' => $upload['url'] - ); - $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $this->post_id ); + $attachment_id = $this->factory->attachment->create_upload_object( $filename ); set_post_thumbnail( $this->post_id, $attachment_id ); diff --git a/tests/phpunit/tests/xmlrpc/mw/getRecentPosts.php b/tests/phpunit/tests/xmlrpc/mw/getRecentPosts.php index 7ad89fdd30..0f046dce49 100644 --- a/tests/phpunit/tests/xmlrpc/mw/getRecentPosts.php +++ b/tests/phpunit/tests/xmlrpc/mw/getRecentPosts.php @@ -39,7 +39,7 @@ class Tests_XMLRPC_mw_getRecentPosts extends WP_XMLRPC_UnitTestCase { $this->assertInstanceOf( 'IXR_Error', $result ); $this->assertEquals( 401, $result->code ); } - + function test_no_editable_posts() { wp_delete_post( $this->post_id ); @@ -100,17 +100,7 @@ class Tests_XMLRPC_mw_getRecentPosts extends WP_XMLRPC_UnitTestCase { // create attachment $filename = ( DIR_TESTDATA.'/images/a2-small.jpg' ); - $contents = file_get_contents( $filename ); - $upload = wp_upload_bits( $filename, null, $contents ); - $this->assertTrue( empty( $upload['error'] ) ); - - $attachment = array( - 'post_title' => 'Post Thumbnail', - 'post_type' => 'attachment', - 'post_mime_type' => 'image/jpeg', - 'guid' => $upload['url'] - ); - $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $this->post_id ); + $attachment_id = $this->factory->attachment->create_upload_object( $filename, $this->post_id ); set_post_thumbnail( $this->post_id, $attachment_id ); $results = $this->myxmlrpcserver->mw_getRecentPosts( array( $this->post_id, 'author', 'author' ) ); diff --git a/tests/phpunit/tests/xmlrpc/mw/newPost.php b/tests/phpunit/tests/xmlrpc/mw/newPost.php index 41b591bc9b..4e8d57ac66 100644 --- a/tests/phpunit/tests/xmlrpc/mw/newPost.php +++ b/tests/phpunit/tests/xmlrpc/mw/newPost.php @@ -117,17 +117,7 @@ class Tests_XMLRPC_mw_newPost extends WP_XMLRPC_UnitTestCase { // create attachment $filename = ( DIR_TESTDATA.'/images/a2-small.jpg' ); - $contents = file_get_contents( $filename ); - $upload = wp_upload_bits( $filename, null, $contents ); - $this->assertTrue( empty( $upload['error'] ) ); - - $attachment = array( - 'post_title' => 'Post Thumbnail', - 'post_type' => 'attachment', - 'post_mime_type' => 'image/jpeg', - 'guid' => $upload['url'] - ); - $attachment_id = wp_insert_attachment( $attachment, $upload['file'] ); + $attachment_id = $this->factory->attachment->create_upload_object( $filename ); $post = array( 'title' => 'Post Thumbnail Test', 'wp_post_thumbnail' => $attachment_id ); $result = $this->myxmlrpcserver->mw_newPost( array( 1, 'author', 'author', $post ) ); diff --git a/tests/phpunit/tests/xmlrpc/wp/editPost.php b/tests/phpunit/tests/xmlrpc/wp/editPost.php index 05a694f321..538a409c87 100644 --- a/tests/phpunit/tests/xmlrpc/wp/editPost.php +++ b/tests/phpunit/tests/xmlrpc/wp/editPost.php @@ -113,7 +113,7 @@ class Tests_XMLRPC_wp_editPost extends WP_XMLRPC_UnitTestCase { $out = get_post( $post_id ); $this->assertEquals( $editor_id, $out->post_author ); } - + function test_post_thumbnail() { add_theme_support( 'post-thumbnails' ); @@ -126,17 +126,7 @@ class Tests_XMLRPC_wp_editPost extends WP_XMLRPC_UnitTestCase { // create attachment $filename = ( DIR_TESTDATA.'/images/a2-small.jpg' ); - $contents = file_get_contents( $filename ); - $upload = wp_upload_bits( $filename, null, $contents ); - $this->assertTrue( empty( $upload['error'] ) ); - - $attachment = array( - 'post_title' => 'Post Thumbnail', - 'post_type' => 'attachment', - 'post_mime_type' => 'image/jpeg', - 'guid' => $upload['url'] - ); - $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $post_id ); + $attachment_id = $this->factory->attachment->create_upload_object( $filename, $post_id ); // add post thumbnail to post that does not have one $post2 = array( 'post_thumbnail' => $attachment_id ); @@ -158,8 +148,7 @@ class Tests_XMLRPC_wp_editPost extends WP_XMLRPC_UnitTestCase { $this->assertEquals( $attachment_id, get_post_meta( $post_id, '_thumbnail_id', true ) ); // create another attachment - $attachment2 = array_merge( $attachment, array( 'post_title' => 'Post Thumbnail 2' ) ); - $attachment2_id = wp_insert_attachment( $attachment2, $upload['file'], $post_id ); + $attachment2_id = $this->factory->attachment->create_upload_object( $filename, $post_id ); // change the post's post_thumbnail $post4 = array( 'post_thumbnail' => $attachment2_id ); diff --git a/tests/phpunit/tests/xmlrpc/wp/newPost.php b/tests/phpunit/tests/xmlrpc/wp/newPost.php index 9cc2d2877a..5f00ddb165 100644 --- a/tests/phpunit/tests/xmlrpc/wp/newPost.php +++ b/tests/phpunit/tests/xmlrpc/wp/newPost.php @@ -128,17 +128,7 @@ class Tests_XMLRPC_wp_newPost extends WP_XMLRPC_UnitTestCase { // create attachment $filename = ( DIR_TESTDATA.'/images/a2-small.jpg' ); - $contents = file_get_contents( $filename ); - $upload = wp_upload_bits( $filename, null, $contents ); - $this->assertTrue( empty( $upload['error'] ) ); - - $attachment = array( - 'post_title' => 'Post Thumbnail', - 'post_type' => 'attachment', - 'post_mime_type' => 'image/jpeg', - 'guid' => $upload['url'] - ); - $attachment_id = wp_insert_attachment( $attachment, $upload['file'] ); + $attachment_id = $this->factory->attachment->create_upload_object( $filename ); $post = array( 'post_title' => 'Post Thumbnail Test', 'post_thumbnail' => $attachment_id ); $result = $this->myxmlrpcserver->wp_newPost( array( 1, 'author', 'author', $post ) );