diff --git a/src/class-tiny-image.php b/src/class-tiny-image.php index 89c07cbf..e193a110 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 . wp_basename( $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'] @@ -84,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'] ) ); } } @@ -550,6 +557,19 @@ 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; + } + 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..5f156ab7 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 => 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, ), ); @@ -305,6 +314,7 @@ public function get_active_tinify_sizes() { $this->tinify_sizes[] = $size; } } + return $this->tinify_sizes; } @@ -353,7 +363,9 @@ 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(); @@ -556,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() ) { 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/ * 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 () => { diff --git a/test/unit/TinySettingsAdminTest.php b/test/unit/TinySettingsAdminTest.php index 3de0d81d..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, @@ -242,6 +262,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',