From d45fe53b3f94551ccdefdac6a5d53f4cf4245fca Mon Sep 17 00:00:00 2001 From: tijmen Date: Thu, 17 Sep 2026 09:15:50 +0200 Subject: [PATCH] ensure conversion sizes are consistent --- src/class-tiny-image.php | 20 ++++----- test/unit/TinyImageTest.php | 87 +++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/src/class-tiny-image.php b/src/class-tiny-image.php index 60fac156..1306fa66 100644 --- a/src/class-tiny-image.php +++ b/src/class-tiny-image.php @@ -621,7 +621,8 @@ public function can_be_converted() { /** * Get the targeted conversion. - * If original is already converted, then we use the originals' mimetype. + * If a size is already converted, then we use that size's mimetype so + * sizes never mix formats, even when the settings changed since. * If nothing is converted yet, we use the settings conversion settings. * * @since 3.6.4 @@ -635,15 +636,14 @@ private function convert_to() { return array(); } - if ( isset( $this->sizes[ self::ORIGINAL ] ) ) { - // original is not in sizes so mimetypes are open - return $convert_settings['convert_to']; - } - - $original_img_size = $this->sizes[ self::ORIGINAL ]; - if ( $original_img_size->converted() ) { - // original has been convert so use that mimetype to convert to - return array( $original_img_size->meta['convert']['type'] ); + foreach ( $this->sizes as $size ) { + if ( ! $size->converted() ) { + continue; + } + $type = $size->meta['convert']['type']; + if ( in_array( $type, array( 'image/avif', 'image/webp' ), true ) ) { + return array( $type ); + } } return $convert_settings['convert_to']; diff --git a/test/unit/TinyImageTest.php b/test/unit/TinyImageTest.php index df4072b3..bbffeed2 100644 --- a/test/unit/TinyImageTest.php +++ b/test/unit/TinyImageTest.php @@ -1,5 +1,7 @@ assertEquals(array('image/webp'), $compress_calls[1]['convert_to']); } + /** + * A size added after the original was converted gets the original's format. + */ + public function test_later_compression_converts_to_format_of_earlier_conversion() + { + $this->wp->addOption('tinypng_convert_format', array( + 'convert' => 'on', + 'convert_to' => 'smallest', + )); + $this->wp->addOption('tinypng_sizes', array( + Tiny_Image::ORIGINAL => 'on', + 'thumbnail' => 'on', + )); + $this->wp->stub('get_post_mime_type', function () { + return 'image/png'; + }); + $this->wp->createImages(array()); + + $settings = new Tiny_Settings(); + $mock_compressor = $this->createMock(Tiny_Compress::class); + $convert_to_calls = array(); + $mock_compressor->method('compress_file') + ->willReturnCallback(function ($file, $resize, $preserve, $convert_to) use (&$convert_to_calls) { + $convert_to_calls[] = $convert_to; + return array( + 'input' => array('size' => 12345), + 'output' => array('size' => 12345, 'type' => 'image/png'), + 'convert' => array('type' => 'image/avif', 'size' => 9000, 'path' => 'vfs://root/test.avif'), + ); + }); + $settings->set_compressor($mock_compressor); + + // first run: only the original exists + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->compress(); + + // second run: a thumbnail has been added since + $this->wp->createImage(1000, '14/01', 'test-thumbnail.png'); + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->compress(); + + + assertEquals(array('image/avif', 'image/webp'), $convert_to_calls[0], 'original can be converted to avif and webp'); + assertEquals(array('image/avif'), $convert_to_calls[1], 'original output was avif so expect subsequent sizes to avif'); + } + + /** + * Marking as compressed records the original's own mimetype as conversion, + * a size added afterwards must not be converted to that mimetype. + */ + public function test_compression_after_mark_as_compressed_uses_conversion_settings() + { + $this->wp->addOption('tinypng_convert_format', array( + 'convert' => 'on', + 'convert_to' => 'smallest', + )); + $this->wp->addOption('tinypng_sizes', array( + Tiny_Image::ORIGINAL => 'on', + 'thumbnail' => 'on', + )); + $this->wp->stub('get_post_mime_type', function () { + return 'image/png'; + }); + $this->wp->createImages(array()); + + $settings = new Tiny_Settings(); + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->mark_as_compressed(); + + $mock_compressor = $this->createMock(Tiny_Compress::class); + + // assert that + $mock_compressor->expects($this->once()) + ->method('compress_file') + ->with($this->anything(), $this->anything(), $this->anything(), array('image/avif', 'image/webp')) + ->willReturn(array( + 'input' => array('size' => 1000), + 'output' => array('size' => 1000, 'type' => 'image/png'), + )); + $settings->set_compressor($mock_compressor); + + $this->wp->createImage(1000, '14/01', 'test-thumbnail.png'); + $tinyimg = new Tiny_Image($settings, 999, $this->wp->getTestMetadata()); + $tinyimg->compress(); + } }