diff --git a/plugins/webp-uploads/deprecated.php b/plugins/webp-uploads/deprecated.php index eba603e76..a21b56a45 100644 --- a/plugins/webp-uploads/deprecated.php +++ b/plugins/webp-uploads/deprecated.php @@ -40,3 +40,25 @@ function webp_uploads_get_attachment_sources( int $attachment_id, string $size = // Return an empty array if no sources found. return array(); } + +/** + * Adds custom styles to hide specific elements in media settings. + * + * @since 1.0.0 + * @deprecated This function is not used as of Modern Image Formats versions 2.0.0. + */ +function webp_uploads_media_setting_style(): void { + _deprecated_function( __FUNCTION__, 'Modern Image Formats 2.0.0' ); + + if ( is_multisite() ) { + return; + } + ?> + + > An array of valid mime types, where the key is the mime type and the value is the extension type. */ function webp_uploads_get_upload_image_mime_transforms(): array { + + // Check the selected output format. + $output_format = webp_uploads_mime_type_supported( 'image/avif' ) ? webp_uploads_get_image_output_format() : 'webp'; + $default_transforms = array( - 'image/jpeg' => array( 'image/webp' ), + 'image/jpeg' => array( 'image/' . $output_format ), 'image/webp' => array( 'image/webp' ), + 'image/avif' => array( 'image/avif' ), ); - // Check setting for whether to generate both JPEG and WebP. + // Check setting for whether to generate both JPEG and the modern output format. if ( true === (bool) get_option( 'perflab_generate_webp_and_jpeg' ) ) { $default_transforms = array( - 'image/jpeg' => array( 'image/jpeg', 'image/webp' ), - 'image/webp' => array( 'image/webp', 'image/jpeg' ), + 'image/jpeg' => array( 'image/jpeg', 'image/' . $output_format ), + 'image/' . $output_format => array( 'image/' . $output_format, 'image/jpeg' ), ); } @@ -246,7 +252,7 @@ function webp_uploads_generate_image_size( int $attachment_id, string $size, str * @return string[] Mime types to use for the image. */ function webp_uploads_get_content_image_mimes( int $attachment_id, string $context ): array { - $target_mimes = array( 'image/webp', 'image/jpeg' ); + $target_mimes = array( 'image/' . webp_uploads_get_image_output_format(), 'image/jpeg' ); /** * Filters mime types that should be used to update all images in the content. The order of @@ -322,3 +328,59 @@ function webp_uploads_should_discard_additional_image_file( array $original, arr } return false; } + +/** + * Checks if a mime type is supported by the server. + * + * Includes special handling for false positives on AVIF support. + * + * @since n.e.x.t + * + * @param string $mime_type The mime type to check. + * @return bool Whether the server supports a given mime type. + */ +function webp_uploads_mime_type_supported( string $mime_type ): bool { + if ( ! wp_image_editor_supports( array( 'mime_type' => $mime_type ) ) ) { + return false; + } + + // In certain server environments Image editors can report a false positive for AVIF support. + if ( 'image/avif' === $mime_type ) { + $editor = _wp_image_editor_choose( array( 'mime_type' => 'image/avif' ) ); + if ( false === $editor ) { + return false; + } + if ( is_a( $editor, WP_Image_Editor_GD::class, true ) ) { + return function_exists( 'imageavif' ); + } + if ( is_a( $editor, WP_Image_Editor_Imagick::class, true ) && class_exists( 'Imagick' ) ) { + return 0 !== count( Imagick::queryFormats( 'AVIF' ) ); + } + } + + return true; +} + +/** + * Get the image output format setting from the option. Default is avif. + * + * @since n.e.x.t + * + * @return string The image output format. One of 'webp' or 'avif'. + */ +function webp_uploads_get_image_output_format(): string { + $image_format = get_option( 'perflab_modern_image_format' ); + return webp_uploads_sanitize_image_format( $image_format ); +} + +/** + * Sanitizes the image format. + * + * @since n.e.x.t + * + * @param string $image_format The image format to check. + * @return string Supported image format. + */ +function webp_uploads_sanitize_image_format( string $image_format ): string { + return in_array( $image_format, array( 'webp', 'avif' ), true ) ? $image_format : 'webp'; +} diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index e5b542da1..a9c5fc3b2 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -679,7 +679,7 @@ function webp_uploads_img_tag_update_mime_type( string $original_image, string $ $image !== $original_image && 'the_content' === $context && 'image/jpeg' === $original_mime && - 'image/webp' === $target_mime + 'image/' . webp_uploads_get_image_output_format() === $target_mime ) { add_action( 'wp_footer', 'webp_uploads_wepb_fallback' ); } @@ -711,41 +711,58 @@ function webp_uploads_update_featured_image( string $html, int $post_id, int $at */ function webp_uploads_wepb_fallback(): void { // Get mime type transforms for the site. - $transforms = webp_uploads_get_upload_image_mime_transforms(); + $transforms = webp_uploads_get_upload_image_mime_transforms(); + $image_format = webp_uploads_get_image_output_format(); - // We need to add fallback only if jpeg alternatives for the webp images are enabled for the server. - $preserve_jpegs_for_jpeg_transforms = isset( $transforms['image/jpeg'] ) && in_array( 'image/jpeg', $transforms['image/jpeg'], true ) && in_array( 'image/webp', $transforms['image/jpeg'], true ); - $preserve_jpegs_for_webp_transforms = isset( $transforms['image/webp'] ) && in_array( 'image/jpeg', $transforms['image/webp'], true ) && in_array( 'image/webp', $transforms['image/webp'], true ); - if ( ! $preserve_jpegs_for_jpeg_transforms && ! $preserve_jpegs_for_webp_transforms ) { + // We need to add fallback only if jpeg alternatives for the image_format images are enabled for the server. + $preserve_jpegs_for_jpeg_transforms = isset( $transforms['image/jpeg'] ) && in_array( 'image/jpeg', $transforms['image/jpeg'], true ) && in_array( 'image/' . $image_format, $transforms['image/jpeg'], true ); + $preserve_jpegs_for_image_type_transforms = isset( $transforms[ 'image/' . $image_format ] ) && in_array( 'image/jpeg', $transforms[ 'image/' . $image_format ], true ) && in_array( 'image/' . $image_format, $transforms[ 'image/' . $image_format ], true ); + if ( ! $preserve_jpegs_for_jpeg_transforms && ! $preserve_jpegs_for_image_type_transforms ) { return; } - ob_start(); + $detection_string = ''; + // The fallback script can only handle a single image format at a time. + if ( 'webp' === $image_format ) { + $detection_string = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA='; + } elseif ( 'avif' === $image_format ) { + $detection_string = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A='; + } + if ( '' === $detection_string ) { + return; + } - ?> - ( function( d, i, s, p ) { + $js_function = <<'; + s.src = fallbackSrc; i = d.createElement( i ); - i.src = p + 'jIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA=='; + i.src = p; i.onload = function() { i.onload = undefined; - i.src = p + 'h4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA='; }; i.onerror = function() { d.body.appendChild( s ); }; - } )( document, 'img', 'script', 'data:image/webp;base64,UklGR' ); - 'webpUploadsFallbackWebpImages', - 'data-rest-api' => esc_url_raw( trailingslashit( get_rest_url() ) ), + 'id' => 'webpUploadsFallbackWebpImages', + 'data-rest-api' => esc_url_raw( trailingslashit( get_rest_url() ) ), + 'data-output-format' => webp_uploads_get_image_output_format(), ) ); } diff --git a/plugins/webp-uploads/readme.txt b/plugins/webp-uploads/readme.txt index e0450993b..80b70a643 100644 --- a/plugins/webp-uploads/readme.txt +++ b/plugins/webp-uploads/readme.txt @@ -4,16 +4,18 @@ Contributors: wordpressdotorg Requires at least: 6.4 Tested up to: 6.5 Requires PHP: 7.2 -Stable tag: 1.1.1 +Stable tag: 2.0.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html -Tags: performance, images, webp +Tags: performance, images, webp, avif, modern image formats Converts images to more modern formats such as WebP or AVIF during upload. == Description == -This plugin adds WebP support for media uploads within the WordPress application. WebP images will be generated only for new uploads, pre-existing imagery will not be converted to WebP format. By default, WebP images will only be generated for JPEG uploads, only the original uploaded file will still exist as a JPEG image. All generated image sizes will exist as WebP only. If you wish to change this behaviour, there is a checkbox in `Settings > Media` that - when checked - will alter the behaviour of this plugin to generate both JPEG and WebP images for every sub-size (noting again that this will only affect newly uploaded images, i.e. after making said change). +This plugin adds WebP and AVIF support for media uploads within the WordPress application. By default, AVIF images will be generated if supported on the hosting server, otherwise WebP will be used as the output format. When both formats are available, the output format can be selected under `Settings > Media`. Modern images will be generated only for new uploads, pre-existing images will only converted to a modern format if images are "Regenerated". + +By default, only modern image format sub-sizes will be generated for JPEG uploads - only the original uploaded file will still exist as a JPEG image, generated image sizes use be WebP or AVIF files. To change this behavior, there is a checkbox in `Settings > Media` "Also output JPEG" that - when checked - will result in the plugin generating both JPEG and WebP or AVIF images for every sub-size (noting again that this will only affect newly uploaded images, i.e. after making said change). _This plugin was formerly known as WebP Uploads._ @@ -60,6 +62,9 @@ By default, the Modern Image Formats plugin will only generate WebP versions of == Changelog == += 2.0.0 = +* Add AVIF image format support. Add setting for output image format to choose between WebP and AVIF. ([1176](https://github.com/WordPress/performance/pull/1176)) + = 1.1.1 = **Enhancements** @@ -104,3 +109,9 @@ By default, the Modern Image Formats plugin will only generate WebP versions of = 1.0.0 = * Initial release of the Modern Image Formats plugin as a standalone plugin. ([664](https://github.com/WordPress/performance/pull/664)) + +== Upgrade Notice == + += 2.0.0 = + +This release adds support for AVIF images and enables selecting the the output image format to choose between WebP and AVIF when both are available. AVIF is used as the default when the server supports it. \ No newline at end of file diff --git a/plugins/webp-uploads/settings.php b/plugins/webp-uploads/settings.php index 6d95f6fd0..392247607 100644 --- a/plugins/webp-uploads/settings.php +++ b/plugins/webp-uploads/settings.php @@ -12,11 +12,26 @@ } /** - * Registers setting for generating both JPEG and WebP versions for image uploads. + * Registers setting for generating JPEG in addition to the selected modern format for image uploads. * * @since 1.0.0 + * @since n.e.x.t The setting was made more general to cover outputting JPEG as a secondary type. The "webp" option naming + * was left unchanged for backward compatibility. + * @since n.e.x.t The `perflab_modern_image_format` was added to enable selecting an output format. + * Currently includes AVIF and WebP. */ function webp_uploads_register_media_settings_field(): void { + register_setting( + 'media', + 'perflab_modern_image_format', + array( + 'sanitize_callback' => 'webp_uploads_sanitize_image_format', + 'type' => 'string', + 'default' => 'avif', // AVIF is the default if the editor supports it. + 'show_in_rest' => false, + ) + ); + register_setting( 'media', 'perflab_generate_webp_and_jpeg', @@ -35,10 +50,21 @@ function webp_uploads_register_media_settings_field(): void { * @since 1.0.0 */ function webp_uploads_add_media_settings_field(): void { + + // Add a dropdown to select the output format between AVIF and WebP output. + add_settings_field( + 'perflab_modern_image_format', + __( 'Modern image format', 'webp-uploads' ), + 'webp_uploads_generate_avif_webp_setting_callback', + 'media', + is_multisite() ? 'default' : 'uploads', + array( 'class' => 'perflab-generate-avif-and-webp' ) + ); + // Add settings field. add_settings_field( 'perflab_generate_webp_and_jpeg', - __( 'WebP and JPEG', 'webp-uploads' ), + __( 'Also output JPEG', 'webp-uploads' ), 'webp_uploads_generate_webp_jpeg_setting_callback', 'media', is_multisite() ? 'default' : 'uploads', @@ -48,45 +74,52 @@ function webp_uploads_add_media_settings_field(): void { add_action( 'admin_init', 'webp_uploads_add_media_settings_field' ); /** - * Renders the settings field for the 'perflab_generate_webp_and_jpeg' setting. + * Renders the settings field for the 'perflab_modern_image_format' setting. * - * @since 1.0.0 + * @since n.e.x.t */ -function webp_uploads_generate_webp_jpeg_setting_callback(): void { - if ( ! is_multisite() ) { - ?> - - - - -

+ + +

+ +
+
+

+

+
+ - + + +

set_image_output_type( 'webp' ); + } + /** * Return an error when creating an additional image source with invalid parameters * @@ -207,6 +212,7 @@ public function test_it_should_prevent_to_create_a_subsize_if_the_image_editor_d TESTS_PLUGIN_DIR . '/tests/data/images/leaves.jpg' ); + update_option( 'perflab_modern_image_format', 'webp' ); // Make sure no editor is available. add_filter( 'wp_image_editors', '__return_empty_array' ); $result = webp_uploads_generate_image_size( $attachment_id, 'medium', 'image/webp' ); @@ -238,7 +244,7 @@ public function test_it_should_prevent_to_process_an_image_when_the_editor_does_ add_filter( 'wp_image_editors', static function () { - return array( 'WP_Image_Doesnt_Support_WebP' ); + return array( 'WP_Image_Doesnt_Support_Modern_Images' ); } ); @@ -355,10 +361,20 @@ public function test_it_should_return_empty_array_when_filter_returns_empty_arra public function test_it_should_return_default_transforms_when_filter_returns_non_array_type(): void { add_filter( 'webp_uploads_upload_image_mime_transforms', '__return_zero' ); - $default_transforms = array( - 'image/jpeg' => array( 'image/webp' ), - 'image/webp' => array( 'image/webp' ), - ); + if ( webp_uploads_mime_type_supported( 'image/avif' ) ) { + $this->set_image_output_type( 'avif' ); + $default_transforms = array( + 'image/jpeg' => array( 'image/avif' ), + 'image/webp' => array( 'image/webp' ), + 'image/avif' => array( 'image/avif' ), + ); + } else { + $default_transforms = array( + 'image/jpeg' => array( 'image/webp' ), + 'image/webp' => array( 'image/webp' ), + 'image/avif' => array( 'image/avif' ), + ); + } $transforms = webp_uploads_get_upload_image_mime_transforms(); @@ -403,17 +419,31 @@ static function () { public function test_it_should_return_jpeg_and_webp_transforms_when_option_generate_webp_and_jpeg_set(): void { remove_all_filters( 'webp_uploads_get_upload_image_mime_transforms' ); + if ( webp_uploads_mime_type_supported( 'image/avif' ) ) { + $this->set_image_output_type( 'avif' ); + } update_option( 'perflab_generate_webp_and_jpeg', true ); $transforms = webp_uploads_get_upload_image_mime_transforms(); - $this->assertSame( - array( - 'image/jpeg' => array( 'image/jpeg', 'image/webp' ), - 'image/webp' => array( 'image/webp', 'image/jpeg' ), - ), - $transforms - ); + // The returned value depends on whether the server supports AVIF. + if ( webp_uploads_mime_type_supported( 'image/avif' ) ) { + $this->assertSame( + array( + 'image/jpeg' => array( 'image/jpeg', 'image/avif' ), + 'image/avif' => array( 'image/avif', 'image/jpeg' ), + ), + $transforms + ); + } else { + $this->assertSame( + array( + 'image/jpeg' => array( 'image/jpeg', 'image/webp' ), + 'image/webp' => array( 'image/webp', 'image/jpeg' ), + ), + $transforms + ); + } } /** diff --git a/tests/plugins/webp-uploads/test-image-edit.php b/tests/plugins/webp-uploads/test-image-edit.php index 3cd705d30..527730946 100644 --- a/tests/plugins/webp-uploads/test-image-edit.php +++ b/tests/plugins/webp-uploads/test-image-edit.php @@ -12,6 +12,7 @@ class Test_WebP_Uploads_Image_Edit extends ImagesTestCase { public function set_up(): void { parent::set_up(); + $this->set_image_output_type( 'webp' ); add_filter( 'webp_uploads_discard_larger_generated_images', '__return_false' ); } diff --git a/tests/plugins/webp-uploads/test-load.php b/tests/plugins/webp-uploads/test-load.php index 8082655d2..44d5eacc4 100644 --- a/tests/plugins/webp-uploads/test-load.php +++ b/tests/plugins/webp-uploads/test-load.php @@ -24,6 +24,9 @@ public function set_up(): void { if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) { $this->markTestSkipped( 'Mime type image/webp is not supported.' ); } + + // Default to webp output for tests. + $this->set_image_output_type( 'webp' ); } public function tear_down(): void { @@ -39,20 +42,27 @@ static function ( string $filename ) { /** * Don't create the original mime type for JPEG images. + * + * @dataProvider data_provider_supported_image_types */ - public function test_it_should_not_create_the_original_mime_type_for_jpeg_images(): void { + public function test_it_should_not_create_the_original_mime_type_for_jpeg_images( string $image_type ): void { + $mime_type = 'image/' . $image_type; + $this->set_image_output_type( $image_type ); + if ( ! webp_uploads_mime_type_supported( $mime_type ) ) { + $this->markTestSkipped( "Mime type $mime_type is not supported." ); + } $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/leaves.jpg' ); - // There should be a WebP source, but no JPEG source for the full image. - $this->assertImageHasSource( $attachment_id, 'image/webp' ); + // There should be an image_type source, but no JPEG source for the full image. + $this->assertImageHasSource( $attachment_id, $mime_type ); $this->assertImageNotHasSource( $attachment_id, 'image/jpeg' ); $metadata = wp_get_attachment_metadata( $attachment_id ); - // The full image should be a WebP. + // The full image should be an image_type. $this->assertArrayHasKey( 'file', $metadata ); - $this->assertStringEndsWith( $metadata['sources']['image/webp']['file'], $metadata['file'] ); - $this->assertStringEndsWith( $metadata['sources']['image/webp']['file'], get_attached_file( $attachment_id ) ); + $this->assertStringEndsWith( $metadata['sources'][ $mime_type ]['file'], $metadata['file'] ); + $this->assertStringEndsWith( $metadata['sources'][ $mime_type ]['file'], get_attached_file( $attachment_id ) ); // The original JPEG should be backed up. $this->assertStringEndsWith( '.jpg', wp_get_original_image_path( $attachment_id ) ); @@ -62,7 +72,7 @@ public function test_it_should_not_create_the_original_mime_type_for_jpeg_images // There should be a WebP source, but no JPEG source for all sizes. foreach ( array_keys( $metadata['sizes'] ) as $size_name ) { - $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/webp' ); + $this->assertImageHasSizeSource( $attachment_id, $size_name, $mime_type ); $this->assertImageNotHasSizeSource( $attachment_id, $size_name, 'image/jpeg' ); } } @@ -71,6 +81,8 @@ public function test_it_should_not_create_the_original_mime_type_for_jpeg_images * Create the original mime type for WebP images. */ public function test_it_should_create_the_original_mime_type_as_well_with_all_the_available_sources_for_the_specified_mime(): void { + update_option( 'perflab_generate_webp_and_jpeg', false ); + $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/balloons.webp' ); // There should be a WebP source, but no JPEG source for the full image. @@ -95,16 +107,23 @@ public function test_it_should_create_the_original_mime_type_as_well_with_all_th } /** - * Create JPEG and WebP for JPEG images, if opted in. + * Create JPEG and output type for JPEG images, if opted in. + * + * @dataProvider data_provider_supported_image_types */ - public function test_it_should_create_jpeg_and_webp_for_jpeg_images_if_opted_in(): void { - $this->opt_in_to_jpeg_and_webp(); + public function test_it_should_create_jpeg_and_webp_for_jpeg_images_if_opted_in( string $image_type ): void { + $mime_type = 'image/' . $image_type; + if ( ! webp_uploads_mime_type_supported( $mime_type ) ) { + $this->markTestSkipped( "Mime type $mime_type is not supported." ); + } + $this->set_image_output_type( $image_type ); + update_option( 'perflab_generate_webp_and_jpeg', true ); $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/leaves.jpg' ); - // There should be JPEG and WebP sources for the full image. + // There should be JPEG and mime_type sources for the full image. $this->assertImageHasSource( $attachment_id, 'image/jpeg' ); - $this->assertImageHasSource( $attachment_id, 'image/webp' ); + $this->assertImageHasSource( $attachment_id, $mime_type ); $metadata = wp_get_attachment_metadata( $attachment_id ); @@ -119,21 +138,29 @@ public function test_it_should_create_jpeg_and_webp_for_jpeg_images_if_opted_in( // There should be JPEG and WebP sources for all sizes. foreach ( array_keys( $metadata['sizes'] ) as $size_name ) { $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/jpeg' ); - $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/webp' ); + $this->assertImageHasSizeSource( $attachment_id, $size_name, $mime_type ); } } /** - * Create JPEG and WebP for JPEG images, if perflab_generate_webp_and_jpeg option set. + * Create JPEG and output format for JPEG images, if perflab_generate_webp_and_jpeg option set. + * + * @dataProvider data_provider_supported_image_types */ - public function test_it_should_create_jpeg_and_webp_for_jpeg_images_if_generate_webp_and_jpeg_set(): void { + public function test_it_should_create_jpeg_and_webp_for_jpeg_images_if_generate_webp_and_jpeg_set( string $image_type ): void { + $mime_type = 'image/' . $image_type; + + if ( ! webp_uploads_mime_type_supported( $mime_type ) ) { + $this->markTestSkipped( "Mime type $mime_type is not supported." ); + } + $this->set_image_output_type( $image_type ); update_option( 'perflab_generate_webp_and_jpeg', true ); $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/leaves.jpg' ); // There should be JPEG and WebP sources for the full image. $this->assertImageHasSource( $attachment_id, 'image/jpeg' ); - $this->assertImageHasSource( $attachment_id, 'image/webp' ); + $this->assertImageHasSource( $attachment_id, $mime_type ); $metadata = wp_get_attachment_metadata( $attachment_id ); @@ -148,7 +175,7 @@ public function test_it_should_create_jpeg_and_webp_for_jpeg_images_if_generate_ // There should be JPEG and WebP sources for all sizes. foreach ( array_keys( $metadata['sizes'] ) as $size_name ) { $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/jpeg' ); - $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/webp' ); + $this->assertImageHasSizeSource( $attachment_id, $size_name, $mime_type ); } } @@ -632,20 +659,34 @@ public function test_it_should_preserve_jpeg_subsizes_using_transform_filter(): } /** - * Allow the upload of a WebP image if at least one editor supports the format + * Allow the upload of an image if at least one editor supports the image type + * + * @dataProvider data_provider_supported_image_types + * + * @group current1 */ - public function test_it_should_allow_the_upload_of_a_webp_image_if_at_least_one_editor_supports_the_format(): void { + public function test_it_should_allow_the_upload_of_a_webp_image_if_at_least_one_editor_supports_the_format( string $image_type ): void { + $mime_type = 'image/' . $image_type; + + // Skip the test if no editors support the image type. + if ( ! webp_uploads_mime_type_supported( $mime_type ) ) { + $this->markTestSkipped( 'No editors support the image type: ' . $image_type ); + } + add_filter( 'wp_image_editors', - static function () { + static function ( $editors ) { // WP core does not choose the WP_Image_Editor instance based on MIME type support, - // therefore the one that does support WebP needs to be first in this list. - return array( 'WP_Image_Editor_GD', 'WP_Image_Doesnt_Support_WebP' ); + // therefore the one that does support modern images needs to be first in this list. + array_unshift( $editors, 'WP_Image_Doesnt_Support_Modern_Images' ); + return $editors; } ); - $this->assertTrue( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ); + $this->assertTrue( wp_image_editor_supports( array( 'mime_type' => $mime_type ) ) ); + // Ensure the output type is WebP for this test. + $this->set_image_output_type( $image_type ); $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/leaves.jpg' ); $metadata = wp_get_attachment_metadata( $attachment_id ); @@ -653,22 +694,48 @@ static function () { $this->assertIsArray( $metadata['sources'] ); $this->assertImageNotHasSource( $attachment_id, 'image/jpeg' ); - $this->assertImageHasSource( $attachment_id, 'image/webp' ); + $this->assertImageHasSource( $attachment_id, 'image/' . $image_type ); $this->assertImageNotHasSizeSource( $attachment_id, 'thumbnail', 'image/jpeg' ); - $this->assertImageHasSizeSource( $attachment_id, 'thumbnail', 'image/webp' ); + $this->assertImageHasSizeSource( $attachment_id, 'thumbnail', 'image/' . $image_type ); } /** - * Replace the featured image to WebP when requesting the featured image + * Replace the featured image to the proper type when requesting the featured image. + * + * @dataProvider data_provider_supported_image_types + * + * @param string $image_type */ - public function test_it_should_replace_the_featured_image_to_webp_when_requesting_the_featured_image(): void { + public function test_it_should_replace_the_featured_image_to_webp_when_requesting_the_featured_image( string $image_type ): void { + $mime_type = 'image/' . $image_type; + + // Skip this test if the image editor doesn't support the image type. + if ( ! webp_uploads_mime_type_supported( $mime_type ) ) { + $this->markTestSkipped( 'The image editor does not support the image type: ' . $image_type ); + } + + // Set the image output format. + $this->set_image_output_type( $image_type ); + $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/paint.jpeg' ); $post_id = self::factory()->post->create(); set_post_thumbnail( $post_id, $attachment_id ); $featured_image = get_the_post_thumbnail( $post_id ); - $this->assertMatchesRegularExpression( '//', $featured_image ); + $this->assertMatchesRegularExpression( '//', $featured_image ); + } + + /** + * Data provider for tests returns the supported image types to run the tests against. + * + * @return array> An array of valid image types. + */ + public function data_provider_supported_image_types(): array { + return array( + 'webp' => array( 'webp' ), + 'avif' => array( 'avif' ), + ); } /** @@ -812,7 +879,7 @@ public function test_it_should_add_fallback_script_if_content_has_updated_images $this->assertTrue( has_action( 'wp_footer', 'webp_uploads_wepb_fallback' ) === 10 ); $footer = get_echo( 'wp_footer' ); - $this->assertStringContainsString( 'data:image/webp;base64,UklGR', $footer ); + $this->assertStringContainsString( 'webp;base64,UklGR', wp_unslash( $footer ) ); } /** @@ -826,7 +893,7 @@ public function test_it_should_not_add_fallback_script_if_content_has_no_updated $this->assertFalse( has_action( 'wp_footer', 'webp_uploads_wepb_fallback' ) ); $footer = get_echo( 'wp_footer' ); - $this->assertStringNotContainsString( 'data:image/webp;base64,UklGR', $footer ); + $this->assertStringNotContainsString( 'webp;base64,UklGR', $footer ); } /** diff --git a/tests/utils/TestCase/ImagesTestCase.php b/tests/utils/TestCase/ImagesTestCase.php index 368e9fc40..558f813c4 100644 --- a/tests/utils/TestCase/ImagesTestCase.php +++ b/tests/utils/TestCase/ImagesTestCase.php @@ -123,4 +123,13 @@ static function ( $transforms ) { } ); } + + /** + * Simple helper to set the output format for image generation. + * + * @param string $format The format to set. + */ + public function set_image_output_type( string $format ): void { + update_option( 'perflab_modern_image_format', $format ); + } }