diff --git a/phpcs.xml b/phpcs.xml
index 7a317ae8..6725daba 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -10,9 +10,6 @@
-
-
-
diff --git a/src/class-tiny-helpers.php b/src/class-tiny-helpers.php
index 51c678c3..dc4f41fb 100644
--- a/src/class-tiny-helpers.php
+++ b/src/class-tiny-helpers.php
@@ -111,36 +111,6 @@ public static function get_mimetype( $input ) {
}
- /**
- * Checks wether a user is viewing from a page builder
- *
- * @since 3.6.5
- */
- public static function is_pagebuilder_request() {
- $pagebuilder_keys = array(
- 'fl_builder', // Beaver Builder
- 'et_fb', // Divi Builder
- 'bricks', // Bricks Builder
- 'breakdance', // Breakdance Builder
- 'breakdance_browser', // Breakdance Builder
- 'ct_builder', // Oxygen Builder
- 'fb-edit', // Avada Live Builder
- 'builder', // Avada Live Builder
- 'spio_no_cdn', // Site Origin
- 'tatsu', // Tatsu Builder
- 'tve', // Thrive Architect
- 'tcbf', // Thrive Architect
- );
-
- foreach ( $pagebuilder_keys as $key ) {
- if ( isset( $_GET[ $key ] ) ) {
- return true;
- }
- }
-
- return false;
- }
-
/**
* Gets or initializes the WordPress filesystem instance.
*
diff --git a/src/class-tiny-notices.php b/src/class-tiny-notices.php
index d946d813..6ca2aac6 100644
--- a/src/class-tiny-notices.php
+++ b/src/class-tiny-notices.php
@@ -148,7 +148,7 @@ public function remove( $name ) {
}
public function dismiss() {
- if ( empty( $_POST['name'] ) || ! $this->check_ajax_referer() ) {
+ if ( ! check_ajax_referer( 'tiny-compress', '_nonce', false ) || empty( $_POST['name'] ) ) {
echo json_encode( false );
exit();
}
diff --git a/src/class-tiny-picture.php b/src/class-tiny-picture.php
index ae3f5e41..f32e3d25 100644
--- a/src/class-tiny-picture.php
+++ b/src/class-tiny-picture.php
@@ -65,13 +65,62 @@ public function __construct( $settings, $base_dir = ABSPATH, $domains = array()
return;
}
- if ( Tiny_Helpers::is_pagebuilder_request() ) {
+ if ( static::is_pagebuilder_request() ) {
return;
}
add_action( 'template_redirect', array( $this, 'on_template_redirect' ) );
}
+ /**
+ * Checks whether the current request originates from a page builder.
+ *
+ * Detects known page builder query parameters to prevent picture-element
+ * injection from interfering with builder previews.
+ *
+ * @since 3.6.5
+ *
+ * @return bool True if a page builder query parameter is present.
+ */
+ protected static function is_pagebuilder_request() {
+ $pagebuilder_keys = array(
+ 'fl_builder', // Beaver Builder
+ 'et_fb', // Divi Builder
+ 'bricks', // Bricks Builder
+ 'breakdance', // Breakdance Builder
+ 'breakdance_browser', // Breakdance Builder
+ 'ct_builder', // Oxygen Builder
+ 'fb-edit', // Avada Live Builder
+ 'builder', // Avada Live Builder
+ 'spio_no_cdn', // Site Origin
+ 'tatsu', // Tatsu Builder
+ 'tve', // Thrive Architect
+ 'tcbf', // Thrive Architect
+ );
+
+ foreach ( $pagebuilder_keys as $key ) {
+ if ( static::has_get_var( $key ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether a GET variable exists in the original request.
+ *
+ * Wraps filter_has_var() to allow overriding in tests via late static binding.
+ *
+ * @since 3.6.5
+ *
+ * @param string $key The query string key to check.
+ * @return bool True if the key exists in the original GET request.
+ */
+ protected static function has_get_var( $key ) {
+ return filter_has_var( INPUT_GET, $key );
+ }
+
public function on_template_redirect() {
$conversion_enabled = $this->settings->get_conversion_enabled();
if ( apply_filters( 'tiny_replace_with_picture', $conversion_enabled ) ) {
diff --git a/src/class-tiny-plugin.php b/src/class-tiny-plugin.php
index b68a10b9..e9e23ec1 100644
--- a/src/class-tiny-plugin.php
+++ b/src/class-tiny-plugin.php
@@ -517,9 +517,8 @@ public function compress_on_upload() {
* or success array ['data' => [$id, $metadata]]
*/
private function validate_ajax_attachment_request() {
- if ( ! $this->check_ajax_referer() ) {
- exit();
- }
+ check_ajax_referer( 'tiny-compress', '_nonce' );
+
if ( ! current_user_can( 'upload_files' ) ) {
return array(
'error' => esc_html__(
@@ -614,11 +613,14 @@ public function compress_image_for_bulk() {
);
wp_update_attachment_metadata( $id, $tiny_image->get_wp_metadata() );
+ // Nonce verified in validate_ajax_attachment_request().
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
$current_library_size = isset( $_POST['current_size'] ) ?
intval( wp_unslash( $_POST['current_size'] ) )
: 0;
- $size_after = $image_statistics['compressed_total_size'];
- $new_library_size = $current_library_size + $size_after - $size_before;
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
+ $size_after = $image_statistics['compressed_total_size'];
+ $new_library_size = $current_library_size + $size_after - $size_before;
$result['message'] = $tiny_image->get_latest_error();
$result['image_sizes_compressed'] = $image_statistics['image_sizes_compressed'];
@@ -655,7 +657,8 @@ public function compress_image_for_bulk() {
}
public function ajax_optimization_statistics() {
- if ( $this->check_ajax_referer() && current_user_can( 'upload_files' ) ) {
+ if ( check_ajax_referer( 'tiny-compress', '_nonce', false ) &&
+ current_user_can( 'upload_files' ) ) {
$stats = Tiny_Bulk_Optimization::get_optimization_statistics( $this->settings );
echo json_encode( $stats );
}
@@ -703,6 +706,7 @@ public function media_library_bulk_action() {
$location = 'upload.php?mode=list&ids=' . $ids;
$location = add_query_arg( 'action', $action, $location );
+ $location = add_query_arg( '_tiny_nonce', wp_create_nonce( 'tiny-bulk-ids' ), $location );
if ( ! empty( $_REQUEST['paged'] ) ) {
$location = add_query_arg( 'paged', absint( $_REQUEST['paged'] ), $location );
@@ -758,6 +762,23 @@ public function show_media_info() {
}
private function render_compress_details( $tiny_image ) {
+ $images_to_compress = array();
+
+ if ( ! empty( $_REQUEST['ids'] ) ) {
+ if (
+ ! isset( $_REQUEST['_tiny_nonce'] ) ||
+ ! wp_verify_nonce(
+ sanitize_key( wp_unslash( $_REQUEST['_tiny_nonce'] ) ),
+ 'tiny-bulk-ids'
+ )
+ ) {
+ return;
+ }
+
+ $request_ids = sanitize_text_field( wp_unslash( $_REQUEST['ids'] ) );
+ $images_to_compress = array_map( 'intval', explode( '-', $request_ids ) );
+ }
+
$in_progress = $tiny_image->filter_image_sizes( 'in_progress' );
if ( count( $in_progress ) > 0 ) {
include __DIR__ . '/views/compress-details-processing.php';
diff --git a/src/class-tiny-settings.php b/src/class-tiny-settings.php
index 58d6049a..415a24ab 100644
--- a/src/class-tiny-settings.php
+++ b/src/class-tiny-settings.php
@@ -160,6 +160,8 @@ public function add_options_to_page() {
}
public function image_sizes_notice() {
+ check_ajax_referer( 'tiny-compress' );
+
if ( current_user_can( 'manage_options' ) ) {
$selected_sizes = isset( $_GET['image_sizes_selected'] ) ?
intval( $_GET['image_sizes_selected'] ) : 0;
@@ -827,9 +829,8 @@ public function render_pending_status() {
}
public function create_api_key() {
- if ( ! $this->check_ajax_referer() ) {
- exit;
- }
+ check_ajax_referer( 'tiny-compress', '_nonce' );
+
$compressor = $this->get_compressor();
if ( ! current_user_can( 'manage_options' ) ) {
$status = (object) array(
@@ -905,9 +906,7 @@ public function create_api_key() {
}
public function update_api_key() {
- if ( ! $this->check_ajax_referer() ) {
- exit;
- }
+ check_ajax_referer( 'tiny-compress', '_nonce' );
$key = null;
if ( ! current_user_can( 'manage_options' ) ) {
diff --git a/src/class-tiny-wp-base.php b/src/class-tiny-wp-base.php
index fd08fb34..0e69bbdd 100644
--- a/src/class-tiny-wp-base.php
+++ b/src/class-tiny-wp-base.php
@@ -87,10 +87,6 @@ protected function get_user_id() {
return get_current_user_id();
}
- protected function check_ajax_referer() {
- return check_ajax_referer( 'tiny-compress', '_nonce', false );
- }
-
public function init() {
}
diff --git a/src/js/admin.js b/src/js/admin.js
index 0ead0f46..03312c0a 100644
--- a/src/js/admin.js
+++ b/src/js/admin.js
@@ -301,7 +301,6 @@
eventOn('click', 'button.tiny-mark-as-compressed', onClickButtonMarkAsCompressed);
setPropOf('button.tiny-compress', 'disabled', null);
-
compressImageSelection();
watchCompressingImages();
@@ -329,29 +328,33 @@
});
}
- eventOn('click', 'input[name*=tinypng_sizes], #tinypng_resize_original_enabled', function() {
- /* Unfortunately, we need some additional information to display
- the correct notice. */
- var totalSelectedSizes = jQuery('input[name*=tinypng_sizes]:checked').length;
- var compressWr2x = propOf('#tinypng_sizes_wr2x', 'checked');
- if (compressWr2x) {
- totalSelectedSizes--;
- }
+ async function refreshSizeDescriptionNotice() {
+ const totalSelectedSizes = document.querySelectorAll('input[name*=tinypng_sizes]:checked').length;
+ const compressWr2x = document.querySelector('#tinypng_sizes_wr2x')?.checked ?? false;
+ const selectedCount = compressWr2x ? totalSelectedSizes - 1 : totalSelectedSizes;
- var image_count_url = ajaxurl + (ajaxurl.indexOf( '?' ) > 0 ? '&' : '?') + 'action=tiny_image_sizes_notice&image_sizes_selected=' + totalSelectedSizes;
- if (propOf('#tinypng_resize_original_enabled', 'checked') && propOf('#tinypng_sizes_0', 'checked')) {
- image_count_url += '&resize_original=true';
+ const separator = ajaxurl.includes('?') ? '&' : '?';
+ let imageCountUrl = `${ajaxurl}${separator}action=tiny_image_sizes_notice&image_sizes_selected=${selectedCount}&_ajax_nonce=${tinyCompress.nonce}`;
+
+ const resizeOriginalChecked = document.querySelector('#tinypng_resize_original_enabled')?.checked;
+ const compressOriginalChecked = document.querySelector('#tinypng_sizes_0')?.checked;
+ if (resizeOriginalChecked && compressOriginalChecked) {
+ imageCountUrl += '&resize_original=true';
}
if (compressWr2x) {
- image_count_url += '&compress_wr2x=true';
+ imageCountUrl += '&compress_wr2x=true';
}
- jQuery('#tiny-image-sizes-notice').load(image_count_url);
- });
-
- eventOn('click', '#tinypng_auto_compress_enabled', function() {
- updateSettings();
- });
+ try {
+ const sizeDescriptionHtml = await fetch(imageCountUrl).then(r => r.text());
+ document.querySelector('#tiny-image-sizes-notice').innerHTML = sizeDescriptionHtml;
+ } catch (err) {
+ document.querySelector('#tiny-image-sizes-notice').innerHTML = '';
+ console.error(err);
+ }
+ }
+ eventOn('click', 'input[name*=tinypng_sizes], #tinypng_resize_original_enabled', refreshSizeDescriptionNotice);
+ eventOn('click', '#tinypng_auto_compress_enabled', updateSettings);
jQuery('#tinypng_sizes_0, #tinypng_resize_original_enabled').click(updateSettings);
updateSettings();
}
diff --git a/src/views/compress-details.php b/src/views/compress-details.php
index b3a0f58a..7c3152f9 100644
--- a/src/views/compress-details.php
+++ b/src/views/compress-details.php
@@ -2,8 +2,9 @@
/**
* Compression details on media overview page.
*
- * @var Tiny_Plugin $this The plugin instance.
- * @var Tiny_Image $tiny_image The image being compressed.
+ * @var Tiny_Plugin $this The plugin instance.
+ * @var Tiny_Image $tiny_image The image being compressed.
+ * @var int[] $images_to_compress The IDs that are being compressed
*/
$available_sizes = array_keys( $this->settings->get_sizes() );
@@ -22,11 +23,6 @@
$size_exists = array_fill_keys( $available_sizes, true );
ksort( $size_exists );
-$images_to_compress = array();
-if ( ! empty( $_REQUEST['ids'] ) ) {
- $request_ids = sanitize_text_field( wp_unslash( $_REQUEST['ids'] ) );
- $images_to_compress = array_map( 'intval', explode( '-', $request_ids ) );
-}
?>
diff --git a/test/unit/TinyHelpersTest.php b/test/unit/TinyHelpersTest.php
index 349a1af8..98abdff5 100644
--- a/test/unit/TinyHelpersTest.php
+++ b/test/unit/TinyHelpersTest.php
@@ -111,26 +111,6 @@ public function test_uppercase_extension_and_mimetype_case_insensitive()
$this->assertEquals($expected, Tiny_Helpers::replace_file_extension('image/avif', $input));
}
-public function test_is_pagebuilder_request_returns_false_when_no_pagebuilder_keys()
-{
- $_GET = array();
- $this->assertFalse(Tiny_Helpers::is_pagebuilder_request());
-}
-
-public function test_is_pagebuilder_request_returns_true_for_beaver_builder()
-{
- $_GET = array('fl_builder' => '1');
- $this->assertTrue(Tiny_Helpers::is_pagebuilder_request());
- $_GET = array();
-}
-
-public function test_is_pagebuilder_request_returns_false_for_non_pagebuilder_keys()
-{
- $_GET = array('page' => 'settings', 'post_id' => '123');
- $this->assertFalse(Tiny_Helpers::is_pagebuilder_request());
- $_GET = array();
-}
-
public function test_str_starts_with_returns_true_when_haystack_starts_with_needle()
{
$this->assertTrue(Tiny_Helpers::str_starts_with('hello world', 'hello'));
diff --git a/test/unit/TinyPictureTest.php b/test/unit/TinyPictureTest.php
index 95298d21..7c24abc0 100644
--- a/test/unit/TinyPictureTest.php
+++ b/test/unit/TinyPictureTest.php
@@ -2,6 +2,19 @@
require_once dirname(__FILE__) . '/TinyTestCase.php';
+class Tiny_Picture_Overrides extends Tiny_Picture {
+ /**
+ * filter_has_var looks for immutable $_GET
+ * so unit tests cannot set the $_GET.
+ *
+ * isset( $_GET ) is similar to filter_has_var
+ * but allows us to mutate.
+ */
+ protected static function has_get_var( $key ) {
+ return isset( $_GET[ $key ] );
+ }
+}
+
class Tiny_Picture_Test extends Tiny_TestCase
{
@@ -337,7 +350,7 @@ public function test_does_not_register_hooks_when_pagebuilder_request()
});
$settings = new Tiny_Settings();
- $tiny_picture = new Tiny_Picture($settings, $this->vfs->url(), array('https://www.tinifytest.com'));
+ $tiny_picture = new Tiny_Picture_Overrides($settings, $this->vfs->url(), array('https://www.tinifytest.com'));
$template_redirect_registered = false;
foreach ($this->wp->getCalls('add_action') as $call) {