From c5d02161a16e1a237e46c1f297f2b7f30a6be2e0 Mon Sep 17 00:00:00 2001 From: tijmen Date: Mon, 22 Jun 2026 11:12:51 +0200 Subject: [PATCH 01/10] compress unscaled when original is active --- src/class-tiny-image.php | 13 ++++++++++++- src/class-tiny-settings.php | 12 +++++++++++- test/unit/TinySettingsAdminTest.php | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/class-tiny-image.php b/src/class-tiny-image.php index 89c07cbf..cd2909a3 100644 --- a/src/class-tiny-image.php +++ b/src/class-tiny-image.php @@ -19,7 +19,8 @@ */ class Tiny_Image { - const ORIGINAL = 0; + const ORIGINAL = 0; + const ORIGINAL_UNSCALED = 'original_unscaled'; /** @var Tiny_Settings */ private $settings; @@ -72,6 +73,12 @@ private function parse_wp_metadata() { $filename = $path_prefix . $this->name; $this->sizes[ self::ORIGINAL ] = new Tiny_Image_Size( $filename ); + if ( isset( $this->wp_metadata['original_image'] ) ) { + $this->sizes[ self::ORIGINAL_UNSCALED ] = new Tiny_Image_Size( + $path_prefix . $this->wp_metadata['original_image'] + ); + } + // Ensure 'sizes' exists and is an array to prevent PHP Warnings $sizes = isset( $this->wp_metadata['sizes'] ) && is_array( $this->wp_metadata['sizes'] ) ? $this->wp_metadata['sizes'] @@ -550,6 +557,10 @@ public static function is_original( $size ) { return self::ORIGINAL === $size; } + public static function is_original_unscaled( $size ) { + return self::ORIGINAL_UNSCALED === $size; + } + public static function is_retina( $size ) { return strrpos( $size, 'wr2x' ) === strlen( $size ) - strlen( 'wr2x' ); } diff --git a/src/class-tiny-settings.php b/src/class-tiny-settings.php index 415a24ab..9621e593 100644 --- a/src/class-tiny-settings.php +++ b/src/class-tiny-settings.php @@ -305,6 +305,16 @@ public function get_active_tinify_sizes() { $this->tinify_sizes[] = $size; } } + + /* + When the original is enabled, also include the unscaled original. + WordPress stores an unscaled copy of any uploaded image that exceeds + the big_image_size_threshold (default 2560 px). Images without such + a copy will simply skip this size in filter_image_sizes(). */ + if ( in_array( Tiny_Image::ORIGINAL, $this->tinify_sizes, true ) ) { + $this->tinify_sizes[] = Tiny_Image::ORIGINAL_UNSCALED; + } + return $this->tinify_sizes; } @@ -353,7 +363,7 @@ public function get_preserve_enabled( $name ) { } public function get_preserve_options( $size_name ) { - if ( ! Tiny_Image::is_original( $size_name ) ) { + if ( ! Tiny_Image::is_original( $size_name ) && ! Tiny_Image::is_original_unscaled( $size_name ) ) { return false; } $options = array(); diff --git a/test/unit/TinySettingsAdminTest.php b/test/unit/TinySettingsAdminTest.php index 3de0d81d..d7b7dab2 100644 --- a/test/unit/TinySettingsAdminTest.php +++ b/test/unit/TinySettingsAdminTest.php @@ -242,6 +242,26 @@ public function test_should_show_additional_size_without_width() { ); } + public function test_get_active_tinify_sizes_includes_original_unscaled_when_original_is_active() { + $this->wp->addOption( 'tinypng_sizes', array( Tiny_Image::ORIGINAL => 'on' ) ); + + $sizes = $this->subject->get_active_tinify_sizes(); + + $this->assertContains( Tiny_Image::ORIGINAL, $sizes ); + $this->assertContains( Tiny_Image::ORIGINAL_UNSCALED, $sizes ); + } + + public function test_get_active_tinify_sizes_excludes_original_unscaled_when_original_is_inactive() { + $this->wp->addOption( 'tinypng_sizes', array( + Tiny_Image::ORIGINAL => 'off', + 'medium' => 'on', + ) ); + + $sizes = $this->subject->get_active_tinify_sizes(); + + $this->assertNotContains( Tiny_Image::ORIGINAL_UNSCALED, $sizes ); + } + public function test_get_resize_enabled_should_return_true_if_enabled() { $this->wp->addOption( 'tinypng_resize_original', array( 'enabled' => 'on', From 2834a6909d23ebb9e927580f3b7d03e055cafe06 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 09:25:07 +0200 Subject: [PATCH 02/10] fix e2e --- test/integration/settings.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/settings.spec.ts b/test/integration/settings.spec.ts index fa5f1c73..e059c38f 100644 --- a/test/integration/settings.spec.ts +++ b/test/integration/settings.spec.ts @@ -181,13 +181,13 @@ test.describe('settings', () => { await page.locator('#submit').click(); - await expect(page.getByText('With these settings you can compress at least 125 images for free each month.')).toBeVisible(); + await expect(page.getByText('With these settings you can compress at least 100 images for free each month.')).toBeVisible(); }); test('update free compressions', async () => { await enableCompressionSizes(page, ['0', 'thumbnail', 'large'], false); - await expect(page.getByText('With these settings you can compress at least 166 images for free each month.')).toBeVisible(); + await expect(page.getByText('With these settings you can compress at least 125 images for free each month.')).toBeVisible(); }); test('show no compressions', async () => { From 9253b6d6439d56b1360ff208832f3f7109dfa66c Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 09:25:44 +0200 Subject: [PATCH 03/10] add unscaled to sizes --- src/class-tiny-settings.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/class-tiny-settings.php b/src/class-tiny-settings.php index 9621e593..42e2f9fb 100644 --- a/src/class-tiny-settings.php +++ b/src/class-tiny-settings.php @@ -253,10 +253,13 @@ protected static function get_intermediate_size( $size ) { } /** - * Retrieves image sizes as a map of size and width, height and tinify meta data - * The first entry will always be '0', aka the original uploaded image. + * Retrieves image sizes as a map of size and width, height and tinify meta data. * - * @return array{string: array{width: int|null, height: int|null, tinify: array{}}} $sizes + * The first entry will always be '0' (the original uploaded image), the second + * will always be 'original_unscaled' (the unscaled copy WordPress stores for images + * that exceed the big_image_size_threshold). Both entries share the same tinify setting. + * + * @return array */ public function get_sizes() { if ( is_array( $this->sizes ) ) { @@ -265,13 +268,19 @@ public function get_sizes() { $setting = get_option( self::get_prefixed_name( 'sizes' ) ); - $size = Tiny_Image::ORIGINAL; - $this->sizes = array( + $size = Tiny_Image::ORIGINAL; + $original_tinify = ! is_array( $setting ) || + ( isset( $setting[ $size ] ) && 'on' === $setting[ $size ] ); + $this->sizes = array( $size => array( 'width' => null, 'height' => null, - 'tinify' => ! is_array( $setting ) || - ( isset( $setting[ $size ] ) && 'on' === $setting[ $size ] ), + 'tinify' => $original_tinify, + ), + Tiny_Image::ORIGINAL_UNSCALED => array( + 'width' => null, + 'height' => null, + 'tinify' => $original_tinify, ), ); @@ -306,15 +315,6 @@ public function get_active_tinify_sizes() { } } - /* - When the original is enabled, also include the unscaled original. - WordPress stores an unscaled copy of any uploaded image that exceeds - the big_image_size_threshold (default 2560 px). Images without such - a copy will simply skip this size in filter_image_sizes(). */ - if ( in_array( Tiny_Image::ORIGINAL, $this->tinify_sizes, true ) ) { - $this->tinify_sizes[] = Tiny_Image::ORIGINAL_UNSCALED; - } - return $this->tinify_sizes; } From be420fce9cf5a5a46ec987178af03f4b08cb2b54 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 09:25:51 +0200 Subject: [PATCH 04/10] fix unit test --- test/unit/TinySettingsAdminTest.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/unit/TinySettingsAdminTest.php b/test/unit/TinySettingsAdminTest.php index d7b7dab2..f477f720 100644 --- a/test/unit/TinySettingsAdminTest.php +++ b/test/unit/TinySettingsAdminTest.php @@ -40,6 +40,11 @@ public function test_should_retrieve_sizes_with_settings() { 'height' => null, 'tinify' => true, ), + Tiny_Image::ORIGINAL_UNSCALED => array( + 'width' => null, + 'height' => null, + 'tinify' => true, + ), 'thumbnail' => array( 'width' => 150, 'height' => 150, @@ -96,6 +101,11 @@ public function test_should_not_retrieve_sizes_with_zero_width_and_height_values 'height' => null, 'tinify' => true, ), + Tiny_Image::ORIGINAL_UNSCALED => array( + 'width' => null, + 'height' => null, + 'tinify' => true, + ), 'thumbnail' => array( 'width' => 150, 'height' => 150, @@ -139,6 +149,11 @@ public function test_should_skip_dummy_size() { 'height' => null, 'tinify' => false, ), + Tiny_Image::ORIGINAL_UNSCALED => array( + 'width' => null, + 'height' => null, + 'tinify' => false, + ), 'thumbnail' => array( 'width' => 150, 'height' => 150, @@ -170,6 +185,11 @@ public function test_should_set_all_sizes_on_without_configuration() { 'height' => null, 'tinify' => true, ), + Tiny_Image::ORIGINAL_UNSCALED => array( + 'width' => null, + 'height' => null, + 'tinify' => true, + ), 'thumbnail' => array( 'width' => 150, 'height' => 150, From e6929be207c5dcf3b28f417de37ea8ce4ecfdaa3 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 09:39:31 +0200 Subject: [PATCH 05/10] format --- src/class-tiny-settings.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/class-tiny-settings.php b/src/class-tiny-settings.php index 42e2f9fb..a61bfb1d 100644 --- a/src/class-tiny-settings.php +++ b/src/class-tiny-settings.php @@ -272,7 +272,7 @@ public function get_sizes() { $original_tinify = ! is_array( $setting ) || ( isset( $setting[ $size ] ) && 'on' === $setting[ $size ] ); $this->sizes = array( - $size => array( + $size => array( 'width' => null, 'height' => null, 'tinify' => $original_tinify, @@ -363,7 +363,9 @@ public function get_preserve_enabled( $name ) { } public function get_preserve_options( $size_name ) { - if ( ! Tiny_Image::is_original( $size_name ) && ! Tiny_Image::is_original_unscaled( $size_name ) ) { + if ( ! Tiny_Image::is_original( $size_name ) && + ! Tiny_Image::is_original_unscaled( $size_name ) + ) { return false; } $options = array(); From 6d0d50e09535ed4f2c63417b508f83319ed9e97e Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 11:45:03 +0200 Subject: [PATCH 06/10] exclude original unscaled from checkbox --- src/class-tiny-settings.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/class-tiny-settings.php b/src/class-tiny-settings.php index a61bfb1d..8193db80 100644 --- a/src/class-tiny-settings.php +++ b/src/class-tiny-settings.php @@ -568,6 +568,10 @@ public function render_sizes() { esc_attr( $dummy_size_name ) . '" value="on"/>'; foreach ( $this->get_sizes() as $size => $option ) { + if ( Tiny_Image::is_original_unscaled( $size) ) { + // unscaled is selected implicitly by original size + continue; + } $this->render_size_checkboxes( $size, $option ); } if ( self::wr2x_active() ) { From 163e1ced01b07611d83fdabd777dbf8e94fb4434 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 11:45:12 +0200 Subject: [PATCH 07/10] add docs for check --- src/class-tiny-image.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/class-tiny-image.php b/src/class-tiny-image.php index cd2909a3..9b4dea40 100644 --- a/src/class-tiny-image.php +++ b/src/class-tiny-image.php @@ -557,6 +557,15 @@ public static function is_original( $size ) { return self::ORIGINAL === $size; } + + /** + * Check wether given $size is the original_unscaled image size. + * + * @since 3.6.14 + * + * @param string $size the size descriptor + * @return bool true if size is the original unscaled image + */ public static function is_original_unscaled( $size ) { return self::ORIGINAL_UNSCALED === $size; } From 30c272d3e959d3f9b60785a9fbbcf659e6e57484 Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 11:46:39 +0200 Subject: [PATCH 08/10] format --- src/class-tiny-settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/class-tiny-settings.php b/src/class-tiny-settings.php index 8193db80..5f156ab7 100644 --- a/src/class-tiny-settings.php +++ b/src/class-tiny-settings.php @@ -568,7 +568,7 @@ public function render_sizes() { esc_attr( $dummy_size_name ) . '" value="on"/>'; foreach ( $this->get_sizes() as $size => $option ) { - if ( Tiny_Image::is_original_unscaled( $size) ) { + if ( Tiny_Image::is_original_unscaled( $size ) ) { // unscaled is selected implicitly by original size continue; } From 6833a9b436998b2f7e8e2bb609ad41c3402fa61a Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 12:12:17 +0200 Subject: [PATCH 09/10] add basename to original image --- src/class-tiny-image.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/class-tiny-image.php b/src/class-tiny-image.php index 9b4dea40..e193a110 100644 --- a/src/class-tiny-image.php +++ b/src/class-tiny-image.php @@ -75,7 +75,7 @@ private function parse_wp_metadata() { if ( isset( $this->wp_metadata['original_image'] ) ) { $this->sizes[ self::ORIGINAL_UNSCALED ] = new Tiny_Image_Size( - $path_prefix . $this->wp_metadata['original_image'] + $path_prefix . wp_basename( $this->wp_metadata['original_image'] ) ); } @@ -91,7 +91,7 @@ private function parse_wp_metadata() { // Add to sanitized metadata $sanitized_sizes[ $size_name ] = $size_info; $this->sizes[ $size_name ] = new Tiny_Image_Size( - $path_prefix . $size_info['file'] + $path_prefix . wp_basename( $size_info['file'] ) ); } } From a9efc6c6afb9ae7cd55292644854924e2fab6bfe Mon Sep 17 00:00:00 2001 From: tijmen Date: Wed, 24 Jun 2026 12:12:24 +0200 Subject: [PATCH 10/10] add basename stub --- test/helpers/wordpress.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/helpers/wordpress.php b/test/helpers/wordpress.php index 2a52c2b6..82e6fa31 100644 --- a/test/helpers/wordpress.php +++ b/test/helpers/wordpress.php @@ -108,6 +108,7 @@ public function __construct($vfs) $this->addMethod('esc_html_e'); $this->addMethod('esc_html'); $this->addMethod('maybe_unserialize'); + $this->addMethod('wp_basename'); $this->defaults(); $this->create_filesystem(); } @@ -533,6 +534,18 @@ public function wp_json_encode($data, $options = 0, $depth = 512) return json_encode($data, $options, $depth); } + /** + * Mocked function for https://developer.wordpress.org/reference/functions/wp_basename/ + * + * @param string $path File path. + * @param string $suffix Optional. If the filename ends in suffix, this will be cut. Default empty string. + * @return string Trailing name component of the path. + */ + public function wp_basename($path, $suffix = '') + { + return urldecode(basename(str_replace(array('%2F', '%2f'), '/', urlencode($path)), $suffix)); + } + /** * Mocked function for https://developer.wordpress.org/reference/functions/esc_attr/ *