Skip to content

Commit 3bd51d1

Browse files
authored
Merge pull request #235 from WordPress/feature/update-secondary-image-onedit
Update secondary image mime types when editing original image
2 parents faa8156 + 5c829ef commit 3bd51d1

File tree

4 files changed

+256
-8
lines changed

4 files changed

+256
-8
lines changed

modules/images/webp-uploads/load.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,151 @@ function webp_uploads_update_rest_attachment( WP_REST_Response $response, WP_Pos
700700
}
701701
add_filter( 'rest_prepare_attachment', 'webp_uploads_update_rest_attachment', 10, 3 );
702702

703+
/**
704+
* Adds sources to metadata for an attachment.
705+
*
706+
* @since n.e.x.t
707+
*
708+
* @param array $metadata Metadata of the attachment.
709+
* @param array $valid_mime_transforms List of valid mime transforms for current image mime type.
710+
* @param array $main_images Path of all main image files of all mime types.
711+
* @param array $subsized_images Path of all subsized image file of all mime types.
712+
* @return array Metadata with sources added.
713+
*/
714+
function webp_uploads_update_sources( $metadata, $valid_mime_transforms, $main_images, $subsized_images ) {
715+
foreach ( $valid_mime_transforms as $targeted_mime ) {
716+
// Make sure the path and file exists as those values are being accessed.
717+
if ( ! isset( $main_images[ $targeted_mime ]['path'], $main_images[ $targeted_mime ]['file'] ) || ! file_exists( $main_images[ $targeted_mime ]['path'] ) ) {
718+
continue;
719+
}
720+
721+
$image_directory = pathinfo( $main_images[ $targeted_mime ]['path'], PATHINFO_DIRNAME );
722+
723+
// Add sources to original image metadata.
724+
$metadata['sources'][ $targeted_mime ] = array(
725+
'file' => $main_images[ $targeted_mime ]['file'],
726+
'filesize' => filesize( $main_images[ $targeted_mime ]['path'] ),
727+
);
728+
729+
foreach ( $metadata['sizes'] as $size_name => $size_details ) {
730+
if ( empty( $subsized_images[ $targeted_mime ][ $size_name ]['file'] ) ) {
731+
continue;
732+
}
733+
734+
// Add sources to resized image metadata.
735+
$subsize_path = path_join( $image_directory, $subsized_images[ $targeted_mime ][ $size_name ]['file'] );
736+
737+
if ( ! file_exists( $subsize_path ) ) {
738+
continue;
739+
}
740+
741+
$metadata['sizes'][ $size_name ]['sources'][ $targeted_mime ] = array(
742+
'file' => $subsized_images[ $targeted_mime ][ $size_name ]['file'],
743+
'filesize' => filesize( $subsize_path ),
744+
);
745+
}
746+
}
747+
748+
return $metadata;
749+
}
750+
751+
/**
752+
* Creates additional image formats when original image is edited.
753+
*
754+
* @since n.e.x.t
755+
*
756+
* @param bool|null $override Value to return instead of saving. Default null.
757+
* @param string $file_path Name of the file to be saved.
758+
* @param WP_Image_Editor $editor The image editor instance.
759+
* @param string $mime_type The mime type of the image.
760+
* @param int $post_id Attachment post ID.
761+
* @return bool|null Potentially modified $override value.
762+
*/
763+
function webp_uploads_update_image_onchange( $override, $file_path, $editor, $mime_type, $post_id ) {
764+
if ( null !== $override ) {
765+
return $override;
766+
}
767+
768+
$transforms = webp_uploads_get_upload_image_mime_transforms();
769+
if ( empty( $transforms[ $mime_type ] ) ) {
770+
return $override;
771+
}
772+
773+
$mime_transforms = $transforms[ $mime_type ];
774+
// This variable allows to unhook the logic from within the closure without the need fo a function name.
775+
$callback_executed = false;
776+
add_filter(
777+
'wp_update_attachment_metadata',
778+
function ( $metadata, $post_meta_id ) use ( $post_id, $file_path, $mime_type, $editor, $mime_transforms, &$callback_executed ) {
779+
if ( $post_meta_id !== $post_id ) {
780+
return $metadata;
781+
}
782+
783+
// This callback was already executed for this post, nothing to do at this point.
784+
if ( $callback_executed ) {
785+
return $metadata;
786+
}
787+
$callback_executed = true;
788+
789+
// No sizes to be created.
790+
if ( empty( $metadata['sizes'] ) ) {
791+
return $metadata;
792+
}
793+
794+
$old_metadata = wp_get_attachment_metadata( $post_id );
795+
$resize_sizes = array();
796+
foreach ( $old_metadata['sizes'] as $size_name => $size_details ) {
797+
if ( isset( $metadata['sizes'][ $size_name ] ) && ! empty( $metadata['sizes'][ $size_name ] ) &&
798+
$metadata['sizes'][ $size_name ]['file'] !== $old_metadata['sizes'][ $size_name ]['file'] ) {
799+
$resize_sizes[ $size_name ] = $metadata['sizes'][ $size_name ];
800+
}
801+
}
802+
803+
$allowed_mimes = array_flip( wp_get_mime_types() );
804+
$original_directory = pathinfo( $file_path, PATHINFO_DIRNAME );
805+
$filename = pathinfo( $file_path, PATHINFO_FILENAME );
806+
$main_images = array();
807+
$subsized_images = array();
808+
foreach ( $mime_transforms as $targeted_mime ) {
809+
if ( $targeted_mime === $mime_type ) {
810+
$main_images[ $targeted_mime ] = array(
811+
'path' => $file_path,
812+
'file' => pathinfo( $file_path, PATHINFO_BASENAME ),
813+
);
814+
$subsized_images[ $targeted_mime ] = $metadata['sizes'];
815+
continue;
816+
}
817+
818+
if ( ! isset( $allowed_mimes[ $targeted_mime ] ) || ! is_string( $allowed_mimes[ $targeted_mime ] ) ) {
819+
continue;
820+
}
821+
822+
if ( ! $editor::supports_mime_type( $targeted_mime ) ) {
823+
continue;
824+
}
825+
826+
$extension = explode( '|', $allowed_mimes[ $targeted_mime ] );
827+
$destination = trailingslashit( $original_directory ) . "{$filename}.{$extension[0]}";
828+
$result = $editor->save( $destination, $targeted_mime );
829+
830+
if ( is_wp_error( $result ) ) {
831+
continue;
832+
}
833+
834+
$main_images[ $targeted_mime ] = $result;
835+
$subsized_images[ $targeted_mime ] = $editor->multi_resize( $resize_sizes );
836+
}
837+
838+
return webp_uploads_update_sources( $metadata, $mime_transforms, $main_images, $subsized_images );
839+
},
840+
10,
841+
2
842+
);
843+
844+
return $override;
845+
}
846+
add_filter( 'wp_save_image_editor_file', 'webp_uploads_update_image_onchange', 10, 7 );
847+
703848
/**
704849
* Inspect if the current call to `wp_update_attachment_metadata()` was done from within the context
705850
* of an edit to an attachment either restore or other type of edit, in that case we perform operations

phpcs.xml.dist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
<file>./tests</file>
2020

2121
<!-- Do not require docblocks for unit tests -->
22+
<rule ref="Squiz.Commenting.FileComment.WrongStyle">
23+
<exclude-pattern>module-i18n.php</exclude-pattern>
24+
</rule>
2225
<rule ref="Squiz.Commenting.FunctionComment.Missing">
2326
<exclude-pattern>tests/*</exclude-pattern>
2427
</rule>

tests/modules/images/webp-uploads/webp-uploads-test.php

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -873,15 +873,31 @@ public function it_should_restore_the_sources_array_from_the_backup_when_an_imag
873873

874874
wp_restore_image( $attachment_id );
875875

876+
$this->assertImageHasSource( $attachment_id, 'image/jpeg' );
877+
$this->assertImageHasSource( $attachment_id, 'image/webp' );
878+
876879
$metadata = wp_get_attachment_metadata( $attachment_id );
877-
$this->assertArrayHasKey( 'sources', $metadata );
880+
878881
$this->assertSame( $backup_sources['full-orig'], $metadata['sources'] );
879882
$this->assertSame( $backup_sources, get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true ) );
880883

881884
$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
882-
foreach ( $metadata['sizes'] as $size_name => $properties ) {
883-
$this->assertArrayHasKey( 'sources', $backup_sizes[ $size_name . '-orig' ] );
884-
$this->assertSame( $backup_sizes[ $size_name . '-orig' ]['sources'], $properties['sources'] );
885+
foreach ( $backup_sizes as $size_name => $properties ) {
886+
// We are only interested in the original filenames to be compared against the backup and restored values.
887+
if ( false === strpos( $size_name, '-orig' ) ) {
888+
$this->assertSizeNameIsHashed( '', $size_name, "{$size_name} is not a valid edited name" );
889+
continue;
890+
}
891+
892+
$size_name = str_replace( '-orig', '', $size_name );
893+
// Full name is verified above.
894+
if ( 'full' === $size_name ) {
895+
continue;
896+
}
897+
898+
$this->assertArrayHasKey( $size_name, $metadata['sizes'] );
899+
$this->assertArrayHasKey( 'sources', $metadata['sizes'][ $size_name ] );
900+
$this->assertSame( $properties['sources'], $metadata['sizes'][ $size_name ]['sources'] );
885901
}
886902
}
887903

@@ -938,7 +954,17 @@ public function it_should_prevent_to_backup_the_full_size_image_if_only_the_thum
938954
$this->assertArrayHasKey( 'sources', $backup_sizes['thumbnail-orig'] );
939955

940956
$metadata = wp_get_attachment_metadata( $attachment_id );
941-
$this->assertArrayHasKey( 'sources', $metadata );
957+
958+
$this->assertImageHasSource( $attachment_id, 'image/jpeg' );
959+
$this->assertImageHasSource( $attachment_id, 'image/webp' );
960+
961+
$this->assertImageHasSizeSource( $attachment_id, 'thumbnail', 'image/jpeg' );
962+
$this->assertImageHasSizeSource( $attachment_id, 'thumbnail', 'image/webp' );
963+
964+
foreach ( $metadata['sizes'] as $size_name => $properties ) {
965+
$this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/jpeg' );
966+
$this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/webp' );
967+
}
942968
}
943969

944970
/**
@@ -959,7 +985,12 @@ public function it_should_backup_the_image_when_all_images_except_the_thumbnail_
959985
$this->assertArrayHasKey( 'full-orig', $backup_sources );
960986
$this->assertSame( $metadata['sources'], $backup_sources['full-orig'] );
961987

962-
$this->assertArrayNotHasKey( 'sources', wp_get_attachment_metadata( $attachment_id ), 'The sources attributes was not removed from the metadata.' );
988+
$updated_metadata = wp_get_attachment_metadata( $attachment_id );
989+
990+
$this->assertArrayHasKey( 'sources', $updated_metadata );
991+
$this->assertNotSame( $metadata['sources'], $updated_metadata['sources'] );
992+
$this->assertImageHasSource( $attachment_id, 'image/jpeg' );
993+
$this->assertImageHasSource( $attachment_id, 'image/webp' );
963994

964995
$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
965996
$this->assertIsArray( $backup_sizes );
@@ -973,6 +1004,39 @@ public function it_should_backup_the_image_when_all_images_except_the_thumbnail_
9731004
}
9741005
}
9751006

1007+
/**
1008+
* Update source attributes when webp is edited.
1009+
*
1010+
* @test
1011+
*/
1012+
public function it_should_validate_source_attribute_update_when_webp_edited() {
1013+
$attachment_id = $this->factory->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/testdata/modules/images/leafs.jpg' );
1014+
1015+
$editor = new WP_Image_Edit( $attachment_id );
1016+
$editor->crop( 1000, 200, 0, 0 )->save();
1017+
$this->assertTrue( $editor->success() );
1018+
1019+
$this->assertImageHasSource( $attachment_id, 'image/webp' );
1020+
$this->assertImageHasSource( $attachment_id, 'image/jpeg' );
1021+
1022+
$metadata = wp_get_attachment_metadata( $attachment_id );
1023+
1024+
$this->assertFileNameIsEdited( $metadata['sources']['image/webp']['file'] );
1025+
$this->assertFileNameIsEdited( $metadata['sources']['image/jpeg']['file'] );
1026+
1027+
$this->assertArrayHasKey( 'sources', $metadata );
1028+
$this->assertArrayHasKey( 'sizes', $metadata );
1029+
1030+
foreach ( $metadata['sizes'] as $size_name => $properties ) {
1031+
$this->assertArrayHasKey( 'sources', $properties );
1032+
$this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/webp' );
1033+
$this->assertImageHasSizeSource( $attachment_id, $size_name, 'image/jpeg' );
1034+
1035+
$this->assertFileNameIsEdited( $properties['sources']['image/webp']['file'] );
1036+
$this->assertFileNameIsEdited( $properties['sources']['image/jpeg']['file'] );
1037+
}
1038+
}
1039+
9761040
/**
9771041
* Allow the upload of a WebP image if at least one editor supports the format
9781042
*
@@ -1058,7 +1122,7 @@ public function it_should_u_se_the_next_available_hash_for_the_full_size_image_o
10581122
remove_filter( 'wp_update_attachment_metadata', 'webp_uploads_update_attachment_metadata' );
10591123

10601124
$editor->rotate_right()->save();
1061-
$this->assertRegExp( '/full-\d{13}/', webp_uploads_get_next_full_size_key_from_backup( $attachment_id ) );
1125+
$this->assertSizeNameIsHashed( 'full', webp_uploads_get_next_full_size_key_from_backup( $attachment_id ) );
10621126
}
10631127

10641128
/**
@@ -1106,7 +1170,7 @@ public function it_should_store_the_metadata_on_the_next_available_hash() {
11061170

11071171
$backup_sources_keys = array_keys( $backup_sources );
11081172
$this->assertSame( 'full-orig', reset( $backup_sources_keys ) );
1109-
$this->assertRegExp( '/full-\d{13}/', end( $backup_sources_keys ) );
1173+
$this->assertSizeNameIsHashed( 'full', end( $backup_sources_keys ) );
11101174
$this->assertSame( $sources, end( $backup_sources ) );
11111175
}
11121176

tests/utils/TestCase/ImagesTestCase.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
* @method void assertImageHasSizeSource( $attachment_id, $size, $mime_type, $message ) Asserts that the image has the appropriate source for the subsize.
1414
* @method void assertImageNotHasSource( $attachment_id, $mime_type, $message ) Asserts that the image doesn't have the appropriate source.
1515
* @method void assertImageNotHasSizeSource( $attachment_id, $size, $mime_type, $message ) Asserts that the image doesn't have the appropriate source for the subsize.
16+
* @method void assertFileNameIsEdited( string $filename, string $message = '' ) Asserts that the provided file name was edited by WordPress contains an e{WITH_13_DIGITS} on the filename.
17+
* @method void assertFileNameIsNotEdited( string $filename, string $message = '' ) Asserts that the provided file name was edited by WordPress contains an e{WITH_13_DIGITS} on the filename.
18+
* @method void assertSizeNameIsHashed( string $size_name, string $hashed_size_name, string $message = '' ) Asserts that the provided size name is an edited name that contains a hash with digits.
1619
*/
1720
abstract class ImagesTestCase extends WP_UnitTestCase {
1821

@@ -76,4 +79,37 @@ public static function assertImageNotHasSizeSource( $attachment_id, $size, $mime
7679
self::assertThat( $attachment_id, $constraint, $message );
7780
}
7881

82+
/**
83+
* Asserts that the provided file name was edited by WordPress contains an e{WITH_13_DIGITS} on the filename.
84+
*
85+
* @param string $filename The name of the filename to be asserted.
86+
* @param string $message The Error message used to display when the assertion fails.
87+
* @return void
88+
*/
89+
public static function assertFileNameIsEdited( $filename, $message = '' ) {
90+
self::assertRegExp( '/e\d{13}/', $filename, $message );
91+
}
92+
93+
/**
94+
* Asserts that the provided file name was edited by WordPress contains an e{WITH_13_DIGITS} on the filename.
95+
*
96+
* @param string $filename The name of the filename to be asserted.
97+
* @param string $message The Error message used to display when the assertion fails.
98+
* @return void
99+
*/
100+
public static function assertFileNameIsNotEdited( $filename, $message = '' ) {
101+
self::assertNotRegExp( '/e\d{13}/', $filename, $message );
102+
}
103+
104+
/**
105+
* Asserts that the provided size name is an edited name that contains a hash with digits.
106+
*
107+
* @param string $size_name The size name we are looking for.
108+
* @param string $hashed_size_name The current size name we are comparing against.
109+
* @param string $message The Error message used to display when the assertion fails.
110+
* @return void
111+
*/
112+
public static function assertSizeNameIsHashed( $size_name, $hashed_size_name, $message = '' ) {
113+
self::assertRegExp( "/{$size_name}-\d{13}/", $hashed_size_name, $message );
114+
}
79115
}

0 commit comments

Comments
 (0)