Skip to content

Commit

Permalink
Merge pull request #152 from WordPress/feature/149-update-the-content
Browse files Browse the repository at this point in the history
Update `the_content` with the appropiate image format
  • Loading branch information
felixarntz authored Feb 28, 2022
2 parents c655588 + a1e2e8c commit 81b2171
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 3 deletions.
135 changes: 135 additions & 0 deletions modules/images/webp-uploads/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,138 @@ function webp_uploads_wp_get_missing_image_subsizes( $missing_sizes, $image_meta
}

add_filter( 'wp_get_missing_image_subsizes', 'webp_uploads_wp_get_missing_image_subsizes', 10, 3 );

/**
* Filters `the_content` to update images so that they use the preferred MIME type where possible.
*
* By default, this is `image/webp`, if the current attachment contains the targeted MIME
* type. In the near future this will be filterable.
*
* Note that most of this function will not be needed for an eventual core implementation as it
* would rely on `wp_filter_content_tags()`.
*
* @since n.e.x.t
*
* @see wp_filter_content_tags()
*
* @param string $content The content of the current post.
* @return string The content with the updated references to the images.
*/
function webp_uploads_update_image_references( $content ) {
// This content does not have any tag on it, move forward.
if ( ! preg_match_all( '/<(img)\s[^>]+>/', $content, $img_tags, PREG_SET_ORDER ) ) {
return $content;
}

$images = array();
foreach ( $img_tags as list( $img ) ) {
// Find the ID of each image by the class.
if ( ! preg_match( '/wp-image-([\d]+)/i', $img, $class_name ) ) {
continue;
}

if ( empty( $class_name ) ) {
continue;
}

// Make sure we use the last item on the list of matches.
$attachment_id = (int) $class_name[1];

if ( ! $attachment_id ) {
continue;
}

$images[ $img ] = $attachment_id;
}

$attachment_ids = array_unique( array_filter( array_values( $images ) ) );
if ( count( $attachment_ids ) > 1 ) {
/**
* Warm the object cache with post and meta information for all found
* images to avoid making individual database calls.
*/
_prime_post_caches( $attachment_ids, false, true );
}

foreach ( $images as $img => $attachment_id ) {
$content = str_replace( $img, webp_uploads_img_tag_update_mime_type( $img, 'the_content', $attachment_id ), $content );
}

return $content;
}

/**
* Finds all the urls with *.jpg and *.jpeg extension and updates with *.webp version for the provided image
* for the specified image sizes, the *.webp references are stored inside of each size.
*
* @since n.e.x.t
*
* @param string $image An <img> tag where the urls would be updated.
* @param string $context The context where this is function is being used.
* @param int $attachment_id The ID of the attachment being modified.
* @return string The updated img tag.
*/
function webp_uploads_img_tag_update_mime_type( $image, $context, $attachment_id ) {
$metadata = wp_get_attachment_metadata( $attachment_id );
if ( empty( $metadata['file'] ) ) {
return $image;
}

// TODO: Add a filterable option to determine image extensions, see https://github.com/WordPress/performance/issues/187 for more details.
$target_image_extensions = array(
'jpg',
'jpeg',
);

// Creates a regular extension to find all the URLS with the provided extension for img tag.
preg_match_all( '/[^\s"]+\.(?:' . implode( '|', $target_image_extensions ) . ')/i', $image, $matches );
if ( empty( $matches ) ) {
return $image;
}

$urls = $matches[0];
// TODO: Add a filterable option to change the selected mime type. See https://github.com/WordPress/performance/issues/187.
$target_mime = 'image/webp';

$basename = wp_basename( $metadata['file'] );
foreach ( $urls as $url ) {
if ( isset( $metadata['file'] ) && strpos( $url, $basename ) !== false ) {
// TODO: we don't have a replacement for full image yet, issue. See: https://github.com/WordPress/performance/issues/174.
continue;
}

if ( empty( $metadata['sizes'] ) ) {
continue;
}

$src_filename = wp_basename( $url );
$extension = wp_check_filetype( $src_filename );
// Extension was not set properly no action possible or extension is already in the expected mime.
if ( empty( $extension['type'] ) || $extension['type'] === $target_mime ) {
continue;
}

// Find the appropriate size for the provided URL.
foreach ( $metadata['sizes'] as $name => $size_data ) {
// Not the size we are looking for.
if ( empty( $size_data['file'] ) || $src_filename !== $size_data['file'] ) {
continue;
}

if ( empty( $size_data['sources'][ $target_mime ]['file'] ) ) {
continue;
}

// This is the same as the file we want to replace nothing to do here.
if ( $size_data['sources'][ $target_mime ]['file'] === $src_filename ) {
continue;
}

$image = str_replace( $src_filename, $size_data['sources'][ $target_mime ]['file'], $image );
}
}

return $image;
}

add_filter( 'the_content', 'webp_uploads_update_image_references', 10 );
152 changes: 149 additions & 3 deletions tests/modules/images/webp-uploads/webp-uploads-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function provider_image_with_default_behaviors_during_upload() {
);

yield 'WebP image' => array(
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/ballons.webp',
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/balloons.webp',
'image/webp',
'image/jpeg',
);
Expand Down Expand Up @@ -110,7 +110,7 @@ function () {
);

$attachment_id = $this->factory->attachment->create_upload_object(
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/ballons.webp'
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/balloons.webp'
);

$metadata = wp_get_attachment_metadata( $attachment_id );
Expand All @@ -130,7 +130,7 @@ function () {
*/
public function it_should_prevent_processing_an_image_with_corrupted_metadata( callable $callback, $size ) {
$attachment_id = $this->factory->attachment->create_upload_object(
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/ballons.webp'
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/balloons.webp'
);
$metadata = wp_get_attachment_metadata( $attachment_id );
wp_update_attachment_metadata( $attachment_id, $callback( $metadata ) );
Expand Down Expand Up @@ -385,4 +385,150 @@ public function it_should_remove_the_webp_version_of_the_image_if_the_image_is_f
path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
);
}

/**
* Avoid the change of URLs of images that are not part of the media library
*
* @group webp_uploads_update_image_references
*
* @test
*/
public function it_should_avoid_the_change_of_urls_of_images_that_are_not_part_of_the_media_library() {
$paragraph = '<p>Donec accumsan, sapien et <img src="https://ia600200.us.archive.org/16/items/SPD-SLRSY-1867/hubblesite_2001_06.jpg">, id commodo nisi sapien et est. Mauris nisl odio, iaculis vitae pellentesque nec.</p>';

$this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
}

/**
* Avoid replacing not existing attachment IDs
*
* @group webp_uploads_update_image_references
*
* @test
*/
public function it_should_avoid_replacing_not_existing_attachment_i_ds() {
$paragraph = '<p>Donec accumsan, sapien et <img class="wp-image-0" src="https://ia600200.us.archive.org/16/items/SPD-SLRSY-1867/hubblesite_2001_06.jpg">, id commodo nisi sapien et est. Mauris nisl odio, iaculis vitae pellentesque nec.</p>';

$this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
}

/**
* Prevent replacing a WebP image
*
* @group webp_uploads_update_image_references
*
* @test
*/
public function it_should_prevent_replacing_a_webp_image() {
$attachment_id = $this->factory->attachment->create_upload_object(
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/balloons.webp'
);

$tag = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );

$this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
}

/**
* Prevent replacing a jpg image if the image does not have the target class name
*
* @test
*/
public function it_should_prevent_replacing_a_jpg_image_if_the_image_does_not_have_the_target_class_name() {
$attachment_id = $this->factory->attachment->create_upload_object(
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/leafs.jpg'
);

$tag = wp_get_attachment_image( $attachment_id, 'medium' );

$this->assertSame( $tag, webp_uploads_update_image_references( $tag ) );
}

/**
* Replace the references to a JPG image to a WebP version
*
* @dataProvider provider_replace_images_with_different_extensions
* @group webp_uploads_update_image_references
*
* @test
*/
public function it_should_replace_the_references_to_a_jpg_image_to_a_webp_version( $image_path ) {
$attachment_id = $this->factory->attachment->create_upload_object( $image_path );

$tag = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
$expected_tag = $tag;
$metadata = wp_get_attachment_metadata( $attachment_id );
foreach ( $metadata['sizes'] as $size => $properties ) {
$expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
}

$this->assertNotEmpty( $expected_tag );
$this->assertNotSame( $tag, $expected_tag );
$this->assertSame( $expected_tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
}

public function provider_replace_images_with_different_extensions() {
yield 'An image with a .jpg extension' => array( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/leafs.jpg' );
yield 'An image with a .jpeg extension' => array( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/car.jpeg' );
}

/**
* Contain the full image size from the original mime
*
* @group webp_uploads_update_image_references
*
* @test
*/
public function it_should_contain_the_full_image_size_from_the_original_mime() {
$attachment_id = $this->factory->attachment->create_upload_object(
TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/leafs.jpg'
);

$tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );

$expected = array(
'ext' => 'jpg',
'type' => 'image/jpeg',
);
$this->assertSame( $expected, wp_check_filetype( get_attached_file( $attachment_id ) ) );
$this->assertContains( wp_basename( get_attached_file( $attachment_id ) ), webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
}

/**
* Prevent replacing an image with no available sources
*
* @group webp_uploads_update_image_references
*
* @test
*/
public function it_should_prevent_replacing_an_image_with_no_available_sources() {
add_filter( 'webp_uploads_supported_image_mime_transforms', '__return_empty_array' );

$attachment_id = $this->factory->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/car.jpeg' );

$tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
$this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
}

/**
* Prevent update not supported images with no available sources
*
* @dataProvider data_provider_not_supported_webp_images
* @group webp_uploads_update_image_references
*
* @test
*/
public function it_should_prevent_update_not_supported_images_with_no_available_sources( $image_path ) {
$attachment_id = $this->factory->attachment->create_upload_object( $image_path );

$this->assertIsNumeric( $attachment_id );
$tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );

$this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
}

public function data_provider_not_supported_webp_images() {
yield 'PNG image' => array( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/dice.png' );
yield 'GIFT image' => array( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/earth.gif' );
}
}
File renamed without changes.
Binary file added tests/testdata/modules/images/car.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/testdata/modules/images/dice.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/testdata/modules/images/earth.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 81b2171

Please sign in to comment.