From ae7f22d999d1a9036068add664af27c4bf81b668 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 31 Oct 2025 02:37:17 +0530 Subject: [PATCH 01/38] Add support for AVIF transparency checks during image uploads --- plugins/webp-uploads/helper.php | 26 +++++++++++++++++ plugins/webp-uploads/hooks.php | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index a089809361..76febca267 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -374,6 +374,11 @@ function webp_uploads_mime_type_supported( string $mime_type ): bool { * @return string The image output format. One of 'webp' or 'avif'. */ function webp_uploads_get_image_output_format(): string { + if ( ! webp_uploads_imagick_avif_transparency_supported() && (bool) wp_cache_get( 'webp_uploads_image_has_transparency', 'webp-uploads' ) ) { + wp_cache_delete( 'webp_uploads_image_has_transparency', 'webp-uploads' ); + return 'webp'; + } + $image_format = get_option( 'perflab_modern_image_format' ); return webp_uploads_sanitize_image_format( $image_format ); } @@ -512,3 +517,24 @@ function webp_uploads_get_attachment_file_mime_type( int $attachment_id, string $mime_type = $filetype['type'] ?? get_post_mime_type( $attachment_id ); return is_string( $mime_type ) ? $mime_type : ''; } + +/** + * Checks if Imagick has AVIF transparency support. + * + * @since n.e.x.t + * + * @return bool True if Imagick has AVIF transparency support, false otherwise. + */ +function webp_uploads_imagick_avif_transparency_supported(): bool { + if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { + $imagick_version = Imagick::getVersion(); + if ( (bool) preg_match( '/((?:[0-9]+\\.?)+)/', $imagick_version['versionString'], $matches ) ) { + $imagick_version = $matches[0]; + } else { + $imagick_version = $imagick_version['versionString']; + } + return version_compare( $imagick_version, '7.0.25', '>=' ); + } + + return false; +} diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 8de5eb450f..50e1388f91 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -966,3 +966,53 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { } add_filter( 'wp_handle_upload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); + +/** + * Checks if an image has transparency when uploading AVIF images with Imagick. + * + * @since n.e.x.t + * + * @param array|mixed $file The uploaded file data. + * @return array The modified file data. + */ +function webp_uploads_check_image_transparency( $file ): array { + if ( 'avif' !== webp_uploads_get_image_output_format() || webp_uploads_imagick_avif_transparency_supported() ) { + return $file; + } + + // Because plugins do bad things. + if ( ! is_array( $file ) ) { + $file = array(); + } + if ( ! isset( $file['tmp_name'], $file['name'] ) ) { + return $file; + } + if ( isset( $file['type'] ) && is_string( $file['type'] ) ) { + if ( ! str_starts_with( strtolower( $file['type'] ), 'image/' ) ) { + return $file; + } + } elseif ( ! str_starts_with( strtolower( (string) wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] )['type'] ), 'image/' ) ) { + return $file; + } + + $editor = wp_get_image_editor( $file['tmp_name'] ); + + if ( is_wp_error( $editor ) || ! $editor instanceof WP_Image_Editor_Imagick ) { + return $file; + } + + $reflection = new ReflectionClass( $editor ); + $image_property = $reflection->getProperty( 'image' ); + if ( PHP_VERSION_ID < 80100 ) { + $image_property->setAccessible( true ); + } + $imagick = $image_property->getValue( $editor ); + + if ( $imagick instanceof Imagick ) { + wp_cache_set( 'webp_uploads_image_has_transparency', (bool) $imagick->getImageAlphaChannel(), 'webp-uploads' ); + } + + return $file; +} +add_filter( 'wp_handle_upload_prefilter', 'webp_uploads_check_image_transparency' ); +add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_check_image_transparency' ); From a1aff66dd80b1198ec021dec366366da8ccff746 Mon Sep 17 00:00:00 2001 From: Aditya Dhade <76063440+b1ink0@users.noreply.github.com> Date: Mon, 3 Nov 2025 02:04:16 +0530 Subject: [PATCH 02/38] Improve Imagick version string regex validation Co-authored-by: Weston Ruter --- plugins/webp-uploads/helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 76febca267..d7b26fe49a 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -528,7 +528,7 @@ function webp_uploads_get_attachment_file_mime_type( int $attachment_id, string function webp_uploads_imagick_avif_transparency_supported(): bool { if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { $imagick_version = Imagick::getVersion(); - if ( (bool) preg_match( '/((?:[0-9]+\\.?)+)/', $imagick_version['versionString'], $matches ) ) { + if ( (bool) preg_match( '/^\d+(?:\.\d+)+$/', $imagick_version['versionString'], $matches ) ) { $imagick_version = $matches[0]; } else { $imagick_version = $imagick_version['versionString']; From 3fe6954a754632803cd46058bbd8860431824641 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 14 Nov 2025 17:47:28 +0530 Subject: [PATCH 03/38] Add site health checks for Imagick AVIF transparency support --- .../helper.php | 72 +++++++++++++++++++ .../hooks.php | 30 ++++++++ .../includes/site-health/load.php | 4 ++ plugins/webp-uploads/helper.php | 2 +- 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/helper.php create mode 100644 plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/hooks.php diff --git a/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/helper.php b/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/helper.php new file mode 100644 index 0000000000..842689c779 --- /dev/null +++ b/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/helper.php @@ -0,0 +1,72 @@ + __( 'Your site supports AVIF image format transparency with ImageMagick', 'performance-lab' ), + 'status' => 'good', + 'badge' => array( + 'label' => __( 'Performance', 'performance-lab' ), + 'color' => 'blue', + ), + 'description' => sprintf( + '

%s

', + __( 'Older versions of ImageMagick do not support transparency in AVIF images, which can result in loss of transparency when uploading AVIF files.', 'performance-lab' ) + ), + 'actions' => sprintf( + '

%s

', + __( 'Your ImageMagick installation supports AVIF transparency.', 'performance-lab' ) + ), + 'test' => 'is_imagick_avif_transparency_supported_enabled', + ); + + if ( ! perflab_imagick_avif_transparency_supported() ) { + $result['status'] = 'recommended'; + $result['label'] = __( 'Your site does not support AVIF transparency', 'performance-lab' ); + $result['actions'] = sprintf( + '

%s

', + __( 'Update ImageMagick to the latest version by contacting your hosting provider.', 'performance-lab' ) + ); + } + + return $result; +} + +/** + * Checks if Imagick has AVIF transparency support. + * + * @since n.e.x.t + * + * @return bool True if Imagick has AVIF transparency support, false otherwise. + */ +function perflab_imagick_avif_transparency_supported(): bool { + if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { + $imagick_version = Imagick::getVersion(); + if ( (bool) preg_match( '/\d+(?:\.\d+)+(?:-\d+)?/', $imagick_version['versionString'], $matches ) ) { + $imagick_version = $matches[0]; + } else { + $imagick_version = $imagick_version['versionString']; + } + return version_compare( $imagick_version, '7.0.25', '>=' ); + } + + return false; +} diff --git a/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/hooks.php b/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/hooks.php new file mode 100644 index 0000000000..7d1d3c7896 --- /dev/null +++ b/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/hooks.php @@ -0,0 +1,30 @@ +} $tests Site Health Tests. + * @return array{direct: array} Amended tests. + */ +function perflab_add_imagick_avif_transparency_supported_test( array $tests ): array { + $tests['direct']['imagick_avif_transparency_supported'] = array( + 'label' => __( 'Imagick AVIF Transparency Support', 'performance-lab' ), + 'test' => 'perflab_imagick_avif_transparency_supported_test', + ); + return $tests; +} +add_filter( 'site_status_tests', 'perflab_add_imagick_avif_transparency_supported_test' ); diff --git a/plugins/performance-lab/includes/site-health/load.php b/plugins/performance-lab/includes/site-health/load.php index 4563d606ab..d6cae4be64 100644 --- a/plugins/performance-lab/includes/site-health/load.php +++ b/plugins/performance-lab/includes/site-health/load.php @@ -39,3 +39,7 @@ // Cache-Control headers site health check. require_once __DIR__ . '/bfcache-compatibility-headers/helper.php'; require_once __DIR__ . '/bfcache-compatibility-headers/hooks.php'; + +// Imagick AVIF transparency support site health check. +require_once __DIR__ . '/imagick-avif-transparency-support/helper.php'; +require_once __DIR__ . '/imagick-avif-transparency-support/hooks.php'; diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index d7b26fe49a..1ff4446663 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -528,7 +528,7 @@ function webp_uploads_get_attachment_file_mime_type( int $attachment_id, string function webp_uploads_imagick_avif_transparency_supported(): bool { if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { $imagick_version = Imagick::getVersion(); - if ( (bool) preg_match( '/^\d+(?:\.\d+)+$/', $imagick_version['versionString'], $matches ) ) { + if ( (bool) preg_match( '/\d+(?:\.\d+)+(?:-\d+)?/', $imagick_version['versionString'], $matches ) ) { $imagick_version = $matches[0]; } else { $imagick_version = $imagick_version['versionString']; From 2247b614e4e895f4c3b880644a3ce0c364644bc0 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Wed, 17 Dec 2025 19:23:33 +0530 Subject: [PATCH 04/38] Add custom image editor class to help with AVIF transparency detection --- ...lass-webp-uploads-image-editor-imagick.php | 101 ++++++++++++++++++ plugins/webp-uploads/helper.php | 60 +++++++++-- plugins/webp-uploads/hooks.php | 53 +++------ plugins/webp-uploads/image-edit.php | 2 +- plugins/webp-uploads/tests/test-helper.php | 10 +- 5 files changed, 174 insertions(+), 52 deletions(-) create mode 100644 plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php new file mode 100644 index 0000000000..807b0f1352 --- /dev/null +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -0,0 +1,101 @@ +file; + } + + /** + * Looks for transparent pixels in the image. + * If there are none, it returns false. + * + * @since n.e.x.t + * + * @return bool|WP_Error True or false based on whether there are transparent pixels, or an error on failure. + */ + public function has_transparency() { + + if ( ! (bool) $this->image ) { + return new WP_Error( 'image_editor_has_transparency_error_no_image', __( 'Transparency detection no image found.', 'webp-uploads' ) ); + } + + try { + /* + * Check if the image has an alpha channel if false, then it can't have transparency so return early. + * + * Note that Imagick::getImageAlphaChannel() is only available if Imagick + * has been compiled against ImageMagick version 6.4.0 or newer. + */ + if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) ) ) { + if ( ! $this->image->getImageAlphaChannel() ) { + return false; + } + } + + // Walk through the pixels and look transparent pixels. + $w = $this->image->getImageWidth(); + $h = $this->image->getImageHeight(); + for ( $x = 0; $x < $w; $x++ ) { + for ( $y = 0; $y < $h; $y++ ) { + $pixel = $this->image->getImagePixelColor( $x, $y ); + $color = $pixel->getColor( 2 ); + if ( $color['a'] > 0 ) { + return true; + } + } + } + return false; + + } catch ( Exception $e ) { + /* translators: %s is the error message */ + return new WP_Error( 'image_editor_has_transparency_error', sprintf( __( 'Transparency detection failed: %s', 'webp-uploads' ), $e->getMessage() ) ); + } + } +} diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 1ff4446663..e4eddb912c 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -21,12 +21,18 @@ * @since 2.0.0 Added support for AVIF. * @since 2.2.0 Added support for PNG. * + * @param string|null $filename Optional. The filename. Default empty string. * @return array> 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 { +function webp_uploads_get_upload_image_mime_transforms( ?string $filename ): array { + $avif_support = webp_uploads_mime_type_supported( 'image/avif' ); + + if ( $avif_support && webp_uploads_check_image_transparency( $filename ) ) { + $avif_support = false; + } // Check the selected output format. - $output_format = webp_uploads_mime_type_supported( 'image/avif' ) ? webp_uploads_get_image_output_format() : 'webp'; + $output_format = $avif_support ? webp_uploads_get_image_output_format() : 'webp'; $default_transforms = array( 'image/jpeg' => array( 'image/' . $output_format ), @@ -374,11 +380,6 @@ function webp_uploads_mime_type_supported( string $mime_type ): bool { * @return string The image output format. One of 'webp' or 'avif'. */ function webp_uploads_get_image_output_format(): string { - if ( ! webp_uploads_imagick_avif_transparency_supported() && (bool) wp_cache_get( 'webp_uploads_image_has_transparency', 'webp-uploads' ) ) { - wp_cache_delete( 'webp_uploads_image_has_transparency', 'webp-uploads' ); - return 'webp'; - } - $image_format = get_option( 'perflab_modern_image_format' ); return webp_uploads_sanitize_image_format( $image_format ); } @@ -538,3 +539,48 @@ function webp_uploads_imagick_avif_transparency_supported(): bool { return false; } + +/** + * Checks if an AVIF image has transparency + * + * @since n.e.x.t + * + * @param string|null $filename The uploaded file name. + * @return bool Whether the image has transparency. + */ +function webp_uploads_check_image_transparency( ?string $filename ): bool { + static $processed_images = array(); + + if ( 'avif' !== webp_uploads_get_image_output_format() || webp_uploads_imagick_avif_transparency_supported() || ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + return false; + } + + if ( null === $filename ) { + $file = WebP_Uploads_Image_Editor_Imagick::$current_instance->get_file(); + if ( '' === $file ) { + return false; + } + $filename = $file; + } + + if ( ! is_string( $filename ) || ! file_exists( $filename ) ) { + return false; + } + + $file_hash = md5_file( $filename ); + if ( isset( $processed_images[ $file_hash ] ) ) { + return $processed_images[ $file_hash ]; + } + $processed_images[ $file_hash ] = false; + + $editor = wp_get_image_editor( $filename ); + + if ( is_wp_error( $editor ) || ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { + return false; + } + + $has_transparency = $editor->has_transparency(); + $processed_images[ $file_hash ] = is_wp_error( $has_transparency ) ? false : $has_transparency; + + return $processed_images[ $file_hash ]; +} diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 50e1388f91..3f900f64a2 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -65,8 +65,7 @@ function webp_uploads_create_sources_property( array $metadata, int $attachment_ return $metadata; } - $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms(); - + $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms( $file ); // Not a supported mime type to create the sources property. if ( ! isset( $valid_mime_transforms[ $mime_type ] ) ) { return $metadata; @@ -334,7 +333,7 @@ function webp_uploads_filter_image_editor_output_format( $output_format, ?string } // Use the original mime type if this type is allowed. - $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms(); + $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms( $filename ); if ( ! isset( $valid_mime_transforms[ $mime_type ] ) || in_array( $mime_type, $valid_mime_transforms[ $mime_type ], true ) @@ -968,51 +967,27 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); /** - * Checks if an image has transparency when uploading AVIF images with Imagick. + * Overloads wp_image_editors() to load the extended class. * * @since n.e.x.t * - * @param array|mixed $file The uploaded file data. - * @return array The modified file data. + * @param string[] $editors Array of available image editor class names. Defaults are 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'. + * @return string[] Registered image editors class names. */ -function webp_uploads_check_image_transparency( $file ): array { +function webp_uploads_set_image_editors( array $editors ): array { if ( 'avif' !== webp_uploads_get_image_output_format() || webp_uploads_imagick_avif_transparency_supported() ) { - return $file; + return $editors; } - // Because plugins do bad things. - if ( ! is_array( $file ) ) { - $file = array(); - } - if ( ! isset( $file['tmp_name'], $file['name'] ) ) { - return $file; - } - if ( isset( $file['type'] ) && is_string( $file['type'] ) ) { - if ( ! str_starts_with( strtolower( $file['type'] ), 'image/' ) ) { - return $file; - } - } elseif ( ! str_starts_with( strtolower( (string) wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] )['type'] ), 'image/' ) ) { - return $file; + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php';// @codeCoverageIgnore } - $editor = wp_get_image_editor( $file['tmp_name'] ); - - if ( is_wp_error( $editor ) || ! $editor instanceof WP_Image_Editor_Imagick ) { - return $file; + $key = array_search( WP_Image_Editor_Imagick::class, $editors, true ); + if ( false !== $key ) { + $editors[ $key ] = WebP_Uploads_Image_Editor_Imagick::class; } - $reflection = new ReflectionClass( $editor ); - $image_property = $reflection->getProperty( 'image' ); - if ( PHP_VERSION_ID < 80100 ) { - $image_property->setAccessible( true ); - } - $imagick = $image_property->getValue( $editor ); - - if ( $imagick instanceof Imagick ) { - wp_cache_set( 'webp_uploads_image_has_transparency', (bool) $imagick->getImageAlphaChannel(), 'webp-uploads' ); - } - - return $file; + return $editors; } -add_filter( 'wp_handle_upload_prefilter', 'webp_uploads_check_image_transparency' ); -add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_check_image_transparency' ); +add_filter( 'wp_image_editors', 'webp_uploads_set_image_editors' ); diff --git a/plugins/webp-uploads/image-edit.php b/plugins/webp-uploads/image-edit.php index d1e782a187..7f4c174081 100644 --- a/plugins/webp-uploads/image-edit.php +++ b/plugins/webp-uploads/image-edit.php @@ -127,7 +127,7 @@ function webp_uploads_update_image_onchange( $override, string $file_path, WP_Im return (bool) $override; } - $transforms = webp_uploads_get_upload_image_mime_transforms(); + $transforms = webp_uploads_get_upload_image_mime_transforms( $file_path ); if ( ! isset( $transforms[ $mime_type ] ) || ! is_array( $transforms[ $mime_type ] ) || 0 === count( $transforms[ $mime_type ] ) ) { return null; } diff --git a/plugins/webp-uploads/tests/test-helper.php b/plugins/webp-uploads/tests/test-helper.php index 0eaa68e74a..f47a8f751f 100644 --- a/plugins/webp-uploads/tests/test-helper.php +++ b/plugins/webp-uploads/tests/test-helper.php @@ -400,7 +400,7 @@ static function ( $passthrough, $orig_w, $orig_h, $dest_w, $dest_h, $crop ) use public function test_it_should_return_empty_array_when_filter_returns_empty_array(): void { add_filter( 'webp_uploads_upload_image_mime_transforms', '__return_empty_array' ); - $transforms = webp_uploads_get_upload_image_mime_transforms(); + $transforms = webp_uploads_get_upload_image_mime_transforms( null ); $this->assertSame( array(), $transforms ); } @@ -428,7 +428,7 @@ public function test_it_should_return_default_transforms_when_filter_returns_non ); } - $transforms = webp_uploads_get_upload_image_mime_transforms(); + $transforms = webp_uploads_get_upload_image_mime_transforms( null ); $this->assertSame( $default_transforms, $transforms ); } @@ -444,7 +444,7 @@ static function () { } ); - $transforms = webp_uploads_get_upload_image_mime_transforms(); + $transforms = webp_uploads_get_upload_image_mime_transforms( null ); $this->assertSame( array( 'image/jpeg' => array( 'image/jpeg' ) ), $transforms ); } @@ -460,7 +460,7 @@ static function () { } ); - $transforms = webp_uploads_get_upload_image_mime_transforms(); + $transforms = webp_uploads_get_upload_image_mime_transforms( null ); $this->assertSame( array( 'image/jpeg' => array( 'image/jpeg', 'image/webp' ) ), $transforms ); } @@ -476,7 +476,7 @@ public function test_it_should_return_jpeg_and_webp_transforms_when_option_gener } update_option( 'perflab_generate_webp_and_jpeg', true ); - $transforms = webp_uploads_get_upload_image_mime_transforms(); + $transforms = webp_uploads_get_upload_image_mime_transforms( null ); // The returned value depends on whether the server supports AVIF. if ( webp_uploads_mime_type_supported( 'image/avif' ) ) { From 1a58d1c1a3be15152e6ef1fde6bf58b94a440028 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 26 Dec 2025 18:26:59 +0530 Subject: [PATCH 05/38] Move Imagick AVIF transparency support site health test --- .../includes/site-health/load.php | 4 -- plugins/webp-uploads/load.php | 1 + .../helper.php | 41 +++++-------------- .../hooks.php | 10 ++--- plugins/webp-uploads/site-health/load.php | 17 ++++++++ 5 files changed, 33 insertions(+), 40 deletions(-) rename plugins/{performance-lab/includes => webp-uploads}/site-health/imagick-avif-transparency-support/helper.php (52%) rename plugins/{performance-lab/includes => webp-uploads}/site-health/imagick-avif-transparency-support/hooks.php (61%) create mode 100644 plugins/webp-uploads/site-health/load.php diff --git a/plugins/performance-lab/includes/site-health/load.php b/plugins/performance-lab/includes/site-health/load.php index d6cae4be64..4563d606ab 100644 --- a/plugins/performance-lab/includes/site-health/load.php +++ b/plugins/performance-lab/includes/site-health/load.php @@ -39,7 +39,3 @@ // Cache-Control headers site health check. require_once __DIR__ . '/bfcache-compatibility-headers/helper.php'; require_once __DIR__ . '/bfcache-compatibility-headers/hooks.php'; - -// Imagick AVIF transparency support site health check. -require_once __DIR__ . '/imagick-avif-transparency-support/helper.php'; -require_once __DIR__ . '/imagick-avif-transparency-support/hooks.php'; diff --git a/plugins/webp-uploads/load.php b/plugins/webp-uploads/load.php index 7c522e38e6..802374d747 100644 --- a/plugins/webp-uploads/load.php +++ b/plugins/webp-uploads/load.php @@ -36,3 +36,4 @@ require_once __DIR__ . '/picture-element.php'; require_once __DIR__ . '/hooks.php'; require_once __DIR__ . '/deprecated.php'; +require_once __DIR__ . '/site-health/load.php'; diff --git a/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/helper.php b/plugins/webp-uploads/site-health/imagick-avif-transparency-support/helper.php similarity index 52% rename from plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/helper.php rename to plugins/webp-uploads/site-health/imagick-avif-transparency-support/helper.php index 842689c779..e4f6b67b80 100644 --- a/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/helper.php +++ b/plugins/webp-uploads/site-health/imagick-avif-transparency-support/helper.php @@ -2,7 +2,7 @@ /** * Helper functions used for checking Imagick AVIF transparency support. * - * @package performance-lab + * @package webp-uploads * @since n.e.x.t */ @@ -13,60 +13,39 @@ // @codeCoverageIgnoreEnd /** - * Callback for webp_enabled test. + * Callback for Imagick AVIF transparency support test. * * @since n.e.x.t * * @return array{label: string, status: string, badge: array{label: string, color: string}, description: string, actions: string, test: string} Result. */ -function perflab_imagick_avif_transparency_supported_test(): array { +function webp_uploads_imagick_avif_transparency_supported_test(): array { $result = array( - 'label' => __( 'Your site supports AVIF image format transparency with ImageMagick', 'performance-lab' ), + 'label' => __( 'Your site supports AVIF image format transparency with ImageMagick', 'webp-uploads' ), 'status' => 'good', 'badge' => array( - 'label' => __( 'Performance', 'performance-lab' ), + 'label' => __( 'Performance', 'webp-uploads' ), 'color' => 'blue', ), 'description' => sprintf( '

%s

', - __( 'Older versions of ImageMagick do not support transparency in AVIF images, which can result in loss of transparency when uploading AVIF files.', 'performance-lab' ) + __( 'Older versions of ImageMagick do not support transparency in AVIF images, which can result in loss of transparency when uploading AVIF files.', 'webp-uploads' ) ), 'actions' => sprintf( '

%s

', - __( 'Your ImageMagick installation supports AVIF transparency.', 'performance-lab' ) + __( 'Your ImageMagick installation supports AVIF transparency.', 'webp-uploads' ) ), 'test' => 'is_imagick_avif_transparency_supported_enabled', ); - if ( ! perflab_imagick_avif_transparency_supported() ) { + if ( ! webp_uploads_imagick_avif_transparency_supported() ) { $result['status'] = 'recommended'; - $result['label'] = __( 'Your site does not support AVIF transparency', 'performance-lab' ); + $result['label'] = __( 'Your site does not support AVIF transparency', 'webp-uploads' ); $result['actions'] = sprintf( '

%s

', - __( 'Update ImageMagick to the latest version by contacting your hosting provider.', 'performance-lab' ) + __( 'Update ImageMagick to the latest version by contacting your hosting provider.', 'webp-uploads' ) ); } return $result; } - -/** - * Checks if Imagick has AVIF transparency support. - * - * @since n.e.x.t - * - * @return bool True if Imagick has AVIF transparency support, false otherwise. - */ -function perflab_imagick_avif_transparency_supported(): bool { - if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { - $imagick_version = Imagick::getVersion(); - if ( (bool) preg_match( '/\d+(?:\.\d+)+(?:-\d+)?/', $imagick_version['versionString'], $matches ) ) { - $imagick_version = $matches[0]; - } else { - $imagick_version = $imagick_version['versionString']; - } - return version_compare( $imagick_version, '7.0.25', '>=' ); - } - - return false; -} diff --git a/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/hooks.php b/plugins/webp-uploads/site-health/imagick-avif-transparency-support/hooks.php similarity index 61% rename from plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/hooks.php rename to plugins/webp-uploads/site-health/imagick-avif-transparency-support/hooks.php index 7d1d3c7896..08a6b31052 100644 --- a/plugins/performance-lab/includes/site-health/imagick-avif-transparency-support/hooks.php +++ b/plugins/webp-uploads/site-health/imagick-avif-transparency-support/hooks.php @@ -2,7 +2,7 @@ /** * Hook callbacks used for checking Imagick AVIF transparency support. * - * @package performance-lab + * @package webp-uploads * @since n.e.x.t */ @@ -20,11 +20,11 @@ * @param array{direct: array} $tests Site Health Tests. * @return array{direct: array} Amended tests. */ -function perflab_add_imagick_avif_transparency_supported_test( array $tests ): array { +function webp_uploads_add_imagick_avif_transparency_supported_test( array $tests ): array { $tests['direct']['imagick_avif_transparency_supported'] = array( - 'label' => __( 'Imagick AVIF Transparency Support', 'performance-lab' ), - 'test' => 'perflab_imagick_avif_transparency_supported_test', + 'label' => __( 'Imagick AVIF Transparency Support', 'webp-uploads' ), + 'test' => 'webp_uploads_imagick_avif_transparency_supported_test', ); return $tests; } -add_filter( 'site_status_tests', 'perflab_add_imagick_avif_transparency_supported_test' ); +add_filter( 'site_status_tests', 'webp_uploads_add_imagick_avif_transparency_supported_test' ); diff --git a/plugins/webp-uploads/site-health/load.php b/plugins/webp-uploads/site-health/load.php new file mode 100644 index 0000000000..2687d8c6f6 --- /dev/null +++ b/plugins/webp-uploads/site-health/load.php @@ -0,0 +1,17 @@ + Date: Wed, 14 Jan 2026 02:45:07 +0530 Subject: [PATCH 06/38] Allow `webp_uploads_get_upload_image_mime_transforms()` to omit the filename parameter by defaulting it to null Co-authored-by: Weston Ruter --- plugins/webp-uploads/helper.php | 4 ++-- plugins/webp-uploads/tests/test-helper.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 1e851343e3..4ba4fb59c7 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -21,10 +21,10 @@ * @since 2.0.0 Added support for AVIF. * @since 2.2.0 Added support for PNG. * - * @param string|null $filename Optional. The filename. Default empty string. + * @param string|null $filename Optional. The filename. Default null. * @return array> 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( ?string $filename ): array { +function webp_uploads_get_upload_image_mime_transforms( ?string $filename = null ): array { $avif_support = webp_uploads_mime_type_supported( 'image/avif' ); if ( $avif_support && webp_uploads_check_image_transparency( $filename ) ) { diff --git a/plugins/webp-uploads/tests/test-helper.php b/plugins/webp-uploads/tests/test-helper.php index f47a8f751f..0eaa68e74a 100644 --- a/plugins/webp-uploads/tests/test-helper.php +++ b/plugins/webp-uploads/tests/test-helper.php @@ -400,7 +400,7 @@ static function ( $passthrough, $orig_w, $orig_h, $dest_w, $dest_h, $crop ) use public function test_it_should_return_empty_array_when_filter_returns_empty_array(): void { add_filter( 'webp_uploads_upload_image_mime_transforms', '__return_empty_array' ); - $transforms = webp_uploads_get_upload_image_mime_transforms( null ); + $transforms = webp_uploads_get_upload_image_mime_transforms(); $this->assertSame( array(), $transforms ); } @@ -428,7 +428,7 @@ public function test_it_should_return_default_transforms_when_filter_returns_non ); } - $transforms = webp_uploads_get_upload_image_mime_transforms( null ); + $transforms = webp_uploads_get_upload_image_mime_transforms(); $this->assertSame( $default_transforms, $transforms ); } @@ -444,7 +444,7 @@ static function () { } ); - $transforms = webp_uploads_get_upload_image_mime_transforms( null ); + $transforms = webp_uploads_get_upload_image_mime_transforms(); $this->assertSame( array( 'image/jpeg' => array( 'image/jpeg' ) ), $transforms ); } @@ -460,7 +460,7 @@ static function () { } ); - $transforms = webp_uploads_get_upload_image_mime_transforms( null ); + $transforms = webp_uploads_get_upload_image_mime_transforms(); $this->assertSame( array( 'image/jpeg' => array( 'image/jpeg', 'image/webp' ) ), $transforms ); } @@ -476,7 +476,7 @@ public function test_it_should_return_jpeg_and_webp_transforms_when_option_gener } update_option( 'perflab_generate_webp_and_jpeg', true ); - $transforms = webp_uploads_get_upload_image_mime_transforms( null ); + $transforms = webp_uploads_get_upload_image_mime_transforms(); // The returned value depends on whether the server supports AVIF. if ( webp_uploads_mime_type_supported( 'image/avif' ) ) { From 991d7c9a023f6ba13af434747614916f49458e42 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 16 Jan 2026 00:06:48 +0530 Subject: [PATCH 07/38] Add explicit return value check for `getImageAlphaChannel` method Co-authored-by: Adam Silverstein --- .../webp-uploads/class-webp-uploads-image-editor-imagick.php | 2 +- plugins/webp-uploads/hooks.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 807b0f1352..506ddd1264 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -74,7 +74,7 @@ public function has_transparency() { * has been compiled against ImageMagick version 6.4.0 or newer. */ if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) ) ) { - if ( ! $this->image->getImageAlphaChannel() ) { + if ( Imagick::ALPHACHANNEL_UNDEFINED === $this->image->getImageAlphaChannel() ) { return false; } } diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 98a7e5655d..913cc548da 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -984,7 +984,7 @@ function webp_uploads_set_image_editors( array $editors ): array { } if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php';// @codeCoverageIgnore + require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php'; // @codeCoverageIgnore } $key = array_search( WP_Image_Editor_Imagick::class, $editors, true ); From fece7412708b398a01f3d254127da76d4c3f34d5 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 20 Jan 2026 21:58:21 +0530 Subject: [PATCH 08/38] Add transparency check caching to custom image editor class Co-authored-by: Adam Silverstein --- .../class-webp-uploads-image-editor-imagick.php | 17 +++++++++++++++++ plugins/webp-uploads/hooks.php | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 506ddd1264..27982faa0e 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -26,6 +26,15 @@ class WebP_Uploads_Image_Editor_Imagick extends WP_Image_Editor_Imagick { */ public static $current_instance; + /** + * Stores already checked images for transparency. + * + * @since n.e.x.t + * + * @var array Associative array with file paths as keys and transparency detection results as values. + */ + private static $checked_images = array(); + /** * Load the image and set the current instance. * @@ -66,6 +75,11 @@ public function has_transparency() { return new WP_Error( 'image_editor_has_transparency_error_no_image', __( 'Transparency detection no image found.', 'webp-uploads' ) ); } + $file_path = $this->get_file(); + if ( isset( self::$checked_images[ $file_path ] ) ) { + return self::$checked_images[ $file_path ]; + } + try { /* * Check if the image has an alpha channel if false, then it can't have transparency so return early. @@ -75,6 +89,7 @@ public function has_transparency() { */ if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) ) ) { if ( Imagick::ALPHACHANNEL_UNDEFINED === $this->image->getImageAlphaChannel() ) { + self::$checked_images[ $file_path ] = false; return false; } } @@ -87,10 +102,12 @@ public function has_transparency() { $pixel = $this->image->getImagePixelColor( $x, $y ); $color = $pixel->getColor( 2 ); if ( $color['a'] > 0 ) { + self::$checked_images[ $file_path ] = true; return true; } } } + self::$checked_images[ $file_path ] = false; return false; } catch ( Exception $e ) { diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 913cc548da..4fbb8d1efd 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -971,7 +971,7 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); /** - * Overloads wp_image_editors() to load the extended class. + * Overloads wp_image_editors() to load the extended class when AVIF transparency is not supported. * * @since n.e.x.t * From 435f5ffa11feedc2eb415a5755d2f68d47702560 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 20 Jan 2026 22:17:22 +0530 Subject: [PATCH 09/38] Refactor to use filename instead of file hash for transparency check --- plugins/webp-uploads/helper.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 4ba4fb59c7..fce1aad7eb 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -567,11 +567,10 @@ function webp_uploads_check_image_transparency( ?string $filename ): bool { return false; } - $file_hash = md5_file( $filename ); - if ( isset( $processed_images[ $file_hash ] ) ) { - return $processed_images[ $file_hash ]; + if ( isset( $processed_images[ $filename ] ) ) { + return $processed_images[ $filename ]; } - $processed_images[ $file_hash ] = false; + $processed_images[ $filename ] = false; $editor = wp_get_image_editor( $filename ); @@ -579,8 +578,8 @@ function webp_uploads_check_image_transparency( ?string $filename ): bool { return false; } - $has_transparency = $editor->has_transparency(); - $processed_images[ $file_hash ] = is_wp_error( $has_transparency ) ? false : $has_transparency; + $has_transparency = $editor->has_transparency(); + $processed_images[ $filename ] = is_wp_error( $has_transparency ) ? false : $has_transparency; - return $processed_images[ $file_hash ]; + return $processed_images[ $filename ]; } From 4da17584772f55713b813695dd977af9818b7433 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 20 Jan 2026 22:18:17 +0530 Subject: [PATCH 10/38] Fix transparency detection to exclude fully opaque pixels --- .../webp-uploads/class-webp-uploads-image-editor-imagick.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 27982faa0e..84f0bfb8b7 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -101,7 +101,7 @@ public function has_transparency() { for ( $y = 0; $y < $h; $y++ ) { $pixel = $this->image->getImagePixelColor( $x, $y ); $color = $pixel->getColor( 2 ); - if ( $color['a'] > 0 ) { + if ( $color['a'] > 0 && $color['a'] < 255 ) { self::$checked_images[ $file_path ] = true; return true; } From 717e7688e7f2aaf6d84c5d21cd655d1a08952dca Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Wed, 21 Jan 2026 23:26:20 +0530 Subject: [PATCH 11/38] Fix image editor confilict with Dominant Color Images Co-authored-by: Weston Ruter --- .../webp-uploads/class-webp-uploads-image-editor-imagick.php | 4 ++-- plugins/webp-uploads/helper.php | 3 +++ plugins/webp-uploads/hooks.php | 5 ++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 84f0bfb8b7..eec8d1fdee 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -22,9 +22,9 @@ class WebP_Uploads_Image_Editor_Imagick extends WP_Image_Editor_Imagick { * * @since n.e.x.t * - * @var WebP_Uploads_Image_Editor_Imagick + * @var WebP_Uploads_Image_Editor_Imagick|null $current_instance The current instance. */ - public static $current_instance; + public static $current_instance = null; /** * Stores already checked images for transparency. diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index fce1aad7eb..305f313f92 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -556,6 +556,9 @@ function webp_uploads_check_image_transparency( ?string $filename ): bool { } if ( null === $filename ) { + if ( null === WebP_Uploads_Image_Editor_Imagick::$current_instance ) { + return false; + } $file = WebP_Uploads_Image_Editor_Imagick::$current_instance->get_file(); if ( '' === $file ) { return false; diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 4fbb8d1efd..26d9d16c40 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -987,9 +987,8 @@ function webp_uploads_set_image_editors( array $editors ): array { require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php'; // @codeCoverageIgnore } - $key = array_search( WP_Image_Editor_Imagick::class, $editors, true ); - if ( false !== $key ) { - $editors[ $key ] = WebP_Uploads_Image_Editor_Imagick::class; + if ( false !== array_search( WP_Image_Editor_Imagick::class, $editors, true ) ) { + array_unshift( $editors, WebP_Uploads_Image_Editor_Imagick::class ); } return $editors; From c033c44526d041eb90e98fa3921befa649484925 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 22 Jan 2026 00:35:38 +0530 Subject: [PATCH 12/38] Dynimacally extend the image editor class --- ...lass-webp-uploads-image-editor-imagick.php | 194 ++++++++++-------- plugins/webp-uploads/hooks.php | 28 ++- 2 files changed, 132 insertions(+), 90 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index eec8d1fdee..7c267c39ee 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -8,111 +8,131 @@ * @since n.e.x.t */ -/** - * WordPress Image Editor Class for Image Manipulation through Imagick - * for transparency detection. - * - * @since n.e.x.t - * - * @see WP_Image_Editor - */ -class WebP_Uploads_Image_Editor_Imagick extends WP_Image_Editor_Imagick { - /** - * The current instance of the image editor. - * - * @since n.e.x.t - * - * @var WebP_Uploads_Image_Editor_Imagick|null $current_instance The current instance. - */ - public static $current_instance = null; +// @codeCoverageIgnoreStart +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} +// @codeCoverageIgnoreEnd - /** - * Stores already checked images for transparency. - * - * @since n.e.x.t - * - * @var array Associative array with file paths as keys and transparency detection results as values. - */ - private static $checked_images = array(); +if ( class_exists( 'WebP_Uploads_Image_Editor_Imagick_Base' ) ) { /** - * Load the image and set the current instance. + * WordPress Image Editor Class for Image Manipulation through Imagick + * for transparency detection. * * @since n.e.x.t * - * @return WP_Error|true True on success, WP_Error on failure. + * @see WP_Image_Editor */ - public function load() { - $result = parent::load(); - if ( ! is_wp_error( $result ) ) { - self::$current_instance = $this; - } - return $result; - } + class WebP_Uploads_Image_Editor_Imagick extends WebP_Uploads_Image_Editor_Imagick_Base { + /** + * The current instance of the image editor. + * + * @since n.e.x.t + * + * @var WebP_Uploads_Image_Editor_Imagick|null $current_instance The current instance. + */ + public static $current_instance = null; - /** - * Get the file path of the image. - * - * @since n.e.x.t - * - * @return string The file path of the image. - */ - public function get_file(): string { - return $this->file; - } + /** + * Stores already checked images for transparency. + * + * @since n.e.x.t + * + * @var array Associative array with file paths as keys and transparency detection results as values. + */ + private static $checked_images = array(); - /** - * Looks for transparent pixels in the image. - * If there are none, it returns false. - * - * @since n.e.x.t - * - * @return bool|WP_Error True or false based on whether there are transparent pixels, or an error on failure. - */ - public function has_transparency() { - - if ( ! (bool) $this->image ) { - return new WP_Error( 'image_editor_has_transparency_error_no_image', __( 'Transparency detection no image found.', 'webp-uploads' ) ); + /** + * Load the image and set the current instance. + * + * @since n.e.x.t + * + * @return WP_Error|true True on success, WP_Error on failure. + */ + public function load() { + // @phpstan-ignore-next-line -- Parent class is created via class_alias at runtime. + $result = parent::load(); + if ( ! is_wp_error( $result ) ) { + self::$current_instance = $this; + } + return $result; } - $file_path = $this->get_file(); - if ( isset( self::$checked_images[ $file_path ] ) ) { - return self::$checked_images[ $file_path ]; + /** + * Get the file path of the image. + * + * @since n.e.x.t + * + * @return string The file path of the image. + */ + public function get_file(): string { + if ( property_exists( $this, 'file' ) && is_string( $this->file ) ) { + return $this->file; + } + return ''; } - try { - /* - * Check if the image has an alpha channel if false, then it can't have transparency so return early. - * - * Note that Imagick::getImageAlphaChannel() is only available if Imagick - * has been compiled against ImageMagick version 6.4.0 or newer. - */ - if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) ) ) { - if ( Imagick::ALPHACHANNEL_UNDEFINED === $this->image->getImageAlphaChannel() ) { - self::$checked_images[ $file_path ] = false; - return false; - } + /** + * Looks for transparent pixels in the image. + * If there are none, it returns false. + * + * @since n.e.x.t + * + * @return bool|WP_Error True or false based on whether there are transparent pixels, or an error on failure. + */ + public function has_transparency() { + if ( ! property_exists( $this, 'image' ) ) { + return false; } - // Walk through the pixels and look transparent pixels. - $w = $this->image->getImageWidth(); - $h = $this->image->getImageHeight(); - for ( $x = 0; $x < $w; $x++ ) { - for ( $y = 0; $y < $h; $y++ ) { - $pixel = $this->image->getImagePixelColor( $x, $y ); - $color = $pixel->getColor( 2 ); - if ( $color['a'] > 0 && $color['a'] < 255 ) { - self::$checked_images[ $file_path ] = true; - return true; + if ( ! (bool) $this->image ) { + return new WP_Error( 'image_editor_has_transparency_error_no_image', __( 'Transparency detection no image found.', 'webp-uploads' ) ); + } + + $file_path = $this->get_file(); + if ( isset( self::$checked_images[ $file_path ] ) ) { + return self::$checked_images[ $file_path ]; + } + + if ( ! $this->image instanceof Imagick ) { + return false; + } + + try { + /* + * Check if the image has an alpha channel if false, then it can't have transparency so return early. + * + * Note that Imagick::getImageAlphaChannel() is only available if Imagick + * has been compiled against ImageMagick version 6.4.0 or newer. + */ + if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) ) ) { + if ( Imagick::ALPHACHANNEL_UNDEFINED === $this->image->getImageAlphaChannel() ) { + self::$checked_images[ $file_path ] = false; + return false; } } - } - self::$checked_images[ $file_path ] = false; - return false; - } catch ( Exception $e ) { - /* translators: %s is the error message */ - return new WP_Error( 'image_editor_has_transparency_error', sprintf( __( 'Transparency detection failed: %s', 'webp-uploads' ), $e->getMessage() ) ); + // Walk through the pixels and look transparent pixels. + $w = $this->image->getImageWidth(); + $h = $this->image->getImageHeight(); + for ( $x = 0; $x < $w; $x++ ) { + for ( $y = 0; $y < $h; $y++ ) { + $pixel = $this->image->getImagePixelColor( $x, $y ); + $color = $pixel->getColor( 2 ); + if ( $color['a'] > 0 && $color['a'] < 255 ) { + self::$checked_images[ $file_path ] = true; + return true; + } + } + } + self::$checked_images[ $file_path ] = false; + return false; + + } catch ( Exception $e ) { + /* translators: %s is the error message */ + return new WP_Error( 'image_editor_has_transparency_error', sprintf( __( 'Transparency detection failed: %s', 'webp-uploads' ), $e->getMessage() ) ); + } } } } diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 26d9d16c40..464c241b55 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -983,13 +983,35 @@ function webp_uploads_set_image_editors( array $editors ): array { return $editors; } + if ( 0 === count( $editors ) || false === array_search( WP_Image_Editor_Imagick::class, $editors, true ) ) { + return $editors; + } + + if ( ! class_exists( $editors[0] ) ) { + return $editors; + } + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick_Base' ) ) { + if ( WP_Image_Editor_Imagick::class !== $editors[0] ) { + if ( ! is_subclass_of( WP_Image_Editor_Imagick::class, $editors[0] ) ) { + return $editors; + } else { + $reflection = new ReflectionClass( $editors[0] ); + if ( $reflection->isFinal() ) { + return $editors; + } + class_alias( $editors[0], 'WebP_Uploads_Image_Editor_Imagick_Base' ); + } + } else { + class_alias( WP_Image_Editor_Imagick::class, 'WebP_Uploads_Image_Editor_Imagick_Base' ); + } + } + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php'; // @codeCoverageIgnore } - if ( false !== array_search( WP_Image_Editor_Imagick::class, $editors, true ) ) { - array_unshift( $editors, WebP_Uploads_Image_Editor_Imagick::class ); - } + array_unshift( $editors, WebP_Uploads_Image_Editor_Imagick::class ); return $editors; } From b28885e974c073e63cd9f87d8d239888c0ba8861 Mon Sep 17 00:00:00 2001 From: Aditya Dhade <76063440+b1ink0@users.noreply.github.com> Date: Thu, 22 Jan 2026 22:21:23 +0530 Subject: [PATCH 13/38] Fix parameters order in `is_subclass_of` Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/webp-uploads/hooks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 464c241b55..3bdb39bbdc 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -993,7 +993,7 @@ function webp_uploads_set_image_editors( array $editors ): array { if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick_Base' ) ) { if ( WP_Image_Editor_Imagick::class !== $editors[0] ) { - if ( ! is_subclass_of( WP_Image_Editor_Imagick::class, $editors[0] ) ) { + if ( ! is_subclass_of( $editors[0], WP_Image_Editor_Imagick::class ) ) { return $editors; } else { $reflection = new ReflectionClass( $editors[0] ); From 6004d18b48e5f5a5fca58ead925c9e3ff89c36b3 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 22 Jan 2026 22:41:48 +0530 Subject: [PATCH 14/38] Simplify conditional checks --- ...lass-webp-uploads-image-editor-imagick.php | 10 +----- plugins/webp-uploads/hooks.php | 32 ++++++++----------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 7c267c39ee..efa5cac937 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -82,11 +82,7 @@ public function get_file(): string { * @return bool|WP_Error True or false based on whether there are transparent pixels, or an error on failure. */ public function has_transparency() { - if ( ! property_exists( $this, 'image' ) ) { - return false; - } - - if ( ! (bool) $this->image ) { + if ( ! property_exists( $this, 'image' ) || ! $this->image instanceof Imagick ) { return new WP_Error( 'image_editor_has_transparency_error_no_image', __( 'Transparency detection no image found.', 'webp-uploads' ) ); } @@ -95,10 +91,6 @@ public function has_transparency() { return self::$checked_images[ $file_path ]; } - if ( ! $this->image instanceof Imagick ) { - return false; - } - try { /* * Check if the image has an alpha channel if false, then it can't have transparency so return early. diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 3bdb39bbdc..564bf18b46 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -979,39 +979,35 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { * @return string[] Registered image editors class names. */ function webp_uploads_set_image_editors( array $editors ): array { - if ( 'avif' !== webp_uploads_get_image_output_format() || webp_uploads_imagick_avif_transparency_supported() ) { - return $editors; - } - - if ( 0 === count( $editors ) || false === array_search( WP_Image_Editor_Imagick::class, $editors, true ) ) { - return $editors; - } - - if ( ! class_exists( $editors[0] ) ) { + if ( + 'avif' !== webp_uploads_get_image_output_format() || + webp_uploads_imagick_avif_transparency_supported() || + 0 === count( $editors ) || + ! class_exists( $editors[0] ) || + ! ( WP_Image_Editor_Imagick::class === $editors[0] || is_subclass_of( $editors[0], WP_Image_Editor_Imagick::class ) ) + ) { return $editors; } if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick_Base' ) ) { if ( WP_Image_Editor_Imagick::class !== $editors[0] ) { - if ( ! is_subclass_of( $editors[0], WP_Image_Editor_Imagick::class ) ) { + $reflection = new ReflectionClass( $editors[0] ); + if ( $reflection->isFinal() ) { return $editors; - } else { - $reflection = new ReflectionClass( $editors[0] ); - if ( $reflection->isFinal() ) { - return $editors; - } - class_alias( $editors[0], 'WebP_Uploads_Image_Editor_Imagick_Base' ); } + class_alias( $editors[0], 'WebP_Uploads_Image_Editor_Imagick_Base' ); } else { class_alias( WP_Image_Editor_Imagick::class, 'WebP_Uploads_Image_Editor_Imagick_Base' ); } } if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php'; // @codeCoverageIgnore + require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php'; } - array_unshift( $editors, WebP_Uploads_Image_Editor_Imagick::class ); + if ( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + array_unshift( $editors, WebP_Uploads_Image_Editor_Imagick::class ); + } return $editors; } From d218d0f28d80eb8585a7fc1e1bc5e910770dd61d Mon Sep 17 00:00:00 2001 From: Aditya Dhade <76063440+b1ink0@users.noreply.github.com> Date: Thu, 22 Jan 2026 22:51:18 +0530 Subject: [PATCH 15/38] Fix transparency detection for fully transparent images Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../webp-uploads/class-webp-uploads-image-editor-imagick.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index efa5cac937..6b3ef02214 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -112,7 +112,7 @@ public function has_transparency() { for ( $y = 0; $y < $h; $y++ ) { $pixel = $this->image->getImagePixelColor( $x, $y ); $color = $pixel->getColor( 2 ); - if ( $color['a'] > 0 && $color['a'] < 255 ) { + if ( $color['a'] < 255 ) { self::$checked_images[ $file_path ] = true; return true; } From 41f94e12d4b2eba28292f7aa43c512700726fe50 Mon Sep 17 00:00:00 2001 From: Aditya Dhade <76063440+b1ink0@users.noreply.github.com> Date: Thu, 22 Jan 2026 22:55:46 +0530 Subject: [PATCH 16/38] Improve doc comments Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../class-webp-uploads-image-editor-imagick.php | 12 ++++++------ plugins/webp-uploads/helper.php | 2 +- plugins/webp-uploads/hooks.php | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 6b3ef02214..16610b3f48 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -93,11 +93,11 @@ public function has_transparency() { try { /* - * Check if the image has an alpha channel if false, then it can't have transparency so return early. - * - * Note that Imagick::getImageAlphaChannel() is only available if Imagick - * has been compiled against ImageMagick version 6.4.0 or newer. - */ + * Check if the image has an alpha channel if false, then it can't have transparency so return early. + * + * Note that Imagick::getImageAlphaChannel() is only available if Imagick + * has been compiled against ImageMagick version 6.4.0 or newer. + */ if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) ) ) { if ( Imagick::ALPHACHANNEL_UNDEFINED === $this->image->getImageAlphaChannel() ) { self::$checked_images[ $file_path ] = false; @@ -105,7 +105,7 @@ public function has_transparency() { } } - // Walk through the pixels and look transparent pixels. + // Walk through the pixels and look for transparent pixels. $w = $this->image->getImageWidth(); $h = $this->image->getImageHeight(); for ( $x = 0; $x < $w; $x++ ) { diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 305f313f92..3605eb0628 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -541,7 +541,7 @@ function webp_uploads_imagick_avif_transparency_supported(): bool { } /** - * Checks if an AVIF image has transparency + * Checks if an image has transparency when AVIF output is configured and AVIF transparency support is missing. * * @since n.e.x.t * diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 564bf18b46..e9f71a663a 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -971,7 +971,7 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); /** - * Overloads wp_image_editors() to load the extended class when AVIF transparency is not supported. + * Filters the list of image editors to load the extended class when AVIF transparency is not supported. * * @since n.e.x.t * From 28f2583df1a858ec7984bea1a45b54480508a174 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 22 Jan 2026 23:14:54 +0530 Subject: [PATCH 17/38] Skip unnecessary transparency check when image format is already WebP --- plugins/webp-uploads/helper.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 3605eb0628..8d03ce7467 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -25,15 +25,13 @@ * @return array> 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( ?string $filename = null ): array { - $avif_support = webp_uploads_mime_type_supported( 'image/avif' ); + // Check the selected output format. + $output_format = webp_uploads_get_image_output_format(); - if ( $avif_support && webp_uploads_check_image_transparency( $filename ) ) { - $avif_support = false; + if ( 'avif' === $output_format && ( ! webp_uploads_mime_type_supported( 'image/avif' ) || webp_uploads_check_image_transparency( $filename ) ) ) { + $output_format = 'webp'; } - // Check the selected output format. - $output_format = $avif_support ? webp_uploads_get_image_output_format() : 'webp'; - $default_transforms = array( 'image/jpeg' => array( 'image/' . $output_format ), 'image/webp' => array( 'image/' . $output_format ), From b421017650dd1b384b57f6e508bbe24491ac2e97 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 22 Jan 2026 23:46:01 +0530 Subject: [PATCH 18/38] Improve doc comments --- plugins/webp-uploads/helper.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 8d03ce7467..88f19dd9e6 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -553,6 +553,12 @@ function webp_uploads_check_image_transparency( ?string $filename ): bool { return false; } + /* + * When WordPress generates subsizes (thumbnail, medium, large, etc.), the 'image_editor_output_format' + * filter is triggered without a filename parameter. In these cases, we need to retrieve the filename + * from the current editor instance that was used to load the original image. This allows us to perform + * the transparency check on the source file even when generating derivative sizes. + */ if ( null === $filename ) { if ( null === WebP_Uploads_Image_Editor_Imagick::$current_instance ) { return false; From b00d2d0edeb46baeca350db83a9fe031d6328093 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 29 Jan 2026 10:55:59 -0500 Subject: [PATCH 19/38] Add unit tests for AVIF transparency detection Add comprehensive tests for the transparency checking functionality: - webp_uploads_imagick_avif_transparency_supported() - webp_uploads_check_image_transparency() - WebP_Uploads_Image_Editor_Imagick::has_transparency() - webp_uploads_set_image_editors() - Site health test function Co-Authored-By: Claude Opus 4.5 --- .../webp-uploads/tests/test-transparency.php | 468 ++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100644 plugins/webp-uploads/tests/test-transparency.php diff --git a/plugins/webp-uploads/tests/test-transparency.php b/plugins/webp-uploads/tests/test-transparency.php new file mode 100644 index 0000000000..d1d6181588 --- /dev/null +++ b/plugins/webp-uploads/tests/test-transparency.php @@ -0,0 +1,468 @@ +set_image_output_type( 'avif' ); + } + + /** + * Tests webp_uploads_imagick_avif_transparency_supported returns expected value based on Imagick availability. + * + * @covers ::webp_uploads_imagick_avif_transparency_supported + */ + public function test_webp_uploads_imagick_avif_transparency_supported_without_imagick(): void { + if ( extension_loaded( 'imagick' ) ) { + $this->markTestSkipped( 'Test requires Imagick to not be loaded.' ); + } + + $this->assertFalse( webp_uploads_imagick_avif_transparency_supported() ); + } + + /** + * Tests webp_uploads_imagick_avif_transparency_supported checks version correctly. + * + * @covers ::webp_uploads_imagick_avif_transparency_supported + */ + public function test_webp_uploads_imagick_avif_transparency_supported_checks_version(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + $result = webp_uploads_imagick_avif_transparency_supported(); + + // Get the actual Imagick version to verify the function's result. + $imagick_version = Imagick::getVersion(); + if ( (bool) preg_match( '/\d+(?:\.\d+)+(?:-\d+)?/', $imagick_version['versionString'], $matches ) ) { + $version = $matches[0]; + } else { + $version = $imagick_version['versionString']; + } + + $expected = version_compare( $version, '7.0.25', '>=' ); + $this->assertSame( $expected, $result ); + } + + /** + * Tests webp_uploads_check_image_transparency returns false when output format is not AVIF. + * + * @covers ::webp_uploads_check_image_transparency + */ + public function test_webp_uploads_check_image_transparency_returns_false_for_non_avif_format(): void { + $this->set_image_output_type( 'webp' ); + + $result = webp_uploads_check_image_transparency( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + $this->assertFalse( $result ); + } + + /** + * Tests webp_uploads_check_image_transparency returns false when Imagick supports AVIF transparency. + * + * @covers ::webp_uploads_check_image_transparency + */ + public function test_webp_uploads_check_image_transparency_returns_false_when_imagick_supports_transparency(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + if ( ! webp_uploads_imagick_avif_transparency_supported() ) { + $this->markTestSkipped( 'Test requires Imagick with AVIF transparency support.' ); + } + + $this->set_image_output_type( 'avif' ); + + $result = webp_uploads_check_image_transparency( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + $this->assertFalse( $result ); + } + + /** + * Tests webp_uploads_check_image_transparency returns false when file does not exist. + * + * @covers ::webp_uploads_check_image_transparency + */ + public function test_webp_uploads_check_image_transparency_returns_false_for_nonexistent_file(): void { + $this->set_image_output_type( 'avif' ); + + $result = webp_uploads_check_image_transparency( '/nonexistent/path/image.png' ); + $this->assertFalse( $result ); + } + + /** + * Tests webp_uploads_check_image_transparency returns false when filename is null without current editor instance. + * + * @covers ::webp_uploads_check_image_transparency + */ + public function test_webp_uploads_check_image_transparency_returns_false_for_null_filename_without_instance(): void { + $this->set_image_output_type( 'avif' ); + + // Ensure no current instance is set. + if ( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + WebP_Uploads_Image_Editor_Imagick::$current_instance = null; + } + + $result = webp_uploads_check_image_transparency( null ); + $this->assertFalse( $result ); + } + + /** + * Tests WebP_Uploads_Image_Editor_Imagick::has_transparency detects transparency in PNG. + * + * @covers WebP_Uploads_Image_Editor_Imagick::has_transparency + */ + public function test_has_transparency_detects_transparent_png(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + $this->set_image_output_type( 'avif' ); + + // Force loading of the extended editor class. + $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); + } + + $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + + if ( is_wp_error( $editor ) ) { + $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); + } + + if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { + $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); + } + + $has_transparency = $editor->has_transparency(); + + $this->assertNotInstanceOf( WP_Error::class, $has_transparency ); + $this->assertTrue( $has_transparency ); + } + + /** + * Tests WebP_Uploads_Image_Editor_Imagick::has_transparency returns false for JPEG (no transparency). + * + * @covers WebP_Uploads_Image_Editor_Imagick::has_transparency + */ + public function test_has_transparency_returns_false_for_jpeg(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + $this->set_image_output_type( 'avif' ); + + // Force loading of the extended editor class. + $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); + } + + $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/car.jpeg' ); + + if ( is_wp_error( $editor ) ) { + $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); + } + + if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { + $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); + } + + $has_transparency = $editor->has_transparency(); + + $this->assertNotInstanceOf( WP_Error::class, $has_transparency ); + $this->assertFalse( $has_transparency ); + } + + /** + * Tests WebP_Uploads_Image_Editor_Imagick::get_file returns correct file path. + * + * @covers WebP_Uploads_Image_Editor_Imagick::get_file + */ + public function test_get_file_returns_correct_path(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + $this->set_image_output_type( 'avif' ); + + // Force loading of the extended editor class. + $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); + } + + $image_path = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; + $editor = wp_get_image_editor( $image_path ); + + if ( is_wp_error( $editor ) ) { + $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); + } + + if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { + $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); + } + + $this->assertSame( $image_path, $editor->get_file() ); + } + + /** + * Tests WebP_Uploads_Image_Editor_Imagick sets current_instance on load. + * + * @covers WebP_Uploads_Image_Editor_Imagick::load + */ + public function test_load_sets_current_instance(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + $this->set_image_output_type( 'avif' ); + + // Force loading of the extended editor class. + $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); + } + + // Reset current instance. + WebP_Uploads_Image_Editor_Imagick::$current_instance = null; + + $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + + if ( is_wp_error( $editor ) ) { + $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); + } + + if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { + $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); + } + + $this->assertNotNull( WebP_Uploads_Image_Editor_Imagick::$current_instance ); + } + + /** + * Tests webp_uploads_set_image_editors prepends custom editor when conditions are met. + * + * @covers ::webp_uploads_set_image_editors + */ + public function test_webp_uploads_set_image_editors_prepends_custom_editor(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + $this->set_image_output_type( 'avif' ); + + // Only run this test if Imagick doesn't support AVIF transparency. + if ( webp_uploads_imagick_avif_transparency_supported() ) { + $this->markTestSkipped( 'Test requires Imagick without AVIF transparency support.' ); + } + + $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + + $this->assertContains( 'WebP_Uploads_Image_Editor_Imagick', $editors ); + $this->assertSame( 'WebP_Uploads_Image_Editor_Imagick', $editors[0] ); + } + + /** + * Tests webp_uploads_set_image_editors returns original editors when output format is not AVIF. + * + * @covers ::webp_uploads_set_image_editors + */ + public function test_webp_uploads_set_image_editors_returns_original_for_non_avif(): void { + $this->set_image_output_type( 'webp' ); + + $original_editors = array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ); + $editors = webp_uploads_set_image_editors( $original_editors ); + + $this->assertSame( $original_editors, $editors ); + } + + /** + * Tests webp_uploads_set_image_editors returns original editors when Imagick supports AVIF transparency. + * + * @covers ::webp_uploads_set_image_editors + */ + public function test_webp_uploads_set_image_editors_returns_original_when_transparency_supported(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + if ( ! webp_uploads_imagick_avif_transparency_supported() ) { + $this->markTestSkipped( 'Test requires Imagick with AVIF transparency support.' ); + } + + $this->set_image_output_type( 'avif' ); + + $original_editors = array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ); + $editors = webp_uploads_set_image_editors( $original_editors ); + + $this->assertSame( $original_editors, $editors ); + } + + /** + * Tests webp_uploads_set_image_editors returns original editors when array is empty. + * + * @covers ::webp_uploads_set_image_editors + */ + public function test_webp_uploads_set_image_editors_returns_original_for_empty_array(): void { + $this->set_image_output_type( 'avif' ); + + $editors = webp_uploads_set_image_editors( array() ); + + $this->assertSame( array(), $editors ); + } + + /** + * Tests webp_uploads_set_image_editors returns original editors when first editor is not Imagick. + * + * @covers ::webp_uploads_set_image_editors + */ + public function test_webp_uploads_set_image_editors_returns_original_when_first_not_imagick(): void { + $this->set_image_output_type( 'avif' ); + + $original_editors = array( 'WP_Image_Editor_GD', 'WP_Image_Editor_Imagick' ); + $editors = webp_uploads_set_image_editors( $original_editors ); + + $this->assertSame( $original_editors, $editors ); + } + + /** + * Tests that transparency check falls back to WebP for transparent PNG images. + * + * @covers ::webp_uploads_get_upload_image_mime_transforms + */ + public function test_upload_image_mime_transforms_fallback_to_webp_for_transparent_png(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + // Only test when Imagick doesn't support AVIF transparency. + if ( webp_uploads_imagick_avif_transparency_supported() ) { + $this->markTestSkipped( 'Test requires Imagick without AVIF transparency support.' ); + } + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + // Load the class by triggering the filter. + webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + } + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); + } + + $this->set_image_output_type( 'avif' ); + + $transparent_image = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; + $transforms = webp_uploads_get_upload_image_mime_transforms( $transparent_image ); + + // For transparent images, should fall back to WebP. + $this->assertArrayHasKey( 'image/png', $transforms ); + $this->assertContains( 'image/webp', $transforms['image/png'] ); + } + + /** + * Tests site health function returns correct structure. + * + * @covers ::webp_uploads_imagick_avif_transparency_supported_test + */ + public function test_site_health_imagick_avif_transparency_test_returns_correct_structure(): void { + $result = webp_uploads_imagick_avif_transparency_supported_test(); + + $this->assertArrayHasKey( 'label', $result ); + $this->assertArrayHasKey( 'status', $result ); + $this->assertArrayHasKey( 'badge', $result ); + $this->assertArrayHasKey( 'description', $result ); + $this->assertArrayHasKey( 'actions', $result ); + $this->assertArrayHasKey( 'test', $result ); + + $this->assertArrayHasKey( 'label', $result['badge'] ); + $this->assertArrayHasKey( 'color', $result['badge'] ); + } + + /** + * Tests site health function returns good status when transparency is supported. + * + * @covers ::webp_uploads_imagick_avif_transparency_supported_test + */ + public function test_site_health_returns_good_status_when_supported(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + if ( ! webp_uploads_imagick_avif_transparency_supported() ) { + $this->markTestSkipped( 'Test requires Imagick with AVIF transparency support.' ); + } + + $result = webp_uploads_imagick_avif_transparency_supported_test(); + + $this->assertSame( 'good', $result['status'] ); + $this->assertStringContainsString( 'supports AVIF', $result['label'] ); + } + + /** + * Tests site health function returns recommended status when transparency is not supported. + * + * @covers ::webp_uploads_imagick_avif_transparency_supported_test + */ + public function test_site_health_returns_recommended_status_when_not_supported(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + if ( webp_uploads_imagick_avif_transparency_supported() ) { + $this->markTestSkipped( 'Test requires Imagick without AVIF transparency support.' ); + } + + $result = webp_uploads_imagick_avif_transparency_supported_test(); + + $this->assertSame( 'recommended', $result['status'] ); + $this->assertStringContainsString( 'does not support', $result['label'] ); + } + + /** + * Tests has_transparency caches results for same file. + * + * @covers WebP_Uploads_Image_Editor_Imagick::has_transparency + */ + public function test_has_transparency_caches_results(): void { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + $this->markTestSkipped( 'Imagick extension is not available.' ); + } + + $this->set_image_output_type( 'avif' ); + + // Force loading of the extended editor class. + $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); + } + + $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + + if ( is_wp_error( $editor ) ) { + $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); + } + + if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { + $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); + } + + // Call has_transparency twice - second call should use cache. + $result1 = $editor->has_transparency(); + $result2 = $editor->has_transparency(); + + $this->assertSame( $result1, $result2 ); + $this->assertTrue( $result1 ); + } +} From 28d6020de6dbce3436f0828706d04b6273a42152 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 29 Jan 2026 23:55:05 +0530 Subject: [PATCH 20/38] Enhance transparency detection logic --- ...lass-webp-uploads-image-editor-imagick.php | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 16610b3f48..5ed3a346d1 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -90,6 +90,7 @@ public function has_transparency() { if ( isset( self::$checked_images[ $file_path ] ) ) { return self::$checked_images[ $file_path ]; } + $transparency = false; try { /* @@ -105,22 +106,47 @@ public function has_transparency() { } } - // Walk through the pixels and look for transparent pixels. - $w = $this->image->getImageWidth(); - $h = $this->image->getImageHeight(); - for ( $x = 0; $x < $w; $x++ ) { - for ( $y = 0; $y < $h; $y++ ) { - $pixel = $this->image->getImagePixelColor( $x, $y ); - $color = $pixel->getColor( 2 ); - if ( $color['a'] < 255 ) { - self::$checked_images[ $file_path ] = true; - return true; + // Use mean and range to determine if there is any transparency more efficiently. + if ( is_callable( array( $this->image, 'getImageChannelMean' ) ) && is_callable( array( $this->image, 'getImageChannelRange' ) ) ) { + $rgb_mean = $this->image->getImageChannelMean( Imagick::CHANNEL_ALL ); + $alpha_range = $this->image->getImageChannelRange( Imagick::CHANNEL_ALPHA ); + + if ( isset( $rgb_mean['mean'], $alpha_range['maxima'] ) ) { + $maxima = (int) $alpha_range['maxima']; + $mean = (int) $rgb_mean['mean']; + + if ( 0 > $maxima || 0 > $mean ) { + // For invalid values assume no transparency. + $transparency = false; + } elseif ( 0 === $maxima && 0 === $mean ) { + // Alpha channel is all zeros AND no RGB content indicates fully transparent image. + $transparency = true; + } elseif ( 0 === $maxima && $mean > 0 ) { + // Alpha maxima of 0 with RGB content present indicates no real alpha channel exists (hence fully opaque). + $transparency = false; + } elseif ( 0 < $maxima && 0 < $mean ) { + // Non-zero alpha values with RGB content present indicates some transparency. + $transparency = true; + } + } + } else { + // Fallback to walk through the pixels and look for transparent pixels. + $w = $this->image->getImageWidth(); + $h = $this->image->getImageHeight(); + for ( $x = 0; $x < $w; $x++ ) { + for ( $y = 0; $y < $h; $y++ ) { + $pixel = $this->image->getImagePixelColor( $x, $y ); + $color = $pixel->getColor( 2 ); + if ( $color['a'] < 255 ) { + $transparency = true; + break 2; + } } } } - self::$checked_images[ $file_path ] = false; - return false; + self::$checked_images[ $file_path ] = $transparency; + return $transparency; } catch ( Exception $e ) { /* translators: %s is the error message */ return new WP_Error( 'image_editor_has_transparency_error', sprintf( __( 'Transparency detection failed: %s', 'webp-uploads' ), $e->getMessage() ) ); From 4cf8302774a12172da7fad1c9de7295d161a3ab6 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 30 Jan 2026 16:57:46 +0530 Subject: [PATCH 21/38] Improve image editor selection logic --- plugins/webp-uploads/helper.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 88f19dd9e6..507f355870 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -579,7 +579,15 @@ function webp_uploads_check_image_transparency( ?string $filename ): bool { } $processed_images[ $filename ] = false; - $editor = wp_get_image_editor( $filename ); + $editor = wp_get_image_editor( + $filename, + array( + 'methods' => array( + 'get_file', + 'has_transparency', + ), + ) + ); if ( is_wp_error( $editor ) || ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { return false; From 92f65281985a4fe0e7b9ba14b1d2f9036ee07651 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Mon, 2 Feb 2026 23:14:27 +0530 Subject: [PATCH 22/38] Ensure helper class is loaded --- plugins/webp-uploads/helper.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 507f355870..d5f30b18e7 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -549,7 +549,16 @@ function webp_uploads_imagick_avif_transparency_supported(): bool { function webp_uploads_check_image_transparency( ?string $filename ): bool { static $processed_images = array(); - if ( 'avif' !== webp_uploads_get_image_output_format() || webp_uploads_imagick_avif_transparency_supported() || ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + if ( 'avif' !== webp_uploads_get_image_output_format() || webp_uploads_imagick_avif_transparency_supported() ) { + return false; + } + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + // Calls filter `wp_image_editors` internally which makes sure `webp_uploads_set_image_editors` is called. + wp_image_editor_supports(); + } + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { return false; } From 23f528b0df08c8aa8eaaae5a72af5edc4a63f9b8 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 10 Feb 2026 01:28:25 +0530 Subject: [PATCH 23/38] Add filter to transparency check function --- plugins/webp-uploads/helper.php | 34 ++++++++++++++++++++++++--------- plugins/webp-uploads/hooks.php | 11 +++++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index d5f30b18e7..2a16ddbcef 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -522,20 +522,36 @@ function webp_uploads_get_attachment_file_mime_type( int $attachment_id, string * * @since n.e.x.t * + * @param string|null $version Optional Imagick version string. If not provided, the version will be retrieved from the Imagick class. * @return bool True if Imagick has AVIF transparency support, false otherwise. */ -function webp_uploads_imagick_avif_transparency_supported(): bool { - if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { +function webp_uploads_imagick_avif_transparency_supported( ?string $version = null ): bool { + $supported = false; + $imagick_version = $version; + + if ( null === $imagick_version && extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { $imagick_version = Imagick::getVersion(); - if ( (bool) preg_match( '/\d+(?:\.\d+)+(?:-\d+)?/', $imagick_version['versionString'], $matches ) ) { - $imagick_version = $matches[0]; - } else { - $imagick_version = $imagick_version['versionString']; - } - return version_compare( $imagick_version, '7.0.25', '>=' ); + $imagick_version = $imagick_version['versionString']; } - return false; + if ( null !== $imagick_version && '' !== $imagick_version && (bool) preg_match( '/\d+(?:\.\d+)+(?:-\d+)?/', $imagick_version, $matches ) ) { + $imagick_version = $matches[0]; + } + + if ( null === $imagick_version || '' === $imagick_version ) { + return false; + } + + $supported = version_compare( $imagick_version, '7.0.25', '>=' ); + + /** + * Filters whether Imagick has AVIF transparency support. + * + * @since n.e.x.t + * + * @param bool $supported Whether AVIF transparency is supported. + */ + return (bool) apply_filters( 'webp_uploads_imagick_avif_transparency_supported', $supported ); } /** diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index e9f71a663a..a1dfa341c5 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -989,12 +989,15 @@ function webp_uploads_set_image_editors( array $editors ): array { return $editors; } + if ( WP_Image_Editor_Imagick::class !== $editors[0] ) { + $reflection = new ReflectionClass( $editors[0] ); + if ( $reflection->isFinal() ) { + return $editors; + } + } + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick_Base' ) ) { if ( WP_Image_Editor_Imagick::class !== $editors[0] ) { - $reflection = new ReflectionClass( $editors[0] ); - if ( $reflection->isFinal() ) { - return $editors; - } class_alias( $editors[0], 'WebP_Uploads_Image_Editor_Imagick_Base' ); } else { class_alias( WP_Image_Editor_Imagick::class, 'WebP_Uploads_Image_Editor_Imagick_Base' ); From 06f67f706162d805442512ff1a7e999cc4958594 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 10 Feb 2026 01:30:30 +0530 Subject: [PATCH 24/38] Improve test code coverage --- package.json | 4 +- .../class-custom-image-editor-imagick.php | 17 + .../data/class-final-test-image-editor.php | 17 + .../webp-uploads/tests/test-transparency.php | 720 ++++++++++++------ 4 files changed, 510 insertions(+), 248 deletions(-) create mode 100644 plugins/webp-uploads/tests/data/class-custom-image-editor-imagick.php create mode 100644 plugins/webp-uploads/tests/data/class-final-test-image-editor.php diff --git a/package.json b/package.json index ccd26a1f4e..939a27d53e 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "test-php:speculation-rules": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:speculation-rules", "test-php:view-transitions": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:view-transitions", "test-php:web-worker-offloading": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:web-worker-offloading", - "test-php:webp-uploads": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:webp-uploads", + "test-php:webp-uploads": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:webp-uploads", "test-php-multisite:performance-lab": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:performance-lab", "test-php-multisite:auto-sizes": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:auto-sizes", "test-php-multisite:dominant-color-images": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:dominant-color-images", @@ -85,7 +85,7 @@ "test-php-multisite:speculation-rules": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:speculation-rules", "test-php-multisite:view-transitions": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:view-transitions", "test-php-multisite:web-worker-offloading": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:web-worker-offloading", - "test-php-multisite:webp-uploads": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:webp-uploads", + "test-php-multisite:webp-uploads": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:webp-uploads", "update-test-case-snapshots": "bin/update-test-case-snapshots.sh", "wp-env": "wp-env", "prepare": "husky" diff --git a/plugins/webp-uploads/tests/data/class-custom-image-editor-imagick.php b/plugins/webp-uploads/tests/data/class-custom-image-editor-imagick.php new file mode 100644 index 0000000000..f0d1d46315 --- /dev/null +++ b/plugins/webp-uploads/tests/data/class-custom-image-editor-imagick.php @@ -0,0 +1,17 @@ +set_image_output_type( 'avif' ); } /** - * Tests webp_uploads_imagick_avif_transparency_supported returns expected value based on Imagick availability. + * Data provider for ImageMagick version strings. * - * @covers ::webp_uploads_imagick_avif_transparency_supported + * @return array Test data with version strings and expected support. */ - public function test_webp_uploads_imagick_avif_transparency_supported_without_imagick(): void { - if ( extension_loaded( 'imagick' ) ) { - $this->markTestSkipped( 'Test requires Imagick to not be loaded.' ); - } - - $this->assertFalse( webp_uploads_imagick_avif_transparency_supported() ); + public function data_provider_imagick_versions(): array { + return array( + 'ImageMagick 6.8.9 Q16 x86_64' => array( 'ImageMagick 6.8.9-9 Q16 x86_64 2018-09-28 https://imagemagick.org/index.php', false ), + 'ImageMagick 6.9.12 Q16 x86_64' => array( 'ImageMagick 6.9.12-27 Q16 x86_64 2021-10-24 https://imagemagick.org', false ), + 'ImageMagick 7.1.0 Q16-HDRI x86_64' => array( 'ImageMagick 7.1.0-57 Q16-HDRI x86_64 d68553b17:20221230 https://imagemagick.org', true ), + 'ImageMagick 7.1.2 Q16-HDRI x86_64' => array( 'ImageMagick 7.1.2-7 Q16-HDRI x86_64 23405 https://imagemagick.org', true ), + 'ImageMagick 6.9.13 Q16 x86_64' => array( 'ImageMagick 6.9.13-17 Q16 x86_64', false ), + 'ImageMagick 7.1.1 Q16 aarch64' => array( 'ImageMagick 7.1.1-15 Q16 aarch64 98eceff6a:20230729 https://imagemagick.org', true ), + 'ImageMagick 7.0.25 (exact minimum version)' => array( 'ImageMagick 7.0.25 Q16 x86_64', true ), + 'ImageMagick 7.0.24 (just below minimum)' => array( 'ImageMagick 7.0.24 Q16 x86_64', false ), + 'Empty string should return false' => array( '', false ), + 'Invalid string without version should be false' => array( 'Invalid version string', false ), + 'String with only text should be false' => array( 'ImageMagick', false ), + 'Malformed version string should be false' => array( 'ImageMagick x.y.z', false ), + ); } /** * Tests webp_uploads_imagick_avif_transparency_supported checks version correctly. * + * @dataProvider data_provider_imagick_versions * @covers ::webp_uploads_imagick_avif_transparency_supported + * + * @param string $version ImageMagick version string. + * @param bool $expected_support Expected transparency support result. */ - public function test_webp_uploads_imagick_avif_transparency_supported_checks_version(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } + public function test_webp_uploads_imagick_avif_transparency_supported_checks_version( string $version, bool $expected_support ): void { + remove_all_filters( 'webp_uploads_imagick_avif_transparency_supported' ); - $result = webp_uploads_imagick_avif_transparency_supported(); + $result = webp_uploads_imagick_avif_transparency_supported( $version ); - // Get the actual Imagick version to verify the function's result. - $imagick_version = Imagick::getVersion(); - if ( (bool) preg_match( '/\d+(?:\.\d+)+(?:-\d+)?/', $imagick_version['versionString'], $matches ) ) { - $version = $matches[0]; - } else { - $version = $imagick_version['versionString']; - } - - $expected = version_compare( $version, '7.0.25', '>=' ); - $this->assertSame( $expected, $result ); + $this->assertSame( $expected_support, $result ); } /** @@ -69,15 +79,7 @@ public function test_webp_uploads_check_image_transparency_returns_false_for_non * @covers ::webp_uploads_check_image_transparency */ public function test_webp_uploads_check_image_transparency_returns_false_when_imagick_supports_transparency(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } - - if ( ! webp_uploads_imagick_avif_transparency_supported() ) { - $this->markTestSkipped( 'Test requires Imagick with AVIF transparency support.' ); - } - - $this->set_image_output_type( 'avif' ); + $this->mock_avif_transparency_support( true ); $result = webp_uploads_check_image_transparency( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); $this->assertFalse( $result ); @@ -89,8 +91,6 @@ public function test_webp_uploads_check_image_transparency_returns_false_when_im * @covers ::webp_uploads_check_image_transparency */ public function test_webp_uploads_check_image_transparency_returns_false_for_nonexistent_file(): void { - $this->set_image_output_type( 'avif' ); - $result = webp_uploads_check_image_transparency( '/nonexistent/path/image.png' ); $this->assertFalse( $result ); } @@ -101,8 +101,6 @@ public function test_webp_uploads_check_image_transparency_returns_false_for_non * @covers ::webp_uploads_check_image_transparency */ public function test_webp_uploads_check_image_transparency_returns_false_for_null_filename_without_instance(): void { - $this->set_image_output_type( 'avif' ); - // Ensure no current instance is set. if ( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { WebP_Uploads_Image_Editor_Imagick::$current_instance = null; @@ -113,356 +111,586 @@ public function test_webp_uploads_check_image_transparency_returns_false_for_nul } /** - * Tests WebP_Uploads_Image_Editor_Imagick::has_transparency detects transparency in PNG. + * Tests WebP_Uploads_Image_Editor_Imagick::get_file returns correct file path. * - * @covers WebP_Uploads_Image_Editor_Imagick::has_transparency + * @covers WebP_Uploads_Image_Editor_Imagick::get_file */ - public function test_has_transparency_detects_transparent_png(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } + public function test_get_file_returns_correct_path(): void { + $this->setup_custom_image_editor( false ); - $this->set_image_output_type( 'avif' ); + $image_path = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; + $editor = wp_get_image_editor( $image_path ); - // Force loading of the extended editor class. - $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + $this->assertNotWPError( $editor, 'Failed to create image editor.' ); + // @phpstan-ignore-next-line Class extends runtime alias WebP_Uploads_Image_Editor_Imagick_Base. + $this->assertInstanceOf( WebP_Uploads_Image_Editor_Imagick::class, $editor, 'Editor is not the custom image editor class.' ); + $this->assertSame( $image_path, $editor->get_file() ); + } - if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); - } + /** + * Tests WebP_Uploads_Image_Editor_Imagick sets current_instance on load. + * + * @covers WebP_Uploads_Image_Editor_Imagick::load + */ + public function test_load_sets_current_instance(): void { + $this->setup_custom_image_editor( false ); + + // Reset current instance. + WebP_Uploads_Image_Editor_Imagick::$current_instance = null; $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); - if ( is_wp_error( $editor ) ) { - $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); - } + $this->assertNotWPError( $editor, 'Failed to create image editor.' ); + // @phpstan-ignore-next-line Class extends runtime alias WebP_Uploads_Image_Editor_Imagick_Base. + $this->assertInstanceOf( WebP_Uploads_Image_Editor_Imagick::class, $editor, 'Editor is not the custom image editor class.' ); + $this->assertNotNull( WebP_Uploads_Image_Editor_Imagick::$current_instance ); + } - if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { - $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); - } + /** + * Tests webp_uploads_set_image_editors prepends custom editor when conditions are met. + * + * @covers ::webp_uploads_set_image_editors + */ + public function test_webp_uploads_set_image_editors_prepends_custom_editor(): void { + $this->mock_avif_transparency_support( false ); - $has_transparency = $editor->has_transparency(); + $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); - $this->assertNotInstanceOf( WP_Error::class, $has_transparency ); - $this->assertTrue( $has_transparency ); + $this->assertContains( 'WebP_Uploads_Image_Editor_Imagick', $editors ); + $this->assertSame( 'WebP_Uploads_Image_Editor_Imagick', $editors[0] ); } /** - * Tests WebP_Uploads_Image_Editor_Imagick::has_transparency returns false for JPEG (no transparency). + * Tests webp_uploads_set_image_editors returns original editors when Imagick supports AVIF transparency. * - * @covers WebP_Uploads_Image_Editor_Imagick::has_transparency + * @covers ::webp_uploads_set_image_editors */ - public function test_has_transparency_returns_false_for_jpeg(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } + public function test_webp_uploads_set_image_editors_returns_original_when_transparency_supported(): void { + $this->mock_avif_transparency_support( true ); - $this->set_image_output_type( 'avif' ); + $original_editors = array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ); + $editors = webp_uploads_set_image_editors( $original_editors ); - // Force loading of the extended editor class. - $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + $this->assertSame( $original_editors, $editors ); + } - if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); - } + /** + * Tests that transparency check falls back to WebP for transparent PNG images. + * + * @covers ::webp_uploads_get_upload_image_mime_transforms + */ + public function test_upload_image_mime_transforms_fallback_to_webp_for_transparent_png(): void { + $this->mock_avif_transparency_support( false ); - $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/car.jpeg' ); + $this->ensure_custom_editor_loaded(); - if ( is_wp_error( $editor ) ) { - $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); - } + // Load the class WebP_Uploads_Image_Editor_Imagick by triggering the filter. + webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); - if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { - $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); - } + $this->assertTrue( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ), 'Custom image editor class should be loaded.' ); - $has_transparency = $editor->has_transparency(); + $transparent_image = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; + $transforms = webp_uploads_get_upload_image_mime_transforms( $transparent_image ); - $this->assertNotInstanceOf( WP_Error::class, $has_transparency ); - $this->assertFalse( $has_transparency ); + // For transparent images, should fall back to WebP. + $this->assertArrayHasKey( 'image/png', $transforms ); + $this->assertContains( 'image/webp', $transforms['image/png'] ); } /** - * Tests WebP_Uploads_Image_Editor_Imagick::get_file returns correct file path. + * Tests webp_uploads_get_upload_image_mime_transforms returns AVIF for non-transparent images. * - * @covers WebP_Uploads_Image_Editor_Imagick::get_file + * @covers ::webp_uploads_get_upload_image_mime_transforms */ - public function test_get_file_returns_correct_path(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); + public function test_upload_image_mime_transforms_uses_avif_for_non_transparent_images(): void { + if ( ! webp_uploads_mime_type_supported( 'image/avif' ) ) { + $this->markTestSkipped( 'AVIF is not supported.' ); } - $this->set_image_output_type( 'avif' ); + $this->mock_avif_transparency_support( false ); - // Force loading of the extended editor class. - $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + $non_transparent_image = TESTS_PLUGIN_DIR . '/tests/data/images/car.jpeg'; + $transforms = webp_uploads_get_upload_image_mime_transforms( $non_transparent_image ); - if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); - } + // For non-transparent images, should use AVIF. + $this->assertArrayHasKey( 'image/jpeg', $transforms ); + $this->assertContains( 'image/avif', $transforms['image/jpeg'] ); + } - $image_path = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; - $editor = wp_get_image_editor( $image_path ); + /** + * Tests webp_uploads_get_upload_image_mime_transforms with WebP output format ignores transparency. + * + * @covers ::webp_uploads_get_upload_image_mime_transforms + */ + public function test_upload_image_mime_transforms_ignores_transparency_for_webp_output(): void { + $this->set_image_output_type( 'webp' ); - if ( is_wp_error( $editor ) ) { - $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); - } + $transparent_image = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; + $transforms = webp_uploads_get_upload_image_mime_transforms( $transparent_image ); - if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { - $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); - } + // WebP output should not check transparency. + $this->assertArrayHasKey( 'image/png', $transforms ); + $this->assertContains( 'image/webp', $transforms['image/png'] ); + } - $this->assertSame( $image_path, $editor->get_file() ); + /** + * Tests site health function returns good status when transparency is supported. + * + * @covers ::webp_uploads_imagick_avif_transparency_supported_test + */ + public function test_site_health_returns_good_status_when_supported(): void { + $this->mock_avif_transparency_support( true ); + + $result = webp_uploads_imagick_avif_transparency_supported_test(); + + $this->assertSame( 'good', $result['status'] ); + $this->assertStringContainsString( 'supports AVIF', $result['label'] ); } /** - * Tests WebP_Uploads_Image_Editor_Imagick sets current_instance on load. + * Tests site health function returns recommended status when transparency is not supported. * - * @covers WebP_Uploads_Image_Editor_Imagick::load + * @covers ::webp_uploads_imagick_avif_transparency_supported_test */ - public function test_load_sets_current_instance(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } + public function test_site_health_returns_recommended_status_when_not_supported(): void { + $this->mock_avif_transparency_support( false ); - $this->set_image_output_type( 'avif' ); + $result = webp_uploads_imagick_avif_transparency_supported_test(); - // Force loading of the extended editor class. - $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + $this->assertSame( 'recommended', $result['status'] ); + $this->assertStringContainsString( 'does not support', $result['label'] ); + } - if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); - } + /** + * Tests webp_uploads_check_image_transparency caches results for same file. + * + * @covers ::webp_uploads_check_image_transparency + */ + public function test_webp_uploads_check_image_transparency_caches_results(): void { + $this->mock_avif_transparency_support( false ); - // Reset current instance. - WebP_Uploads_Image_Editor_Imagick::$current_instance = null; + $this->ensure_custom_editor_loaded(); - $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + // Force loading of the extended editor class. + webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); - if ( is_wp_error( $editor ) ) { - $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); - } + $this->assertTrue( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ), 'Custom image editor class should be loaded.' ); - if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { - $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); - } + $image_path = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; - $this->assertNotNull( WebP_Uploads_Image_Editor_Imagick::$current_instance ); + // Call the function twice - second call should use cache. + $result1 = webp_uploads_check_image_transparency( $image_path ); + $result2 = webp_uploads_check_image_transparency( $image_path ); + + $this->assertSame( $result1, $result2 ); + $this->assertTrue( $result1 ); } /** - * Tests webp_uploads_set_image_editors prepends custom editor when conditions are met. + * Tests webp_uploads_check_image_transparency when editor cannot be instantiated. * - * @covers ::webp_uploads_set_image_editors + * @covers ::webp_uploads_check_image_transparency */ - public function test_webp_uploads_set_image_editors_prepends_custom_editor(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } + public function test_webp_uploads_check_image_transparency_when_editor_fails(): void { + $this->mock_avif_transparency_support( false ); - $this->set_image_output_type( 'avif' ); + $this->ensure_custom_editor_loaded(); - // Only run this test if Imagick doesn't support AVIF transparency. - if ( webp_uploads_imagick_avif_transparency_supported() ) { - $this->markTestSkipped( 'Test requires Imagick without AVIF transparency support.' ); - } + webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); - $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + // Temporarily disable image editors to make wp_get_image_editor fail. + add_filter( 'wp_image_editors', '__return_empty_array' ); - $this->assertContains( 'WebP_Uploads_Image_Editor_Imagick', $editors ); - $this->assertSame( 'WebP_Uploads_Image_Editor_Imagick', $editors[0] ); + $image_path = TESTS_PLUGIN_DIR . '/tests/data/images/earth.gif'; + $result = webp_uploads_check_image_transparency( $image_path ); + + remove_filter( 'wp_image_editors', '__return_empty_array' ); + + $this->assertFalse( $result ); } /** - * Tests webp_uploads_set_image_editors returns original editors when output format is not AVIF. + * Tests webp_uploads_check_image_transparency with null filename and valid current instance. * - * @covers ::webp_uploads_set_image_editors + * @covers ::webp_uploads_check_image_transparency */ - public function test_webp_uploads_set_image_editors_returns_original_for_non_avif(): void { - $this->set_image_output_type( 'webp' ); + public function test_webp_uploads_check_image_transparency_with_null_filename_and_current_instance(): void { + $this->mock_avif_transparency_support( false ); - $original_editors = array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ); - $editors = webp_uploads_set_image_editors( $original_editors ); + $this->ensure_custom_editor_loaded(); - $this->assertSame( $original_editors, $editors ); + webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + + $this->assertTrue( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ), 'Custom image editor class should be loaded.' ); + + $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + $this->assertNotWPError( $editor ); + + // Now call with null filename - should use current instance's file. + $result = webp_uploads_check_image_transparency( null ); + + $this->assertTrue( $result ); } /** - * Tests webp_uploads_set_image_editors returns original editors when Imagick supports AVIF transparency. + * Tests webp_uploads_check_image_transparency returns false when current instance file is empty. * - * @covers ::webp_uploads_set_image_editors + * @covers ::webp_uploads_check_image_transparency */ - public function test_webp_uploads_set_image_editors_returns_original_when_transparency_supported(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } + public function test_webp_uploads_check_image_transparency_with_empty_current_instance_file(): void { + $this->mock_avif_transparency_support( false ); - if ( ! webp_uploads_imagick_avif_transparency_supported() ) { - $this->markTestSkipped( 'Test requires Imagick with AVIF transparency support.' ); - } + webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); - $this->set_image_output_type( 'avif' ); + $this->assertTrue( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ), 'Custom image editor class should be loaded.' ); - $original_editors = array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ); - $editors = webp_uploads_set_image_editors( $original_editors ); + // Create a mock editor with empty file property. + $editor = $this->getMockBuilder( 'WebP_Uploads_Image_Editor_Imagick' ) + ->disableOriginalConstructor() + ->getMock(); - $this->assertSame( $original_editors, $editors ); + $editor->method( 'get_file' )->willReturn( '' ); + + WebP_Uploads_Image_Editor_Imagick::$current_instance = $editor; + + $result = webp_uploads_check_image_transparency( null ); + + // Clean up. + WebP_Uploads_Image_Editor_Imagick::$current_instance = null; + + $this->assertFalse( $result ); } /** - * Tests webp_uploads_set_image_editors returns original editors when array is empty. + * Tests editor methods return expected values when properties are not set. * - * @covers ::webp_uploads_set_image_editors + * @covers WebP_Uploads_Image_Editor_Imagick::has_transparency + * @covers WebP_Uploads_Image_Editor_Imagick::get_file */ - public function test_webp_uploads_set_image_editors_returns_original_for_empty_array(): void { - $this->set_image_output_type( 'avif' ); + public function test_editor_methods_with_unset_properties(): void { + $this->mock_avif_transparency_support( false ); + + webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); - $editors = webp_uploads_set_image_editors( array() ); + $this->assertTrue( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ), 'Custom image editor class should be loaded.' ); - $this->assertSame( array(), $editors ); + $test_file = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; + // @phpstan-ignore-next-line Constructor inherited from parent class. + $editor = new WebP_Uploads_Image_Editor_Imagick( $test_file ); + $editor->load(); + $reflection = new ReflectionClass( $editor ); + + // Test has_transparency() with null image property. + $image_prop = $reflection->getProperty( 'image' ); + $image_prop->setAccessible( true ); + $image_prop->setValue( $editor, null ); + + $result = $editor->has_transparency(); + + $this->assertInstanceOf( WP_Error::class, $result ); + $this->assertSame( 'image_editor_has_transparency_error_no_image', $result->get_error_code() ); + + // Test get_file() with empty file property. + $file_prop = $reflection->getProperty( 'file' ); + $file_prop->setAccessible( true ); + $file_prop->setValue( $editor, '' ); + + $this->assertSame( '', $editor->get_file() ); } /** - * Tests webp_uploads_set_image_editors returns original editors when first editor is not Imagick. + * Tests webp_uploads_add_imagick_avif_transparency_supported_test adds test to site health. * - * @covers ::webp_uploads_set_image_editors + * @covers ::webp_uploads_add_imagick_avif_transparency_supported_test */ - public function test_webp_uploads_set_image_editors_returns_original_when_first_not_imagick(): void { - $this->set_image_output_type( 'avif' ); - - $original_editors = array( 'WP_Image_Editor_GD', 'WP_Image_Editor_Imagick' ); - $editors = webp_uploads_set_image_editors( $original_editors ); + public function test_webp_uploads_add_imagick_avif_transparency_supported_test_adds_test(): void { + $tests = array( + 'direct' => array(), + ); + + $result = webp_uploads_add_imagick_avif_transparency_supported_test( $tests ); + + $this->assertArrayHasKey( 'direct', $result ); + $this->assertArrayHasKey( 'imagick_avif_transparency_supported', $result['direct'] ); + $this->assertArrayHasKey( 'label', $result['direct']['imagick_avif_transparency_supported'] ); + $this->assertArrayHasKey( 'test', $result['direct']['imagick_avif_transparency_supported'] ); + $this->assertSame( 'webp_uploads_imagick_avif_transparency_supported_test', $result['direct']['imagick_avif_transparency_supported']['test'] ); + } - $this->assertSame( $original_editors, $editors ); + /** + * Tests webp_uploads_add_imagick_avif_transparency_supported_test preserves existing tests. + * + * @covers ::webp_uploads_add_imagick_avif_transparency_supported_test + */ + public function test_webp_uploads_add_imagick_avif_transparency_supported_test_preserves_existing_tests(): void { + $tests = array( + 'direct' => array( + 'existing_test' => array( + 'label' => 'Existing Test', + 'test' => 'existing_test_callback', + ), + ), + ); + + $result = webp_uploads_add_imagick_avif_transparency_supported_test( $tests ); + + $this->assertArrayHasKey( 'existing_test', $result['direct'] ); + $this->assertArrayHasKey( 'imagick_avif_transparency_supported', $result['direct'] ); } /** - * Tests that transparency check falls back to WebP for transparent PNG images. + * Tests integration: upload transparent PNG with AVIF output falls back to WebP. * + * @covers ::webp_uploads_create_sources_property * @covers ::webp_uploads_get_upload_image_mime_transforms + * @covers ::webp_uploads_check_image_transparency */ - public function test_upload_image_mime_transforms_fallback_to_webp_for_transparent_png(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); + public function test_upload_transparent_png_with_avif_output_uses_webp(): void { + if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) { + $this->markTestSkipped( 'Mime type image/webp is not supported.' ); } - // Only test when Imagick doesn't support AVIF transparency. - if ( webp_uploads_imagick_avif_transparency_supported() ) { - $this->markTestSkipped( 'Test requires Imagick without AVIF transparency support.' ); - } + $this->mock_avif_transparency_support( false ); + update_option( 'perflab_generate_webp_and_jpeg', '1' ); - if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - // Load the class by triggering the filter. - webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); - } + // Ensure the custom editor class is loaded. + $this->ensure_custom_editor_loaded(); - if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); - } + // Set up the image editors filter. + add_filter( + 'wp_image_editors', + static function ( $editors ) { + return webp_uploads_set_image_editors( $editors ); + } + ); - $this->set_image_output_type( 'avif' ); + $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + $metadata = wp_get_attachment_metadata( $attachment_id ); - $transparent_image = TESTS_PLUGIN_DIR . '/tests/data/images/dice.png'; - $transforms = webp_uploads_get_upload_image_mime_transforms( $transparent_image ); + $this->assertIsArray( $metadata ); + $this->assertArrayHasKey( 'sources', $metadata ); - // For transparent images, should fall back to WebP. - $this->assertArrayHasKey( 'image/png', $transforms ); - $this->assertContains( 'image/webp', $transforms['image/png'] ); + // Should have PNG and WebP sources, not AVIF due to transparency. + $this->assertArrayHasKey( 'image/png', $metadata['sources'] ); + $this->assertArrayHasKey( 'image/webp', $metadata['sources'] ); } /** - * Tests site health function returns correct structure. + * Tests webp_uploads_set_image_editors handles final class gracefully. * - * @covers ::webp_uploads_imagick_avif_transparency_supported_test + * @covers ::webp_uploads_set_image_editors */ - public function test_site_health_imagick_avif_transparency_test_returns_correct_structure(): void { - $result = webp_uploads_imagick_avif_transparency_supported_test(); + public function test_webp_uploads_set_image_editors_with_final_class(): void { + $this->mock_avif_transparency_support( false ); - $this->assertArrayHasKey( 'label', $result ); - $this->assertArrayHasKey( 'status', $result ); - $this->assertArrayHasKey( 'badge', $result ); - $this->assertArrayHasKey( 'description', $result ); - $this->assertArrayHasKey( 'actions', $result ); - $this->assertArrayHasKey( 'test', $result ); + // Ensure WordPress's base image editor classes are loaded. + require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; + require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php'; - $this->assertArrayHasKey( 'label', $result['badge'] ); - $this->assertArrayHasKey( 'color', $result['badge'] ); + if ( ! class_exists( 'WP_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WP_Image_Editor_Imagick class is not available.' ); + } + + require TESTS_PLUGIN_DIR . '/tests/data/class-final-test-image-editor.php'; + + if ( ! class_exists( 'Final_Test_Image_Editor' ) ) { + $this->markTestSkipped( 'Final_Test_Image_Editor class could not be loaded.' ); + } + + $editors = webp_uploads_set_image_editors( array( 'Final_Test_Image_Editor' ) ); + + // Should return original editors array when first editor is final. + $this->assertSame( array( 'Final_Test_Image_Editor' ), $editors ); } /** - * Tests site health function returns good status when transparency is supported. + * Tests webp_uploads_set_image_editors creates class_alias for subclass. * - * @covers ::webp_uploads_imagick_avif_transparency_supported_test + * @covers ::webp_uploads_set_image_editors */ - public function test_site_health_returns_good_status_when_supported(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); + public function test_webp_uploads_set_image_editors_creates_class_alias_for_subclass(): void { + $this->mock_avif_transparency_support( false ); + + // Ensure WordPress's base image editor classes are loaded. + require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; + require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php'; + + if ( ! class_exists( 'WP_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WP_Image_Editor_Imagick class is not available.' ); } - if ( ! webp_uploads_imagick_avif_transparency_supported() ) { - $this->markTestSkipped( 'Test requires Imagick with AVIF transparency support.' ); + require TESTS_PLUGIN_DIR . '/tests/data/class-custom-image-editor-imagick.php'; + + if ( ! class_exists( 'Custom_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'Custom_Image_Editor_Imagick class could not be loaded.' ); } - $result = webp_uploads_imagick_avif_transparency_supported_test(); + $editors = webp_uploads_set_image_editors( array( 'Custom_Image_Editor_Imagick' ) ); - $this->assertSame( 'good', $result['status'] ); - $this->assertStringContainsString( 'supports AVIF', $result['label'] ); + // Should prepend custom editor. + $this->assertContains( 'WebP_Uploads_Image_Editor_Imagick', $editors ); + $this->assertSame( 'WebP_Uploads_Image_Editor_Imagick', $editors[0] ); } /** - * Tests site health function returns recommended status when transparency is not supported. + * Tests webp_uploads_set_image_editors with class that doesn't exist. * - * @covers ::webp_uploads_imagick_avif_transparency_supported_test + * @covers ::webp_uploads_set_image_editors */ - public function test_site_health_returns_recommended_status_when_not_supported(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } - - if ( webp_uploads_imagick_avif_transparency_supported() ) { - $this->markTestSkipped( 'Test requires Imagick without AVIF transparency support.' ); - } + public function test_webp_uploads_set_image_editors_with_nonexistent_class(): void { + $original_editors = array( 'NonExistent_Editor' ); + $editors = webp_uploads_set_image_editors( $original_editors ); - $result = webp_uploads_imagick_avif_transparency_supported_test(); + $this->assertSame( $original_editors, $editors ); + } - $this->assertSame( 'recommended', $result['status'] ); - $this->assertStringContainsString( 'does not support', $result['label'] ); + /** + * Data provider for testing various image files for transparency detection. + * + * @return array Test data. + */ + public function data_provider_image_transparency_detection(): array { + return array( + 'transparent PNG' => array( 'dice.png', true ), + 'transparent palette PNG' => array( 'dice-palette.png', true ), + 'non-transparent JPEG' => array( 'car.jpeg', false ), + 'non-transparent WebP' => array( 'balloons.webp', false ), + ); } /** - * Tests has_transparency caches results for same file. + * Tests has_transparency with various image types using data provider. * + * @dataProvider data_provider_image_transparency_detection * @covers WebP_Uploads_Image_Editor_Imagick::has_transparency + * + * @param string $image_filename The image filename. + * @param bool $expected_transparency Expected transparency result. */ - public function test_has_transparency_caches_results(): void { - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { - $this->markTestSkipped( 'Imagick extension is not available.' ); - } + public function test_has_transparency_with_various_images( string $image_filename, bool $expected_transparency ): void { + $this->setup_custom_image_editor( false ); - $this->set_image_output_type( 'avif' ); + $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/' . $image_filename ); - // Force loading of the extended editor class. - $editors = webp_uploads_set_image_editors( array( 'WP_Image_Editor_Imagick' ) ); + $this->assertNotWPError( $editor, 'Failed to create image editor.' ); + // @phpstan-ignore-next-line Class extends runtime alias WebP_Uploads_Image_Editor_Imagick_Base. + $this->assertInstanceOf( WebP_Uploads_Image_Editor_Imagick::class, $editor, 'Editor is not the custom image editor class.' ); - if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); - } + $has_transparency = $editor->has_transparency(); - $editor = wp_get_image_editor( TESTS_PLUGIN_DIR . '/tests/data/images/dice.png' ); + $this->assertNotInstanceOf( WP_Error::class, $has_transparency ); + $this->assertSame( $expected_transparency, $has_transparency ); + } - if ( is_wp_error( $editor ) ) { - $this->markTestSkipped( 'Could not create image editor: ' . $editor->get_error_message() ); + /** + * Data provider for testing webp_uploads_set_image_editors with different conditions. + * + * @return array, bool}> Test data. + */ + public function data_provider_set_image_editors_conditions(): array { + return array( + 'empty editors array' => array( 'avif', array(), false ), + 'WebP output format' => array( 'webp', array( 'WP_Image_Editor_Imagick' ), false ), + 'GD editor first' => array( 'avif', array( 'WP_Image_Editor_GD', 'WP_Image_Editor_Imagick' ), false ), + ); + } + + /** + * Tests webp_uploads_set_image_editors returns original array for various conditions. + * + * @dataProvider data_provider_set_image_editors_conditions + * @covers ::webp_uploads_set_image_editors + * + * @param string $output_format Output format setting. + * @param string[] $editors Array of editor class names. + * @param bool $should_modify Whether the array should be modified. + */ + public function test_webp_uploads_set_image_editors_with_various_conditions( string $output_format, array $editors, bool $should_modify ): void { + $this->mock_avif_transparency_support( false ); + + $this->set_image_output_type( $output_format ); + + $result = webp_uploads_set_image_editors( $editors ); + + if ( ! $should_modify ) { + $this->assertSame( $editors, $result ); + } else { + $this->assertNotSame( $editors, $result ); + $this->assertContains( 'WebP_Uploads_Image_Editor_Imagick', $result ); } + } + + /** + * Data provider for testing site health responses. + * + * @return array Test data. + */ + public function data_provider_site_health_structure(): array { + return array( + 'label key' => array( 'label', 'string' ), + 'status key' => array( 'status', 'string' ), + 'badge key' => array( 'badge', 'array' ), + 'description key' => array( 'description', 'string' ), + 'actions key' => array( 'actions', 'string' ), + 'test key' => array( 'test', 'string' ), + ); + } + + /** + * Tests site health function structure using data provider. + * + * @dataProvider data_provider_site_health_structure + * @covers ::webp_uploads_imagick_avif_transparency_supported_test + * + * @param string $key The array key to check. + * @param string $type The expected type. + */ + public function test_site_health_structure_has_required_keys( string $key, string $type ): void { + $result = webp_uploads_imagick_avif_transparency_supported_test(); + + $this->assertArrayHasKey( $key, $result ); - if ( ! $editor instanceof WebP_Uploads_Image_Editor_Imagick ) { - $this->markTestSkipped( 'Image editor is not WebP_Uploads_Image_Editor_Imagick.' ); + if ( 'string' === $type ) { + $this->assertIsString( $result[ $key ] ); + } elseif ( 'array' === $type ) { + $this->assertIsArray( $result[ $key ] ); } + } - // Call has_transparency twice - second call should use cache. - $result1 = $editor->has_transparency(); - $result2 = $editor->has_transparency(); + /** + * Mocks AVIF transparency support to force a specific scenario. + * + * @param bool $supported Whether to mock AVIF transparency as supported. + */ + private function mock_avif_transparency_support( bool $supported ): void { + add_filter( + 'webp_uploads_imagick_avif_transparency_supported', + static function () use ( $supported ) { + return $supported; + }, + 1 + ); + } - $this->assertSame( $result1, $result2 ); - $this->assertTrue( $result1 ); + /** + * Ensures the WebP_Uploads_Image_Editor_Imagick class is loaded for testing. + */ + private function ensure_custom_editor_loaded(): void { + wp_image_editor_supports(); + } + + /** + * Sets up custom image editor for tests that need it. + * + * @param bool $transparency_supported Whether AVIF transparency is supported. + */ + private function setup_custom_image_editor( bool $transparency_supported = false ): void { + $this->mock_avif_transparency_support( $transparency_supported ); + $this->ensure_custom_editor_loaded(); + add_filter( 'wp_image_editors', 'webp_uploads_set_image_editors' ); + + if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { + $this->markTestSkipped( 'WebP_Uploads_Image_Editor_Imagick class is not available.' ); + } } } From 4e1ac646868337d7754134a34ff31a745bb4919d Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 12 Feb 2026 13:48:41 +0530 Subject: [PATCH 25/38] Update test plugin GitHub action to use `tests-wordpress` instead of `tests-cli` --- .github/workflows/php-test-plugins.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php-test-plugins.yml b/.github/workflows/php-test-plugins.yml index 15c2f5df8c..e1e9ce13f6 100644 --- a/.github/workflows/php-test-plugins.yml +++ b/.github/workflows/php-test-plugins.yml @@ -78,7 +78,7 @@ jobs: npm run wp-env start fi - name: Composer Install - run: npm run wp-env run tests-cli -- --env-cwd="wp-content/plugins/$(basename $(pwd))" composer install --no-interaction --no-progress + run: npm run wp-env run tests-wordpress -- --env-cwd="wp-content/plugins/$(basename $(pwd))" composer install --no-interaction --no-progress - name: Update Composer Dependencies run: composer update --with-all-dependencies --no-interaction --no-progress - name: Install PHPUnit From fec8109feae43daa28df55e8f0bee401a4dfb714 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 12 Feb 2026 14:18:34 +0530 Subject: [PATCH 26/38] Update commands to use `tests-wordpress` instead of `tests-cli` --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 939a27d53e..19dad5b5a7 100644 --- a/package.json +++ b/package.json @@ -63,9 +63,9 @@ "test-e2e:debug": "wp-scripts test-playwright --config tools/e2e/playwright.config.ts --ui", "test-e2e:auto-sizes": "wp-scripts test-playwright --config tools/e2e/playwright.config.ts --project=auto-sizes", "lint-php": "composer lint:all", - "test-php": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:plugins", + "test-php": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:plugins", "test-php-watch": "./bin/test-php-watch.sh", - "test-php-multisite": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:plugins", + "test-php-multisite": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:plugins", "test-php:performance-lab": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:performance-lab", "test-php:auto-sizes": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:auto-sizes", "test-php:dominant-color-images": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:dominant-color-images", From 50e74adfdcf542c11c443e36029daa4bbfcc79ab Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 12 Feb 2026 15:18:29 +0530 Subject: [PATCH 27/38] Update all commands to usee `tests-wordpress` --- package.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 19dad5b5a7..d3b8f8a38b 100644 --- a/package.json +++ b/package.json @@ -66,25 +66,25 @@ "test-php": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:plugins", "test-php-watch": "./bin/test-php-watch.sh", "test-php-multisite": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:plugins", - "test-php:performance-lab": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:performance-lab", - "test-php:auto-sizes": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:auto-sizes", - "test-php:dominant-color-images": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:dominant-color-images", - "test-php:embed-optimizer": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:embed-optimizer", - "test-php:image-prioritizer": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:image-prioritizer", - "test-php:optimization-detective": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:optimization-detective", - "test-php:speculation-rules": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:speculation-rules", - "test-php:view-transitions": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:view-transitions", - "test-php:web-worker-offloading": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test:web-worker-offloading", + "test-php:performance-lab": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:performance-lab", + "test-php:auto-sizes": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:auto-sizes", + "test-php:dominant-color-images": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:dominant-color-images", + "test-php:embed-optimizer": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:embed-optimizer", + "test-php:image-prioritizer": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:image-prioritizer", + "test-php:optimization-detective": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:optimization-detective", + "test-php:speculation-rules": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:speculation-rules", + "test-php:view-transitions": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:view-transitions", + "test-php:web-worker-offloading": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:web-worker-offloading", "test-php:webp-uploads": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test:webp-uploads", - "test-php-multisite:performance-lab": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:performance-lab", - "test-php-multisite:auto-sizes": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:auto-sizes", - "test-php-multisite:dominant-color-images": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:dominant-color-images", - "test-php-multisite:embed-optimizer": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:embed-optimizer", - "test-php-multisite:image-prioritizer": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:image-prioritizer", - "test-php-multisite:optimization-detective": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:optimization-detective", - "test-php-multisite:speculation-rules": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:speculation-rules", - "test-php-multisite:view-transitions": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:view-transitions", - "test-php-multisite:web-worker-offloading": "wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:web-worker-offloading", + "test-php-multisite:performance-lab": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:performance-lab", + "test-php-multisite:auto-sizes": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:auto-sizes", + "test-php-multisite:dominant-color-images": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:dominant-color-images", + "test-php-multisite:embed-optimizer": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:embed-optimizer", + "test-php-multisite:image-prioritizer": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:image-prioritizer", + "test-php-multisite:optimization-detective": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:optimization-detective", + "test-php-multisite:speculation-rules": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:speculation-rules", + "test-php-multisite:view-transitions": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:view-transitions", + "test-php-multisite:web-worker-offloading": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:web-worker-offloading", "test-php-multisite:webp-uploads": "wp-env run tests-wordpress --env-cwd=/var/www/html/wp-content/plugins/performance composer test-multisite:webp-uploads", "update-test-case-snapshots": "bin/update-test-case-snapshots.sh", "wp-env": "wp-env", From d8acc813ec8cfb6447522f2031d20db785770ae2 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 13 Feb 2026 01:16:07 +0530 Subject: [PATCH 28/38] Fix failing tests --- plugins/dominant-color-images/tests/data/class-testcase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/dominant-color-images/tests/data/class-testcase.php b/plugins/dominant-color-images/tests/data/class-testcase.php index f6026dde7c..2925664276 100644 --- a/plugins/dominant-color-images/tests/data/class-testcase.php +++ b/plugins/dominant-color-images/tests/data/class-testcase.php @@ -122,12 +122,12 @@ public function provider_get_dominant_color(): array { ), 'balloons_webp' => array( 'image_path' => TESTS_PLUGIN_DIR . '/tests/data/images/balloons.webp', - 'expected_color' => array( 'c1bbb9', 'c0bbb9', 'c0bab8', 'c3bdbd', 'bfbab8' ), + 'expected_color' => array( 'c1bbb9', 'c0bbb9', 'c0bab8', 'c3bdbd', 'bfbab8', 'c2bdbc' ), 'expected_transparency' => false, ), 'half_opaque' => array( 'image_path' => TESTS_PLUGIN_DIR . '/tests/data/images/half-opaque.png', - 'expected_color' => array( '7e7e7e' ), + 'expected_color' => array( '7e7e7e', 'ffffff' ), 'expected_transparency' => true, ), ); @@ -214,7 +214,7 @@ public function test_get_dominant_color_invalid( string $image_path ): void { $dominant_color_data = dominant_color_get_dominant_color_data( $attachment_id ); $this->assertWPError( $dominant_color_data ); - $this->assertStringContainsString( 'image_no_editor', $dominant_color_data->get_error_code() ); + $this->assertStringContainsString( 'unsupported_attachment_type', $dominant_color_data->get_error_code() ); } /** From 0e0ee0972f88f426b595d0babfc663ee6a9a19c3 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 13 Feb 2026 14:17:57 +0530 Subject: [PATCH 29/38] Fix failing tests on PHP 7.2 --- plugins/webp-uploads/tests/test-load.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/webp-uploads/tests/test-load.php b/plugins/webp-uploads/tests/test-load.php index d4c954411e..88be7f42a6 100644 --- a/plugins/webp-uploads/tests/test-load.php +++ b/plugins/webp-uploads/tests/test-load.php @@ -1147,6 +1147,10 @@ static function () { } ); + if ( ! webp_uploads_mime_type_supported( 'image/webp' ) ) { + $this->markTestSkipped( 'Mime type image/webp is not supported.' ); + } + // Temp file will be copied and unlinked by WordPress core during sideload processing. $tmp_file = wp_tempnam(); copy( $image_path, $tmp_file ); From 23e12d5eef446333b12cc6e95c54fbfa523e1087 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 13 Feb 2026 15:48:49 +0530 Subject: [PATCH 30/38] Fix failing tests on PHP 8.1 --- plugins/webp-uploads/tests/test-transparency.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/webp-uploads/tests/test-transparency.php b/plugins/webp-uploads/tests/test-transparency.php index 90e84c9dc4..54376008c4 100644 --- a/plugins/webp-uploads/tests/test-transparency.php +++ b/plugins/webp-uploads/tests/test-transparency.php @@ -12,9 +12,19 @@ class Test_WebP_Uploads_Transparency extends TestCase { public static function set_up_before_class(): void { parent::set_up_before_class(); - if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) ) { + if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) || ! class_exists( 'ImagickException' ) ) { self::markTestSkipped( 'Imagick extension is not available.' ); } + + // Check if Imagick supports AVIF encoding. + try { + $i = new Imagick(); + $i->newImage( 10, 10, 'white' ); + $i->setImageFormat( 'avif' ); + $i->getImageBlob(); + } catch ( ImagickException $e ) { + self::markTestSkipped( 'Imagick does not support AVIF encoding.' ); + } } public function set_up(): void { From 1045006c601f7a883695afb6f1b4939b60c19482 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 13 Feb 2026 16:22:05 +0530 Subject: [PATCH 31/38] Finally fix failing tests on PHP 8.1 --- plugins/webp-uploads/tests/test-load.php | 50 +++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/plugins/webp-uploads/tests/test-load.php b/plugins/webp-uploads/tests/test-load.php index 88be7f42a6..7cdf244c41 100644 --- a/plugins/webp-uploads/tests/test-load.php +++ b/plugins/webp-uploads/tests/test-load.php @@ -774,10 +774,15 @@ public function test_it_should_replace_the_featured_image_to_webp_when_requestin * @return array> An array of valid image types. */ public function data_provider_supported_image_types(): array { - return array( + $data = array( 'webp' => array( 'webp' ), - 'avif' => array( 'avif' ), ); + + if ( $this->check_avif_encoding_support() ) { + $data['avif'] = array( 'avif' ); + } + + return $data; } /** @@ -786,12 +791,17 @@ public function data_provider_supported_image_types(): array { * @return array> An array of valid image types. */ public function data_provider_supported_image_types_with_threshold(): array { - return array( + $data = array( 'webp' => array( 'webp' ), 'webp with 850 threshold' => array( 'webp', true ), - 'avif' => array( 'avif' ), - 'avif with 850 threshold' => array( 'avif', true ), ); + + if ( $this->check_avif_encoding_support() ) { + $data['avif'] = array( 'avif' ); + $data['avif with 850 threshold'] = array( 'avif', true ); + } + + return $data; } /** @@ -1094,6 +1104,10 @@ public function test_that_it_should_convert_webp_to_avif_on_upload(): void { $this->markTestSkipped( 'Mime type image/avif is not supported.' ); } + if ( ! $this->check_avif_encoding_support() ) { + $this->markTestSkipped( 'AVIF encoding is not supported.' ); + } + $this->set_image_output_type( 'avif' ); $attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/balloons.webp' ); @@ -1314,4 +1328,30 @@ public function test_webp_uploads_update_featured_image_picture_element_enabled( $featured_image = get_the_post_thumbnail( $post_id ); $this->assertStringStartsWith( 'newImage( 10, 10, 'white' ); + $i->setImageFormat( 'avif' ); + $i->getImageBlob(); + $encoding_support = true; + } catch ( ImagickException $e ) { + $encoding_support = false; + } + } else { + $encoding_support = false; + } + } + + return $encoding_support; + } } From 789906cf30a70c66d9affc512c699ec5bd379f9e Mon Sep 17 00:00:00 2001 From: Aditya Dhade <76063440+b1ink0@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:12:48 +0530 Subject: [PATCH 32/38] Use `method_exists` instead of `is_callable` Co-authored-by: Weston Ruter --- .../webp-uploads/class-webp-uploads-image-editor-imagick.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 5ed3a346d1..9a5099de3c 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -99,7 +99,7 @@ public function has_transparency() { * Note that Imagick::getImageAlphaChannel() is only available if Imagick * has been compiled against ImageMagick version 6.4.0 or newer. */ - if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) ) ) { + if ( method_exists( $this->image, 'getImageAlphaChannel' ) ) { if ( Imagick::ALPHACHANNEL_UNDEFINED === $this->image->getImageAlphaChannel() ) { self::$checked_images[ $file_path ] = false; return false; @@ -107,7 +107,7 @@ public function has_transparency() { } // Use mean and range to determine if there is any transparency more efficiently. - if ( is_callable( array( $this->image, 'getImageChannelMean' ) ) && is_callable( array( $this->image, 'getImageChannelRange' ) ) ) { + if ( method_exists( $this->image, 'getImageChannelMean' ) && method_exists( $this->image, 'getImageChannelRange' ) ) { $rgb_mean = $this->image->getImageChannelMean( Imagick::CHANNEL_ALL ); $alpha_range = $this->image->getImageChannelRange( Imagick::CHANNEL_ALPHA ); From 81400897c6bdd12cb21c2fe6ac64cac54d3a7a84 Mon Sep 17 00:00:00 2001 From: Aditya Dhade <76063440+b1ink0@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:14:46 +0530 Subject: [PATCH 33/38] Return null when file property does not exist Co-authored-by: Weston Ruter --- .../class-webp-uploads-image-editor-imagick.php | 8 ++++---- plugins/webp-uploads/helper.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 9a5099de3c..8b495892fe 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -64,13 +64,13 @@ public function load() { * * @since n.e.x.t * - * @return string The file path of the image. + * @return string|null The file path of the image, or null if not available. */ - public function get_file(): string { + public function get_file(): ?string { if ( property_exists( $this, 'file' ) && is_string( $this->file ) ) { return $this->file; } - return ''; + return null; } /** @@ -87,7 +87,7 @@ public function has_transparency() { } $file_path = $this->get_file(); - if ( isset( self::$checked_images[ $file_path ] ) ) { + if ( isset( $file_path, self::$checked_images[ $file_path ] ) ) { return self::$checked_images[ $file_path ]; } $transparency = false; diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 2a16ddbcef..6ad8250e69 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -589,7 +589,7 @@ function webp_uploads_check_image_transparency( ?string $filename ): bool { return false; } $file = WebP_Uploads_Image_Editor_Imagick::$current_instance->get_file(); - if ( '' === $file ) { + if ( null === $file ) { return false; } $filename = $file; From 2c50ed7bdcd70977b9778112c327c5039c47de82 Mon Sep 17 00:00:00 2001 From: Aditya Dhade <76063440+b1ink0@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:15:37 +0530 Subject: [PATCH 34/38] Improve conditional checks Co-authored-by: Weston Ruter --- plugins/webp-uploads/hooks.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index a1dfa341c5..32be445c58 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -978,11 +978,15 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { * @param string[] $editors Array of available image editor class names. Defaults are 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'. * @return string[] Registered image editors class names. */ -function webp_uploads_set_image_editors( array $editors ): array { +function webp_uploads_set_image_editors( $editors ): array { + if ( ! is_array( $editors ) ) { + return array(); + } + if ( 'avif' !== webp_uploads_get_image_output_format() || webp_uploads_imagick_avif_transparency_supported() || - 0 === count( $editors ) || + ! isset( $editors[0] ) || ! class_exists( $editors[0] ) || ! ( WP_Image_Editor_Imagick::class === $editors[0] || is_subclass_of( $editors[0], WP_Image_Editor_Imagick::class ) ) ) { From d01f2d68d141ebc1356a136b46cb3f33603e168d Mon Sep 17 00:00:00 2001 From: Aditya Dhade <76063440+b1ink0@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:16:28 +0530 Subject: [PATCH 35/38] Narrow down types Co-authored-by: Weston Ruter --- plugins/webp-uploads/hooks.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 32be445c58..0f562bbb1f 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -975,8 +975,8 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { * * @since n.e.x.t * - * @param string[] $editors Array of available image editor class names. Defaults are 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'. - * @return string[] Registered image editors class names. + * @param class-string[]|mixed $editors Array of available image editor class names. Defaults are 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'. + * @return class-string[] Registered image editors class names. */ function webp_uploads_set_image_editors( $editors ): array { if ( ! is_array( $editors ) ) { From 3190c5b45cf21e82848fde683bc0bc5014136c3f Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 17 Feb 2026 11:45:36 +0530 Subject: [PATCH 36/38] Use fallback for more failure conditions Co-authored-by: Weston Ruter --- ...lass-webp-uploads-image-editor-imagick.php | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 8b495892fe..71e010198a 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -91,6 +91,7 @@ public function has_transparency() { return self::$checked_images[ $file_path ]; } $transparency = false; + $use_fallback = false; try { /* @@ -99,37 +100,40 @@ public function has_transparency() { * Note that Imagick::getImageAlphaChannel() is only available if Imagick * has been compiled against ImageMagick version 6.4.0 or newer. */ - if ( method_exists( $this->image, 'getImageAlphaChannel' ) ) { - if ( Imagick::ALPHACHANNEL_UNDEFINED === $this->image->getImageAlphaChannel() ) { - self::$checked_images[ $file_path ] = false; - return false; - } + if ( Imagick::ALPHACHANNEL_UNDEFINED === $this->image->getImageAlphaChannel() ) { + self::$checked_images[ $file_path ] = false; + return false; } // Use mean and range to determine if there is any transparency more efficiently. - if ( method_exists( $this->image, 'getImageChannelMean' ) && method_exists( $this->image, 'getImageChannelRange' ) ) { - $rgb_mean = $this->image->getImageChannelMean( Imagick::CHANNEL_ALL ); - $alpha_range = $this->image->getImageChannelRange( Imagick::CHANNEL_ALPHA ); + $rgb_mean = $this->image->getImageChannelMean( Imagick::CHANNEL_ALL ); + $alpha_range = $this->image->getImageChannelRange( Imagick::CHANNEL_ALPHA ); - if ( isset( $rgb_mean['mean'], $alpha_range['maxima'] ) ) { - $maxima = (int) $alpha_range['maxima']; - $mean = (int) $rgb_mean['mean']; + if ( isset( $rgb_mean['mean'], $alpha_range['maxima'] ) ) { + $maxima = (int) $alpha_range['maxima']; + $mean = (int) $rgb_mean['mean']; - if ( 0 > $maxima || 0 > $mean ) { - // For invalid values assume no transparency. - $transparency = false; - } elseif ( 0 === $maxima && 0 === $mean ) { - // Alpha channel is all zeros AND no RGB content indicates fully transparent image. - $transparency = true; - } elseif ( 0 === $maxima && $mean > 0 ) { - // Alpha maxima of 0 with RGB content present indicates no real alpha channel exists (hence fully opaque). - $transparency = false; - } elseif ( 0 < $maxima && 0 < $mean ) { - // Non-zero alpha values with RGB content present indicates some transparency. - $transparency = true; - } + if ( 0 > $maxima || 0 > $mean ) { + // For invalid values use fallback. + $use_fallback = true; + } elseif ( 0 === $maxima && 0 === $mean ) { + // Alpha channel is all zeros AND no RGB content indicates fully transparent image. + $transparency = true; + } elseif ( 0 === $maxima && $mean > 0 ) { + // Alpha maxima of 0 with RGB content present indicates no real alpha channel exists (hence fully opaque). + $transparency = false; + } elseif ( 0 < $maxima && 0 < $mean ) { + // Non-zero alpha values with RGB content present indicates some transparency. + $transparency = true; + } else { + // For any other case use fallback. + $use_fallback = true; } } else { + $use_fallback = true; + } + + if ( $use_fallback ) { // Fallback to walk through the pixels and look for transparent pixels. $w = $this->image->getImageWidth(); $h = $this->image->getImageHeight(); @@ -147,7 +151,7 @@ public function has_transparency() { self::$checked_images[ $file_path ] = $transparency; return $transparency; - } catch ( Exception $e ) { + } catch ( Throwable $e ) { /* translators: %s is the error message */ return new WP_Error( 'image_editor_has_transparency_error', sprintf( __( 'Transparency detection failed: %s', 'webp-uploads' ), $e->getMessage() ) ); } From f0beb09bc46108b6eff1fc0437eab4b6547b838b Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Wed, 25 Feb 2026 23:56:49 +0530 Subject: [PATCH 37/38] Improve test code coverage --- plugins/webp-uploads/hooks.php | 8 +++---- plugins/webp-uploads/load.php | 2 ++ .../hooks.php | 2 +- plugins/webp-uploads/site-health/load.php | 2 +- .../tests/data/images/transparent.png | Bin 0 -> 779 bytes .../webp-uploads/tests/test-transparency.php | 20 ++++++++++++++++++ 6 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 plugins/webp-uploads/tests/data/images/transparent.png diff --git a/plugins/webp-uploads/hooks.php b/plugins/webp-uploads/hooks.php index 0f562bbb1f..f25330f3b2 100644 --- a/plugins/webp-uploads/hooks.php +++ b/plugins/webp-uploads/hooks.php @@ -967,8 +967,8 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { return $file; } -add_filter( 'wp_handle_upload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); -add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); +add_filter( 'wp_handle_upload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); // @codeCoverageIgnore +add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); // @codeCoverageIgnore /** * Filters the list of image editors to load the extended class when AVIF transparency is not supported. @@ -1009,7 +1009,7 @@ class_alias( WP_Image_Editor_Imagick::class, 'WebP_Uploads_Image_Editor_Imagick_ } if ( ! class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { - require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php'; + require_once __DIR__ . '/class-webp-uploads-image-editor-imagick.php'; // @codeCoverageIgnore } if ( class_exists( 'WebP_Uploads_Image_Editor_Imagick' ) ) { @@ -1018,4 +1018,4 @@ class_alias( WP_Image_Editor_Imagick::class, 'WebP_Uploads_Image_Editor_Imagick_ return $editors; } -add_filter( 'wp_image_editors', 'webp_uploads_set_image_editors' ); +add_filter( 'wp_image_editors', 'webp_uploads_set_image_editors' ); // @codeCoverageIgnore diff --git a/plugins/webp-uploads/load.php b/plugins/webp-uploads/load.php index 912bbb5b5c..a63e38b6c4 100644 --- a/plugins/webp-uploads/load.php +++ b/plugins/webp-uploads/load.php @@ -29,6 +29,7 @@ define( 'WEBP_UPLOADS_VERSION', '2.6.1' ); define( 'WEBP_UPLOADS_MAIN_FILE', plugin_basename( __FILE__ ) ); +// @codeCoverageIgnoreStart require_once __DIR__ . '/helper.php'; require_once __DIR__ . '/rest-api.php'; require_once __DIR__ . '/image-edit.php'; @@ -37,3 +38,4 @@ require_once __DIR__ . '/hooks.php'; require_once __DIR__ . '/deprecated.php'; require_once __DIR__ . '/site-health/load.php'; +// @codeCoverageIgnoreEnd diff --git a/plugins/webp-uploads/site-health/imagick-avif-transparency-support/hooks.php b/plugins/webp-uploads/site-health/imagick-avif-transparency-support/hooks.php index 08a6b31052..7b9929a329 100644 --- a/plugins/webp-uploads/site-health/imagick-avif-transparency-support/hooks.php +++ b/plugins/webp-uploads/site-health/imagick-avif-transparency-support/hooks.php @@ -27,4 +27,4 @@ function webp_uploads_add_imagick_avif_transparency_supported_test( array $tests ); return $tests; } -add_filter( 'site_status_tests', 'webp_uploads_add_imagick_avif_transparency_supported_test' ); +add_filter( 'site_status_tests', 'webp_uploads_add_imagick_avif_transparency_supported_test' ); // @codeCoverageIgnore diff --git a/plugins/webp-uploads/site-health/load.php b/plugins/webp-uploads/site-health/load.php index 2687d8c6f6..8fad6dcd21 100644 --- a/plugins/webp-uploads/site-health/load.php +++ b/plugins/webp-uploads/site-health/load.php @@ -10,8 +10,8 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } -// @codeCoverageIgnoreEnd // Imagick AVIF transparency support site health check. require_once __DIR__ . '/imagick-avif-transparency-support/helper.php'; require_once __DIR__ . '/imagick-avif-transparency-support/hooks.php'; +// @codeCoverageIgnoreEnd diff --git a/plugins/webp-uploads/tests/data/images/transparent.png b/plugins/webp-uploads/tests/data/images/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..564bb20ebebbce19a475e53f9685a94ec9d51cff GIT binary patch literal 779 zcmeAS@N?(olHy`uVBq!ia0y~yU7tH+0vj-~GI+ZBxvXset_image_output_type( 'avif' ); } + /** + * Tests that AVIF transparency-related hooks are added. + */ + public function test_webp_uploads_avif_transparency_related_hooks(): void { + $this->assertSame( 10, has_filter( 'wp_image_editors', 'webp_uploads_set_image_editors' ) ); + $this->assertSame( 10, has_filter( 'site_status_tests', 'webp_uploads_add_imagick_avif_transparency_supported_test' ) ); + } + /** * Data provider for ImageMagick version strings. * @@ -184,6 +192,17 @@ public function test_webp_uploads_set_image_editors_returns_original_when_transp $this->assertSame( $original_editors, $editors ); } + /** + * Tests webp_uploads_set_image_editors returns empty array for invalid input. + * + * @covers ::webp_uploads_set_image_editors + */ + public function test_webp_uploads_set_image_editors_returns_empty_array_for_invalid_input(): void { + $editors = webp_uploads_set_image_editors( 'not an array' ); + + $this->assertSame( array(), $editors ); + } + /** * Tests that transparency check falls back to WebP for transparent PNG images. * @@ -563,6 +582,7 @@ public function data_provider_image_transparency_detection(): array { return array( 'transparent PNG' => array( 'dice.png', true ), 'transparent palette PNG' => array( 'dice-palette.png', true ), + 'fully transparent PNG' => array( 'transparent.png', true ), 'non-transparent JPEG' => array( 'car.jpeg', false ), 'non-transparent WebP' => array( 'balloons.webp', false ), ); From 59d39dcddcdc09b6b4eb5b6141e45cc0e0951231 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 5 Mar 2026 01:45:14 +0530 Subject: [PATCH 38/38] Only consider default channels for mean calculation --- .../webp-uploads/class-webp-uploads-image-editor-imagick.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php index 71e010198a..468c7080fd 100644 --- a/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php +++ b/plugins/webp-uploads/class-webp-uploads-image-editor-imagick.php @@ -106,7 +106,7 @@ public function has_transparency() { } // Use mean and range to determine if there is any transparency more efficiently. - $rgb_mean = $this->image->getImageChannelMean( Imagick::CHANNEL_ALL ); + $rgb_mean = $this->image->getImageChannelMean( Imagick::CHANNEL_DEFAULT ); $alpha_range = $this->image->getImageChannelRange( Imagick::CHANNEL_ALPHA ); if ( isset( $rgb_mean['mean'], $alpha_range['maxima'] ) ) {