diff --git a/composer.json b/composer.json index 04a30aae22..8748adc857 100644 --- a/composer.json +++ b/composer.json @@ -34,5 +34,10 @@ "dealerdirect/phpcodesniffer-composer-installer": true, "composer/installers": true } + }, + "autoload-dev": { + "psr-4": { + "PerformanceLab\\Tests\\": "tests/utils" + } } } diff --git a/modules/images/webp-uploads/load.php b/modules/images/webp-uploads/load.php index 98da401090..81cacbb998 100644 --- a/modules/images/webp-uploads/load.php +++ b/modules/images/webp-uploads/load.php @@ -11,8 +11,8 @@ /** * Hook called by `wp_generate_attachment_metadata` to create the `sources` property for every image * size, the sources' property would create a new image size with all the mime types specified in - * `webp_uploads_get_supported_image_mime_transforms`. If the original image is one of the mimes from - * `webp_uploads_get_supported_image_mime_transforms` the image is just added to the `sources` property and not + * `webp_uploads_get_upload_image_mime_transforms`. If the original image is one of the mimes from + * `webp_uploads_get_upload_image_mime_transforms` the image is just added to the `sources` property and not * created again. If the uploaded attachment is not a supported mime by this function, the hook does not alter the * metadata of the attachment. In addition to every single size the `sources` property is added at the * top level of the image metadata to store the references for all the mime types for the `full` size image of the @@ -21,7 +21,7 @@ * @since 1.0.0 * * @see wp_generate_attachment_metadata() - * @see webp_uploads_get_supported_image_mime_transforms() + * @see webp_uploads_get_upload_image_mime_transforms() * * @param array $metadata An array with the metadata from this attachment. * @param int $attachment_id The ID of the attachment where the hook was dispatched. @@ -29,7 +29,8 @@ */ function webp_uploads_create_sources_property( array $metadata, $attachment_id ) { // This should take place only on the JPEG image. - $valid_mime_transforms = webp_uploads_get_supported_image_mime_transforms(); + $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms(); + // Not a supported mime type to create the sources property. $mime_type = get_post_mime_type( $attachment_id ); if ( ! isset( $valid_mime_transforms[ $mime_type ] ) ) { @@ -47,7 +48,10 @@ function webp_uploads_create_sources_property( array $metadata, $attachment_id ) $metadata['sources'] = array(); } - if ( empty( $metadata['sources'][ $mime_type ] ) ) { + if ( + empty( $metadata['sources'][ $mime_type ] ) && + in_array( $mime_type, $valid_mime_transforms[ $mime_type ], true ) + ) { $metadata['sources'][ $mime_type ] = array( 'file' => wp_basename( $file ), 'filesize' => filesize( $file ), @@ -64,6 +68,7 @@ function webp_uploads_create_sources_property( array $metadata, $attachment_id ) $original_directory = pathinfo( $file, PATHINFO_DIRNAME ); $filename = pathinfo( $file, PATHINFO_FILENAME ); $allowed_mimes = array_flip( wp_get_mime_types() ); + // Create the sources for the full sized image. foreach ( $valid_mime_transforms[ $mime_type ] as $targeted_mime ) { // If this property exists no need to create the image again. @@ -131,9 +136,7 @@ function webp_uploads_create_sources_property( array $metadata, $attachment_id ) wp_update_attachment_metadata( $attachment_id, $metadata ); } - $formats = isset( $valid_mime_transforms[ $current_mime ] ) ? $valid_mime_transforms[ $current_mime ] : array(); - - foreach ( $formats as $mime ) { + foreach ( $valid_mime_transforms[ $mime_type ] as $mime ) { // If this property exists no need to create the image again. if ( ! empty( $properties['sources'][ $mime ] ) ) { continue; @@ -154,9 +157,42 @@ function webp_uploads_create_sources_property( array $metadata, $attachment_id ) return $metadata; } - add_filter( 'wp_generate_attachment_metadata', 'webp_uploads_create_sources_property', 10, 2 ); +/** + * Filter the image editor default output format mapping to select the most appropriate + * output format depending on desired output formats and supported mime types by the image + * editor. + * + * @since n.e.x.t + * + * @param string $output_format The image editor default output format mapping. + * @param string $filename Path to the image. + * @param string $mime_type The source image mime type. + * @return string The new output format mapping. + */ +function webp_uploads_filter_image_editor_output_format( $output_format, $filename, $mime_type ) { + // Use the original mime type if this type is allowed. + $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms(); + if ( + ! isset( $valid_mime_transforms[ $mime_type ] ) || + in_array( $mime_type, $valid_mime_transforms[ $mime_type ], true ) + ) { + return $output_format; + } + + // Find the first supported mime type by the image editor to use it as the default one. + foreach ( $valid_mime_transforms[ $mime_type ] as $target_mime ) { + if ( wp_image_editor_supports( array( 'mime_type' => $target_mime ) ) ) { + $output_format[ $mime_type ] = $target_mime; + break; + } + } + + return $output_format; +} +add_filter( 'image_editor_output_format', 'webp_uploads_filter_image_editor_output_format', 10, 3 ); + /** * Creates a new image based of the specified attachment with a defined mime type * this image would be stored in the same place as the provided size name inside the @@ -217,30 +253,40 @@ function webp_uploads_generate_image_size( $attachment_id, $size, $mime ) { * * @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_supported_image_mime_transforms() { - $image_mime_transforms = array( - 'image/jpeg' => array( 'image/webp' ), - 'image/webp' => array( 'image/jpeg' ), +function webp_uploads_get_upload_image_mime_transforms() { + $default_transforms = array( + 'image/jpeg' => array( 'image/jpeg', 'image/webp' ), + 'image/webp' => array( 'image/webp', 'image/jpeg' ), ); - $valid_transforms = array(); /** * Filter to allow the definition of a custom mime types, in which a defined mime type * can be transformed and provide a wide range of mime types. * + * The order of supported mime types matters. If the original mime type of the uploaded image + * is not needed, then the first mime type in the list supported by the image editor will be + * selected for the default subsizes. + * * @since 1.0.0 * - * @param array $image_mime_transforms A map with the valid mime transforms. + * @param array $default_transforms A map with the valid mime transforms. */ - $transforms = (array) apply_filters( 'webp_uploads_supported_image_mime_transforms', $image_mime_transforms ); - // Remove any invalid transform, by making sure all the transform values are arrays. - foreach ( $transforms as $mime => $list_transforms ) { - if ( ! is_array( $list_transforms ) ) { - continue; + $transforms = (array) apply_filters( 'webp_uploads_upload_image_mime_transforms', $default_transforms ); + + // Return the default mime transforms if a non-array result is returned from the filter. + if ( ! is_array( $transforms ) ) { + return $default_transforms; + } + + // Ensure that all mime types have correct transforms. If a mime type has invalid transforms array, + // then fallback to the original mime type to make sure that the correct subsizes are created. + foreach ( $transforms as $mime_type => $transform_types ) { + if ( ! is_array( $transform_types ) || empty( $transform_types ) ) { + $transforms[ $mime_type ] = array( $mime_type ); } - $valid_transforms[ $mime ] = $list_transforms; } - return $valid_transforms; + + return $transforms; } /** @@ -416,7 +462,6 @@ function webp_uploads_remove_sources_files( $attachment_id ) { wp_delete_file_from_directory( $full_size_file, $intermediate_dir ); } } - add_action( 'delete_attachment', 'webp_uploads_remove_sources_files', 10, 1 ); /** @@ -463,7 +508,6 @@ function webp_uploads_wp_get_missing_image_subsizes( $missing_sizes, $image_meta return array(); } - add_filter( 'wp_get_missing_image_subsizes', 'webp_uploads_wp_get_missing_image_subsizes', 10, 3 ); /** @@ -524,6 +568,7 @@ function webp_uploads_update_image_references( $content ) { return $content; } +add_filter( 'the_content', 'webp_uploads_update_image_references', 10 ); /** * Finds all the urls with *.jpg and *.jpeg extension and updates with *.webp version for the provided image @@ -602,8 +647,6 @@ function webp_uploads_img_tag_update_mime_type( $image, $context, $attachment_id return $image; } -add_filter( 'the_content', 'webp_uploads_update_image_references', 10 ); - /** * Updates the response for an attachment to include sources for additional mime types available the image. * @@ -639,5 +682,4 @@ function webp_uploads_update_rest_attachment( WP_REST_Response $response, WP_Pos return rest_ensure_response( $data ); } - add_filter( 'rest_prepare_attachment', 'webp_uploads_update_rest_attachment', 10, 3 ); diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 29e34a94fa..f542112bef 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -46,4 +46,20 @@ tests/* + + + + tests/* + + + tests/* + + + + + tests/utils/* + + + tests/utils/* + diff --git a/tests/modules/images/webp-uploads/webp-uploads-test.php b/tests/modules/images/webp-uploads/webp-uploads-test.php index b8593bce7b..93084b0593 100644 --- a/tests/modules/images/webp-uploads/webp-uploads-test.php +++ b/tests/modules/images/webp-uploads/webp-uploads-test.php @@ -6,7 +6,9 @@ * @group webp-uploads */ -class WebP_Uploads_Tests extends WP_UnitTestCase { +use PerformanceLab\Tests\TestCase\ImagesTestCase; + +class WebP_Uploads_Tests extends ImagesTestCase { /** * Create the original mime type as well with all the available sources for the specified mime * @@ -17,32 +19,16 @@ class WebP_Uploads_Tests extends WP_UnitTestCase { public function it_should_create_the_original_mime_type_as_well_with_all_the_available_sources_for_the_specified_mime( $file_location, $expected_mime, $targeted_mime ) { $attachment_id = $this->factory->attachment->create_upload_object( $file_location ); - $metadata = wp_get_attachment_metadata( $attachment_id ); - - $this->assertIsArray( $metadata ); - $this->assertArrayHasKey( 'sources', $metadata ); - $this->assertIsArray( $metadata['sources'] ); - $this->assertArrayHasKey( $targeted_mime, $metadata['sources'] ); - $this->assertIsArray( $metadata['sources'][ $targeted_mime ] ); - $this->assertArrayHasKey( $expected_mime, $metadata['sources'] ); - $this->assertIsArray( $metadata['sources'][ $expected_mime ] ); - $this->assertArrayHasKey( 'file', $metadata['sources'][ $targeted_mime ] ); - $this->assertArrayHasKey( 'filesize', $metadata['sources'][ $targeted_mime ] ); - $this->assertArrayHasKey( 'file', $metadata['sources'][ $expected_mime ] ); - $this->assertArrayHasKey( 'filesize', $metadata['sources'][ $expected_mime ] ); + $this->assertImageHasSource( $attachment_id, $targeted_mime ); + $this->assertImageHasSource( $attachment_id, $expected_mime ); + $metadata = wp_get_attachment_metadata( $attachment_id ); $this->assertArrayHasKey( 'file', $metadata ); $this->assertStringEndsWith( $metadata['sources'][ $expected_mime ]['file'], $metadata['file'] ); - foreach ( $metadata['sizes'] as $size_name => $properties ) { - $this->assertArrayHasKey( 'sources', $properties ); - $this->assertIsArray( $properties['sources'] ); - $this->assertArrayHasKey( $expected_mime, $properties['sources'] ); - $this->assertArrayHasKey( 'filesize', $properties['sources'][ $expected_mime ] ); - $this->assertArrayHasKey( 'file', $properties['sources'][ $expected_mime ] ); - $this->assertArrayHasKey( $targeted_mime, $properties['sources'] ); - $this->assertArrayHasKey( 'filesize', $properties['sources'][ $targeted_mime ] ); - $this->assertArrayHasKey( 'file', $properties['sources'][ $targeted_mime ] ); + foreach ( array_keys( $metadata['sizes'] ) as $size_name ) { + $this->assertImageHasSizeSource( $attachment_id, $size_name, $targeted_mime ); + $this->assertImageHasSizeSource( $attachment_id, $size_name, $expected_mime ); } } @@ -66,7 +52,7 @@ public function provider_image_with_default_behaviors_during_upload() { * @test */ public function it_should_not_create_the_sources_property_if_no_transform_is_provided() { - add_filter( 'webp_uploads_supported_image_mime_transforms', '__return_empty_array' ); + add_filter( 'webp_uploads_upload_image_mime_transforms', '__return_empty_array' ); $attachment_id = $this->factory->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/leafs.jpg' @@ -76,7 +62,7 @@ public function it_should_not_create_the_sources_property_if_no_transform_is_pro $this->assertIsArray( $metadata ); $this->assertArrayNotHasKey( 'sources', $metadata ); - foreach ( $metadata['sizes'] as $size_name => $properties ) { + foreach ( $metadata['sizes'] as $properties ) { $this->assertArrayNotHasKey( 'sources', $properties ); } } @@ -88,7 +74,7 @@ public function it_should_not_create_the_sources_property_if_no_transform_is_pro */ public function it_should_create_the_sources_property_when_no_transform_is_available() { add_filter( - 'webp_uploads_supported_image_mime_transforms', + 'webp_uploads_upload_image_mime_transforms', function () { return array( 'image/jpeg' => array() ); } @@ -98,22 +84,13 @@ function () { TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/leafs.jpg' ); - $metadata = wp_get_attachment_metadata( $attachment_id ); - - $this->assertIsArray( $metadata ); - $this->assertArrayHasKey( 'sources', $metadata ); - $this->assertIsArray( $metadata['sources'] ); - $this->assertArrayHasKey( 'image/jpeg', $metadata['sources'] ); - $this->assertIsArray( $metadata['sources']['image/jpeg'] ); - $this->assertArrayNotHasKey( 'image/webp', $metadata['sources'] ); + $this->assertImageHasSource( $attachment_id, 'image/jpeg' ); + $this->assertImageNotHasSource( $attachment_id, 'image/webp' ); - foreach ( $metadata['sizes'] as $size_name => $properties ) { - $this->assertArrayHasKey( 'sources', $properties ); - $this->assertIsArray( $properties['sources'] ); - $this->assertArrayHasKey( 'image/jpeg', $properties['sources'] ); - $this->assertArrayHasKey( 'filesize', $properties['sources']['image/jpeg'] ); - $this->assertArrayHasKey( 'file', $properties['sources']['image/jpeg'] ); - $this->assertArrayNotHasKey( 'image/webp', $properties['sources'] ); + $metadata = wp_get_attachment_metadata( $attachment_id ); + foreach ( array_keys( $metadata['sizes'] ) as $size_name ) { + $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/jpeg' ); + $this->assertImageNotHasSizeSource( $attachment_id, $size_name, 'image/webp' ); } } @@ -124,7 +101,7 @@ function () { */ public function it_should_not_create_the_sources_property_if_the_mime_is_not_specified_on_the_transforms_images() { add_filter( - 'webp_uploads_supported_image_mime_transforms', + 'webp_uploads_upload_image_mime_transforms', function () { return array( 'image/jpeg' => array() ); } @@ -138,7 +115,7 @@ function () { $this->assertIsArray( $metadata ); $this->assertArrayNotHasKey( 'sources', $metadata ); - foreach ( $metadata['sizes'] as $size_name => $properties ) { + foreach ( $metadata['sizes'] as $properties ) { $this->assertArrayNotHasKey( 'sources', $properties ); } } @@ -280,31 +257,17 @@ public function it_should_create_a_webp_version_with_all_the_required_properties $file = get_attached_file( $attachment_id, true ); $dirname = pathinfo( $file, PATHINFO_DIRNAME ); - $this->assertArrayHasKey( 'image/jpeg', $metadata['sources'] ); - $this->assertIsArray( $metadata['sources']['image/jpeg'] ); - $this->assertArrayHasKey( 'file', $metadata['sources']['image/jpeg'] ); - $this->assertArrayHasKey( 'filesize', $metadata['sources']['image/jpeg'] ); + $this->assertImageHasSource( $attachment_id, 'image/jpeg' ); $this->assertStringEndsWith( $metadata['sources']['image/jpeg']['file'], $file ); $this->assertFileExists( path_join( $dirname, $metadata['sources']['image/jpeg']['file'] ) ); $this->assertSame( $metadata['sources']['image/jpeg']['filesize'], filesize( path_join( $dirname, $metadata['sources']['image/jpeg']['file'] ) ) ); - $this->assertArrayHasKey( 'image/webp', $metadata['sources'] ); - $this->assertIsArray( $metadata['sources']['image/webp'] ); - $this->assertArrayHasKey( 'file', $metadata['sources']['image/webp'] ); - $this->assertArrayHasKey( 'filesize', $metadata['sources']['image/webp'] ); - $this->assertStringEndsWith( '.webp', $metadata['sources']['image/webp']['file'] ); + $this->assertImageHasSource( $attachment_id, 'image/webp' ); $this->assertFileExists( path_join( $dirname, $metadata['sources']['image/webp']['file'] ) ); $this->assertSame( $metadata['sources']['image/webp']['filesize'], filesize( path_join( $dirname, $metadata['sources']['image/webp']['file'] ) ) ); - $this->assertArrayHasKey( 'sources', $metadata['sizes']['thumbnail'] ); - $this->assertArrayHasKey( 'image/jpeg', $metadata['sizes']['thumbnail']['sources'] ); - $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] ); - $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] ); - $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['thumbnail']['sources'] ); - $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/webp'] ); - $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/webp'] ); - $this->assertStringEndsNotWith( '.jpeg', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] ); - $this->assertStringEndsWith( '.webp', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] ); + $this->assertImageHasSizeSource( $attachment_id, 'thumbnail', 'image/jpeg' ); + $this->assertImageHasSizeSource( $attachment_id, 'thumbnail', 'image/webp' ); } /** @@ -319,13 +282,10 @@ public function it_should_create_the_full_size_images_when_no_size_is_available( $attachment_id = $this->factory->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/leafs.jpg' ); $metadata = wp_get_attachment_metadata( $attachment_id ); - $this->assertEmpty( $metadata['sizes'] ); - $this->assertArrayHasKey( 'sources', $metadata ); - $this->assertArrayHasKey( 'image/jpeg', $metadata['sources'] ); - $this->assertIsArray( $metadata['sources']['image/jpeg'] ); - $this->assertIsArray( $metadata['sources']['image/webp'] ); + $this->assertImageHasSource( $attachment_id, 'image/jpeg' ); + $this->assertImageHasSource( $attachment_id, 'image/webp' ); } /** @@ -347,7 +307,7 @@ function () { ); $metadata = wp_get_attachment_metadata( $attachment_id ); $this->assertStringEndsWith( '-scaled.jpg', get_attached_file( $attachment_id ) ); - $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['medium']['sources'] ); + $this->assertImageHasSizeSource( $attachment_id, 'medium', 'image/webp' ); $this->assertStringEndsNotWith( '-scaled.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] ); $this->assertStringEndsWith( '-300x200.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] ); } @@ -374,8 +334,7 @@ public function it_should_remove_the_generated_webp_images_when_the_attachment_i $this->assertFileExists( path_join( $dirname, $metadata['sources']['image/webp']['file'] ) ); foreach ( $sizes as $size_name ) { - $this->assertArrayHasKey( 'image/webp', $metadata['sizes'][ $size_name ]['sources'] ); - $this->assertArrayHasKey( 'file', $metadata['sizes'][ $size_name ]['sources']['image/webp'] ); + $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/webp' ); $this->assertFileExists( path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] ) ); } @@ -589,7 +548,7 @@ public function it_should_replace_all_the_images_including_the_full_size_image() * @test */ public function it_should_prevent_replacing_an_image_with_no_available_sources() { - add_filter( 'webp_uploads_supported_image_mime_transforms', '__return_empty_array' ); + add_filter( 'webp_uploads_upload_image_mime_transforms', '__return_empty_array' ); $attachment_id = $this->factory->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/car.jpeg' ); @@ -795,4 +754,33 @@ function () { ); } } + + /** + * Tests that we can force transformation from jpeg to webp by using the webp_uploads_upload_image_mime_transforms filter. + * + * @test + */ + public function it_should_transform_jpeg_to_webp_subsizes_using_transform_filter() { + remove_all_filters( 'webp_uploads_upload_image_mime_transforms' ); + + add_filter( + 'webp_uploads_upload_image_mime_transforms', + function( $transforms ) { + // Unset "image/jpeg" mime type for jpeg images. + unset( $transforms['image/jpeg'][ array_search( 'image/jpeg', $transforms['image/jpeg'], true ) ] ); + return $transforms; + } + ); + + $attachment_id = $this->factory->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/car.jpeg' ); + + $this->assertImageHasSource( $attachment_id, 'image/webp' ); + $this->assertImageNotHasSource( $attachment_id, 'image/jpeg' ); + + $metadata = wp_get_attachment_metadata( $attachment_id ); + foreach ( array_keys( $metadata['sizes'] ) as $size_name ) { + $this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/webp' ); + $this->assertImageNotHasSizeSource( $attachment_id, $size_name, 'image/jpeg' ); + } + } } diff --git a/tests/utils/Constraint/ImageHasSizeSource.php b/tests/utils/Constraint/ImageHasSizeSource.php new file mode 100644 index 0000000000..5009d296e1 --- /dev/null +++ b/tests/utils/Constraint/ImageHasSizeSource.php @@ -0,0 +1,67 @@ +size = $size; + } + + /** + * Returns a string representation of the constraint. + * + * @return string String representation of the constraint. + */ + public function toString(): string { + return sprintf( + '%s the source with the "%s" mime type for the "%s" size', + $this->is_not ? 'doesn\'t have' : 'has', + $this->mime_type, + $this->size + ); + } + + /** + * Evaluates the constraint for the provided attachment ID. + * + * @param int $attachment_id Attachment ID. + * @return bool TRUE if the attachment has a source with the requested mime type for the subsize, otherwise FALSE. + */ + protected function matches( $attachment_id ): bool { + $metadata = wp_get_attachment_metadata( $attachment_id ); + + // Fail if there is no metadata for the provided attachment ID. + if ( ! is_array( $metadata ) ) { + return false; + } + + // Fail if metadata doesn't contain the sources property. + if ( + ! isset( $metadata['sizes'][ $this->size ]['sources'] ) || + ! is_array( $metadata['sizes'][ $this->size ]['sources'] ) + ) { + return false; + } + + return $this->verify_sources( $metadata['sizes'][ $this->size ]['sources'] ); + } + +} diff --git a/tests/utils/Constraint/ImageHasSource.php b/tests/utils/Constraint/ImageHasSource.php new file mode 100644 index 0000000000..2c55f01e28 --- /dev/null +++ b/tests/utils/Constraint/ImageHasSource.php @@ -0,0 +1,140 @@ +is_not = false; + $this->mime_type = $mime_type; + } + + /** + * Tells to check for absence of the mime type. + */ + public function isNot() { + $this->is_not = true; + } + + /** + * Returns a string representation of the constraint. + * + * @return string String representation of the constraint. + */ + public function toString(): string { + return sprintf( + '%s the source with the "%s" mime type', + $this->is_not ? 'doesn\'t have' : 'has', + $this->mime_type + ); + } + + /** + * Evaluates the constraint for the provided attachment ID. + * + * @param int $attachment_id Attachment ID. + * @return bool TRUE if the attachment has a source for the requested mime type, otherwise FALSE. + */ + protected function matches( $attachment_id ): bool { + $metadata = wp_get_attachment_metadata( $attachment_id ); + + // Fail if there is no metadata for the provided attachment ID. + if ( ! is_array( $metadata ) ) { + return false; + } + + // Fail if metadata doesn't contain the sources property. + if ( + ! isset( $metadata['sources'] ) || + ! is_array( $metadata['sources'] ) + ) { + return false; + } + + return $this->verify_sources( $metadata['sources'] ); + } + + /** + * Verifies the sources to have the requested mime type. + * + * @param array $sources The sources array. + * @return bool TRUE if the sources array contains the correct mime type source, otherwise FALSE. + */ + protected function verify_sources( $sources ) { + // Fail if the mime type is supposed not to exist, but it is set. + if ( $this->is_not ) { + return ! isset( $sources[ $this->mime_type ] ); + } + + // Fail if metadata doesn't have the requested mime type in the sources property. + if ( + ! isset( $sources[ $this->mime_type ] ) || + ! is_array( $sources[ $this->mime_type ] ) + ) { + return false; + } + + // Fail if the file property is empty or not a string. + if ( + empty( $sources[ $this->mime_type ]['file'] ) || + ! is_string( $sources[ $this->mime_type ]['file'] ) + ) { + return false; + } + + // Fail if the file has wrong extension. + $allowed_mimes = array_flip( wp_get_mime_types() ); + if ( isset( $allowed_mimes[ $this->mime_type ] ) ) { + $extensions = explode( '|', $allowed_mimes[ $this->mime_type ] ); + $extension = pathinfo( $sources[ $this->mime_type ]['file'], PATHINFO_EXTENSION ); + if ( ! in_array( strtolower( $extension ), $extensions, true ) ) { + return false; + } + } + + // Fail if the filesize property is not set or it is not a number. + if ( + ! isset( $sources[ $this->mime_type ]['filesize'] ) || + ! is_numeric( $sources[ $this->mime_type ]['filesize'] ) + ) { + return false; + } + + return true; + } + + /** + * Returns the description of the failure. + * + * @param int $attachment_id Attachment ID. + * @return string The description of the failure. + */ + protected function failureDescription( $attachment_id ): string { + return sprintf( 'an image %s', $this->toString() ); + } + +} diff --git a/tests/utils/TestCase/ImagesTestCase.php b/tests/utils/TestCase/ImagesTestCase.php new file mode 100644 index 0000000000..22376def0a --- /dev/null +++ b/tests/utils/TestCase/ImagesTestCase.php @@ -0,0 +1,79 @@ +isNot(); + self::assertThat( $attachment_id, $constraint, $message ); + } + + /** + * Asserts that an image has a source with the specific mime type for a subsize. + * + * @since n.e.x.t + * + * @param int $attachment_id The attachment ID. + * @param string $size The subsize name. + * @param string $mime_type The mime type of the source. + * @param string $message An optional message to show on failure. + */ + public static function assertImageHasSizeSource( $attachment_id, $size, $mime_type, $message = '' ) { + $constraint = new ImageHasSizeSource( $mime_type, $size ); + self::assertThat( $attachment_id, $constraint, $message ); + } + + /** + * Asserts that an image doesn't have a source with the specific mime type for a subsize. + * + * @since n.e.x.t + * + * @param int $attachment_id The attachment ID. + * @param string $size The subsize name. + * @param string $mime_type The mime type of the source. + * @param string $message An optional message to show on failure. + */ + public static function assertImageNotHasSizeSource( $attachment_id, $size, $mime_type, $message = '' ) { + $constraint = new ImageHasSizeSource( $mime_type, $size ); + $constraint->isNot(); + self::assertThat( $attachment_id, $constraint, $message ); + } + +}