diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 0d98884c..132cc1b4 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -73,6 +73,7 @@ jobs: run: npm run test:playwright env: WORDPRESS_PORT: 80${{ matrix.wp }} + PHP_VERSION: ${{ matrix.php }} - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} diff --git a/readme.txt b/readme.txt index 3926b455..565e7c67 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://tinypng.com/ Tags: compress images, compression, image size, page speed, performance Requires at least: 4.0 Tested up to: 6.9 -Stable tag: 3.6.8 +Stable tag: 3.6.9 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -174,6 +174,9 @@ A: You can upgrade to a paid account by adding your *Payment details* on your [a A: When the conversion feature is enabled (to convert images to AVIF or WebP), each image will use double the number of credits: one for compression and one for format conversion. == Changelog == += 3.6.9 = +* fix: prevent picture element on product pages (WooCommerce) + = 3.6.8 = * feat: download logs and diagnostics * chore: updated phpcs rules and formatting diff --git a/src/class-tiny-picture.php b/src/class-tiny-picture.php index 1c9eaf9e..4806a715 100644 --- a/src/class-tiny-picture.php +++ b/src/class-tiny-picture.php @@ -38,13 +38,17 @@ class Tiny_Picture extends Tiny_WP_Base { /** @var array */ private $allowed_domains = array(); + /** @var Tiny_Settings */ + private $settings; + /** * Initialize the plugin. * * @param string $base_dir Absolute path (e.g. ABSPATH) * @param array $domains List of allowed domain URLs */ - public function __construct( $base_dir = ABSPATH, $domains = array() ) { + public function __construct( $settings, $base_dir = ABSPATH, $domains = array() ) { + $this->settings = $settings; $this->base_dir = $base_dir; $this->allowed_domains = $domains; @@ -64,12 +68,20 @@ public function __construct( $base_dir = ABSPATH, $domains = array() ) { return; } - add_action( - 'template_redirect', - function () { - ob_start( array( $this, 'replace_sources' ), 1000 ); - } - ); + add_action( 'template_redirect', array( $this, 'on_template_redirect' ) ); + } + + public function on_template_redirect() { + $conversion_enabled = $this->settings->get_conversion_enabled(); + if ( apply_filters( 'tiny_replace_with_picture', $conversion_enabled ) ) { + /** + * Controls wether the page should replace with elements + * converted sources. + * + * @since 3.6.9 + */ + ob_start( array( $this, 'replace_sources' ), 1000 ); + } } public function replace_sources( $content ) { diff --git a/src/class-tiny-plugin.php b/src/class-tiny-plugin.php index 0beec978..1fc976c8 100644 --- a/src/class-tiny-plugin.php +++ b/src/class-tiny-plugin.php @@ -72,18 +72,8 @@ public function init() { dirname( plugin_basename( __FILE__ ) ) . '/languages' ); - if ( $this->settings->get_conversion_enabled() ) { - /** - * Controls wether the page should replace with elements - * converted sources. - * - * @since 3.7.0 - */ - $should_replace = apply_filters( 'tiny_replace_with_picture', true ); - if ( $should_replace ) { - new Tiny_Picture( ABSPATH, array( get_site_url() ) ); - } - } + new Tiny_Picture( $this->settings, ABSPATH, array( get_site_url() ) ); + $this->tiny_compatibility(); } public function cli_init() { @@ -236,12 +226,14 @@ public function add_plugin_links( $current_links ) { public function tiny_compatibility() { if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { - $tiny_wpml_compatibility = new Tiny_WPML(); + new Tiny_WPML(); } if ( Tiny_AS3CF::is_active() ) { - $tiny_as3cf = new Tiny_AS3CF( $this->settings ); + new Tiny_AS3CF( $this->settings ); } + + new Tiny_WooCommerce(); } public function compress_original_retina_image( $attachment_id, $path ) { diff --git a/src/compatibility/as3cf/class-tiny-as3cf.php b/src/compatibility/as3cf/class-tiny-as3cf.php index 6ad7ca26..314f65e3 100644 --- a/src/compatibility/as3cf/class-tiny-as3cf.php +++ b/src/compatibility/as3cf/class-tiny-as3cf.php @@ -25,18 +25,14 @@ class Tiny_AS3CF { * Checks wether the lite version is active */ public static function lite_is_active() { - $lite_name = 'amazon-s3-and-cloudfront/wordpress-s3.php'; - - return is_plugin_active( $lite_name ); + return class_exists( 'Amazon_S3_And_CloudFront' ); } /** * Checks wether the pro version is active */ public static function pro_is_active() { - $pro_name = 'amazon-s3-and-cloudfront-pro/amazon-s3-and-cloudfront-pro.php'; - - return is_plugin_active( $pro_name ); + return class_exists( 'Amazon_S3_And_CloudFront_Pro' ); } public function __construct( $settings ) { diff --git a/src/compatibility/woocommerce/class-tiny-woocommerce.php b/src/compatibility/woocommerce/class-tiny-woocommerce.php new file mode 100644 index 00000000..3f0f7a66 --- /dev/null +++ b/src/compatibility/woocommerce/class-tiny-woocommerce.php @@ -0,0 +1,58 @@ +add_hooks(); + } + + private function add_hooks() { + add_filter( 'tiny_replace_with_picture', array( $this, 'skip_on_product_pages' ), 10, 1 ); + } + + /** + * We are skipping single product pages for now. + * Variation images in the product gallery are injected through JavaScript but might never + * display because the sourceset takes priority over the root img. The replacement is on the image and not + * on the srcset. + * + * @since 3.6.9 + * + * @param bool $should_replace Whether to replace images with picture elements. + * @return bool False on product pages, otherwise unchanged. + */ + public function skip_on_product_pages( $should_replace ) { + if ( is_product() ) { + return false; + } + + return $should_replace; + } +} diff --git a/test/integration/compatibility.spec.ts b/test/integration/compatibility.spec.ts index 935be5f5..17f015c0 100644 --- a/test/integration/compatibility.spec.ts +++ b/test/integration/compatibility.spec.ts @@ -1,5 +1,5 @@ import { Page, expect, test } from '@playwright/test'; -import { activatePlugin, clearMediaLibrary, deactivatePlugin, enableCompressionSizes, getWPVersion, setAPIKey, setCompressionTiming, uploadMedia } from './utils'; +import { activatePlugin, clearMediaLibrary, deactivatePlugin, enableCompressionSizes, getPHPVersion, getWPVersion, setAPIKey, setCompressionTiming, uploadMedia } from './utils'; test.describe.configure({ mode: 'serial' }); @@ -36,9 +36,16 @@ test.describe('as3cf', () => { test.beforeAll(async ({ browser }) => { page = await browser.newPage(); WPVersion = await getWPVersion(page); + const phpVersion = getPHPVersion(); - if (WPVersion < 5.5) { - // Skipping test as it WP Offload does not support WordPress < 5.5 + if (WPVersion < 5.9) { + // Skipping test as it WP Offload does not support WordPress < 5.9 + test.skip(); + return; + } + + if (phpVersion < 81) { + // Skipping test as WP Offload Media requires PHP 8.1+ test.skip(); return; } diff --git a/test/integration/utils.ts b/test/integration/utils.ts index 5bcfbc7c..1208da7a 100644 --- a/test/integration/utils.ts +++ b/test/integration/utils.ts @@ -160,6 +160,18 @@ export async function getWPVersion(page: Page): Promise { return parsedText; } +/** + * @returns {number} retrieves the current PHP version from environment variable + */ +export function getPHPVersion(): number { + const { PHP_VERSION } = process.env; + if (!PHP_VERSION) { + throw Error('PHP_VERSION is not set'); + } + + return +PHP_VERSION; +} + /** * @param {Page} page context * @param {string} pluginSlug slug of the plugin, ex 'tiny-compress-images' @@ -167,7 +179,7 @@ export async function getWPVersion(page: Page): Promise { export async function activatePlugin(page: Page, pluginSlug: string) { await page.goto('/wp-admin/plugins.php'); - const plugin = await page.locator('tr[data-slug="' + pluginSlug + '"]'); + const plugin = page.locator('tr[data-slug="' + pluginSlug + '"]').first(); if (!plugin) { throw Error(`Plug-in ${pluginSlug} not found. Are you sure it is installed?`); } @@ -192,7 +204,7 @@ export async function deactivatePlugin(page: Page, pluginSlug: string) { return; } - const plugin = await page.locator('tr[data-slug="' + pluginSlug + '"]'); + const plugin = await page.locator('tr[data-slug="' + pluginSlug + '"]').first(); const className = await plugin.getAttribute('class'); const pluginActivated = className === 'active'; if (!pluginActivated) { diff --git a/test/unit/TinyPictureTest.php b/test/unit/TinyPictureTest.php index 60713120..79ee7fda 100644 --- a/test/unit/TinyPictureTest.php +++ b/test/unit/TinyPictureTest.php @@ -12,7 +12,8 @@ public function set_up() { parent::set_up(); - $this->tiny_picture = new Tiny_Picture($this->vfs->url(), array('https://www.tinifytest.com')); + $settings = new Tiny_Settings(); + $this->tiny_picture = new Tiny_Picture($settings, $this->vfs->url(), array('https://www.tinifytest.com')); } /** @@ -335,7 +336,8 @@ public function test_does_not_register_hooks_when_pagebuilder_request() return false; }); - $tiny_picture = new Tiny_Picture($this->vfs->url(), array('https://www.tinifytest.com')); + $settings = new Tiny_Settings(); + $tiny_picture = new Tiny_Picture($settings, $this->vfs->url(), array('https://www.tinifytest.com')); $template_redirect_registered = false; foreach ($this->wp->getCalls('add_action') as $call) { diff --git a/test/unit/TinyTestCase.php b/test/unit/TinyTestCase.php index dab0ea46..4838a404 100644 --- a/test/unit/TinyTestCase.php +++ b/test/unit/TinyTestCase.php @@ -5,6 +5,9 @@ require_once dirname( __FILE__ ) . '/../helpers/wordpress.php'; require_once dirname( __FILE__ ) . '/../helpers/wordpress-cli.php'; require_once dirname( __FILE__ ) . '/../../src/config/class-tiny-config.php'; +require_once dirname( __FILE__ ) . '/../../src/compatibility/wpml/class-tiny-wpml.php'; +require_once dirname( __FILE__ ) . '/../../src/compatibility/as3cf/class-tiny-as3cf.php'; +require_once dirname( __FILE__ ) . '/../../src/compatibility/woocommerce/class-tiny-woocommerce.php'; require_once 'vendor/autoload.php'; use org\bovigo\vfs\vfsStream; diff --git a/test/unit/compatibility/TinyAC3SFTest.php b/test/unit/compatibility/TinyAC3SFTest.php index 42d6ce20..9fb95a7e 100644 --- a/test/unit/compatibility/TinyAC3SFTest.php +++ b/test/unit/compatibility/TinyAC3SFTest.php @@ -3,6 +3,9 @@ require_once dirname(__FILE__) . '/../TinyTestCase.php'; require_once dirname(__FILE__) . '/../../../src/compatibility/as3cf/class-tiny-as3cf.php'; +class Amazon_S3_And_CloudFront_Pro {} +class Amazon_S3_And_CloudFront {} + class Tiny_AC3SF_Test extends Tiny_TestCase { public function set_up() @@ -10,84 +13,39 @@ public function set_up() parent::set_up(); } + /** - * Stub is_plugin_active to return true for the pro plugin - * is_plugin_active will only work in admin area (after admin_init); + * Will check if AS3CF exists (pro version) */ - public function test_is_active_when_pro_is_enabled() + public function test_is_active_when_pro_is_available() { - $this->wp->stub( - 'is_plugin_active', - function ($plugin_name) { - return $plugin_name === 'amazon-s3-and-cloudfront-pro/amazon-s3-and-cloudfront-pro.php'; - } - ); - - $tiny_settings = new Tiny_Settings(); - $tiny_ac3sf = new Tiny_AS3CF($tiny_settings); - + $this->assertTrue(Tiny_AS3CF::pro_is_active()); $this->assertTrue(Tiny_AS3CF::is_active()); } + /** + * Will check if AS3CF exists (lite version) + * @see https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/blob/master/wordpress-s3.php + */ public function test_is_active_when_lite_is_enabled() { - $this->wp->stub( - 'is_plugin_active', - function ($plugin_name) { - return $plugin_name === 'amazon-s3-and-cloudfront/wordpress-s3.php'; - } - ); - - + // Amazon_S3_And_CloudFront stub class is defined at top of file + $this->assertTrue(Tiny_AS3CF::lite_is_active()); $this->assertTrue(Tiny_AS3CF::is_active()); } - public function test_is_not_active() - { - $this->wp->stub( - 'is_plugin_active', - function ($plugin_name) { - return false; - } - ); - - - $this->assertFalse(Tiny_AS3CF::is_active()); - } - public function test_remove_local_enabled_is_false_when_plugin_inactive() { - $this->wp->stub( - 'is_plugin_active', - function ($plugin_name) { - return false; - } - ); - $this->assertFalse(Tiny_AS3CF::remove_local_files_setting_enabled()); } public function test_remove_local_false_when_option_not_exists() { - $this->wp->stub( - 'is_plugin_active', - function ($plugin_name) { - return true; - } - ); - $this->assertFalse(Tiny_AS3CF::remove_local_files_setting_enabled()); } public function test_remove_local_false_when_option_is_false() { - $this->wp->stub( - 'is_plugin_active', - function ($plugin_name) { - return true; - } - ); - $this->wp->addOption('tantan_wordpress_s3', array( 'remove-local-file' => false, )); @@ -97,13 +55,6 @@ function ($plugin_name) { public function test_remove_local_true_when_option_is_true() { - $this->wp->stub( - 'is_plugin_active', - function ($plugin_name) { - return true; - } - ); - $this->wp->addOption('tantan_wordpress_s3', array( 'remove-local-file' => true, )); diff --git a/tiny-compress-images.php b/tiny-compress-images.php index 2e56b0c8..57b90c17 100644 --- a/tiny-compress-images.php +++ b/tiny-compress-images.php @@ -2,7 +2,7 @@ /** * Plugin Name: TinyPNG - JPEG, PNG & WebP image compression * Description: Speed up your website. Optimize your JPEG, PNG, and WebP images automatically with TinyPNG. - * Version: 3.6.8 + * Version: 3.6.9 * Author: TinyPNG * Author URI: https://tinypng.com * Text Domain: tiny-compress-images @@ -27,6 +27,7 @@ require dirname( __FILE__ ) . '/src/class-tiny-picture.php'; require dirname( __FILE__ ) . '/src/compatibility/wpml/class-tiny-wpml.php'; require dirname( __FILE__ ) . '/src/compatibility/as3cf/class-tiny-as3cf.php'; +require dirname( __FILE__ ) . '/src/compatibility/woocommerce/class-tiny-woocommerce.php'; if ( Tiny_PHP::client_supported() ) { require dirname( __FILE__ ) . '/src/class-tiny-compress-client.php';