--- a/w3-total-cache/Cache_Memcached.php
+++ b/w3-total-cache/Cache_Memcached.php
@@ -244,7 +244,7 @@
/**
* Retrieves multiple cached values in a single request.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $keys Cache keys.
* @param string $group Cache group.
@@ -293,7 +293,7 @@
/**
* Stores multiple values in a single request.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $items Map of cache key => payload.
* @param string $group Cache group.
--- a/w3-total-cache/Cache_Redis.php
+++ b/w3-total-cache/Cache_Redis.php
@@ -426,7 +426,7 @@
/**
* Retrieves multiple cached values in a single request.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $keys Cache keys.
* @param string $group Cache group.
@@ -479,7 +479,7 @@
/**
* Stores multiple values in a single request.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $items Map of cache key => payload.
* @param string $group Cache group.
--- a/w3-total-cache/Cdn_Plugin.php
+++ b/w3-total-cache/Cdn_Plugin.php
@@ -110,7 +110,7 @@
*
* @return void
*
- * @since 2.9.1
+ * @since 2.9.2
*/
public function send_headers() {
$cdn_engine = $this->_config->get_string( 'cdn.engine' );
--- a/w3-total-cache/DbCache_WpdbInjection_QueryCaching.php
+++ b/w3-total-cache/DbCache_WpdbInjection_QueryCaching.php
@@ -823,7 +823,7 @@
/**
* Clears request-wide dbcache reject state so subsequent queries can be reconsidered.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
--- a/w3-total-cache/DbCache_WpdbLegacy.php
+++ b/w3-total-cache/DbCache_WpdbLegacy.php
@@ -250,7 +250,7 @@
/**
* Returns the array of dbcache processors attached to this wpdb wrapper.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return array
*/
--- a/w3-total-cache/DbCache_WpdbNew.php
+++ b/w3-total-cache/DbCache_WpdbNew.php
@@ -242,7 +242,7 @@
/**
* Returns the array of dbcache processors attached to this wpdb wrapper.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return array
*/
--- a/w3-total-cache/Extension_ImageService_Environment.php
+++ b/w3-total-cache/Extension_ImageService_Environment.php
@@ -150,7 +150,7 @@
/**
* Generate AVIF rewrite rules (higher priority).
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @see Dispatcher::nginx_rules_for_browsercache_section()
*
@@ -266,7 +266,7 @@
/**
* Generate WebP rewrite rules (lower priority than AVIF).
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @see Dispatcher::nginx_rules_for_browsercache_section()
*
--- a/w3-total-cache/Extension_ImageService_Plugin_Admin.php
+++ b/w3-total-cache/Extension_ImageService_Plugin_Admin.php
@@ -1009,6 +1009,7 @@
// Determine classes.
$link_classes = 'w3tc-convert';
+ $can_edit = current_user_can( 'edit_post', $post_id );
switch ( $status ) {
case 'processing':
@@ -1021,16 +1022,24 @@
$aria_attr = 'true';
break;
default:
- $disabled_class = '';
- $aria_attr = 'false';
+ if ( $can_edit ) {
+ $disabled_class = '';
+ $aria_attr = 'false';
+ } else {
+ $disabled_class = 'w3tc-disabled';
+ $aria_attr = 'true';
+ }
break;
}
+ // Prevent JS from polling status for images the current user cannot edit.
+ $data_status = ( ! $can_edit && 'processing' === $status ) ? '' : $status;
+
// Print action links.
?>
<span class="<?php echo esc_attr( $disabled_class ); ?>">
<a class="<?php echo esc_attr( $link_classes ); ?>" data-post-id="<?php echo esc_attr( $post_id ); ?>"
- data-status="<?php echo esc_attr( $status ); ?>" aria-disabled="<?php echo esc_attr( $aria_attr ); ?>">
+ data-status="<?php echo esc_attr( $data_status ); ?>" aria-disabled="<?php echo esc_attr( $aria_attr ); ?>" role="button" tabindex="0">
<?php
// phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact
switch ( $status ) {
@@ -1101,8 +1110,10 @@
// If converted, then show revert link.
if ( 'converted' === $status ) {
+ $revert_span_class = $can_edit ? 'w3tc-revert' : 'w3tc-revert w3tc-disabled';
+ $revert_aria_disabled = $can_edit ? 'false' : 'true';
?>
- <span class="w3tc-revert"> | <a><?php esc_attr_e( 'Revert', 'w3-total-cache' ); ?></a></span>
+ <span class="<?php echo esc_attr( $revert_span_class ); ?>"> | <a aria-disabled="<?php echo esc_attr( $revert_aria_disabled ); ?>" role="button" tabindex="0"><?php esc_html_e( 'Revert', 'w3-total-cache' ); ?></a></span>
<?php
// Check if WEBP and AVIF already exist.
$has_webp = false;
@@ -1134,16 +1145,20 @@
// Show additional convert links only when the format is enabled.
if ( $has_webp && ! $has_avif && $avif_enabled ) {
+ $avif_span_class = $can_edit ? 'w3tc-convert-avif' : 'w3tc-convert-avif w3tc-disabled';
+ $avif_aria_disabled = $can_edit ? 'false' : 'true';
?>
- <span class="w3tc-convert-avif"> | <a class="w3tc-convert-format" data-post-id="<?php echo esc_attr( $post_id ); ?>"
- data-status="<?php echo esc_attr( $status ); ?>" data-format="avif" aria-disabled="false"><?php esc_html_e( 'Convert to AVIF', 'w3-total-cache' ); ?></a></span>
+ <span class="<?php echo esc_attr( $avif_span_class ); ?>"> | <a class="w3tc-convert-format" data-post-id="<?php echo esc_attr( $post_id ); ?>"
+ data-status="<?php echo esc_attr( $status ); ?>" data-format="avif" aria-disabled="<?php echo esc_attr( $avif_aria_disabled ); ?>"><?php esc_html_e( 'Convert to AVIF', 'w3-total-cache' ); ?></a></span>
<?php
}
if ( $has_avif && ! $has_webp && $webp_enabled ) {
+ $webp_span_class = $can_edit ? 'w3tc-convert-webp' : 'w3tc-convert-webp w3tc-disabled';
+ $webp_aria_disabled = $can_edit ? 'false' : 'true';
?>
- <span class="w3tc-convert-webp"> | <a class="w3tc-convert-format" data-post-id="<?php echo esc_attr( $post_id ); ?>"
- data-status="<?php echo esc_attr( $status ); ?>" data-format="webp" aria-disabled="false"><?php esc_html_e( 'Convert to WebP', 'w3-total-cache' ); ?></a></span>
+ <span class="<?php echo esc_attr( $webp_span_class ); ?>"> | <a class="w3tc-convert-format" data-post-id="<?php echo esc_attr( $post_id ); ?>"
+ data-status="<?php echo esc_attr( $status ); ?>" data-format="webp" aria-disabled="<?php echo esc_attr( $webp_aria_disabled ); ?>"><?php esc_html_e( 'Convert to WebP', 'w3-total-cache' ); ?></a></span>
<?php
}
}
@@ -1192,6 +1207,14 @@
// Remove custom query args.
$location = remove_query_arg( array( 'w3tc_imageservice_submitted', 'w3tc_imageservice_reverted' ), $location );
+ // Filter to only attachment IDs the current user is allowed to edit.
+ $post_ids = array_filter(
+ $post_ids,
+ function ( $post_id ) {
+ return current_user_can( 'edit_post', $post_id );
+ }
+ );
+
switch ( $doaction ) {
case 'w3tc_imageservice_convert':
$stats = $this->submit_images( $post_ids );
@@ -1439,7 +1462,7 @@
// Check WP_Filesystem credentials.
Util_WpFile::ajax_check_credentials(
sprintf(
- // translators: 1: HTML achor open tag, 2: HTML anchor close tag.
+ // translators: 1: HTML anchor open tag, 2: HTML anchor close tag.
__( '%1$sLearn more%2$s.', 'w3-total-cache' ),
'<a target="_blank" href="' . esc_url(
'https://www.boldgrid.com/support/w3-total-cache/image-service/?utm_source=w3tc&utm_medium=conversion_error&utm_campaign=imageservice#unable-to-connect-to-the-filesystem-error'
@@ -1766,18 +1789,6 @@
public function ajax_submit() {
check_ajax_referer( 'w3tc_imageservice_submit' );
- // Check WP_Filesystem credentials.
- Util_WpFile::ajax_check_credentials(
- sprintf(
- // translators: 1: HTML achor open tag, 2: HTML anchor close tag.
- __( '%1$sLearn more%2$s.', 'w3-total-cache' ),
- '<a target="_blank" href="' . esc_url(
- 'https://www.boldgrid.com/support/w3-total-cache/image-service/?utm_source=w3tc&utm_medium=conversion_error&utm_campaign=imageservice#unable-to-connect-to-the-filesystem-error'
- ) . '">',
- '</a>'
- )
- );
-
// Check for post id.
$post_id_val = Util_Request::get_integer( 'post_id' );
$post_id = ! empty( $post_id_val ) ? $post_id_val : null;
@@ -1791,6 +1802,27 @@
);
}
+ if ( ! current_user_can( 'edit_post', $post_id ) ) {
+ wp_send_json_error(
+ array(
+ 'error' => __( 'You do not have permission to convert this image.', 'w3-total-cache' ),
+ ),
+ 403
+ );
+ }
+
+ // Check WP_Filesystem credentials.
+ Util_WpFile::ajax_check_credentials(
+ sprintf(
+ // translators: 1: HTML anchor open tag, 2: HTML anchor close tag.
+ __( '%1$sLearn more%2$s.', 'w3-total-cache' ),
+ '<a target="_blank" href="' . esc_url(
+ 'https://www.boldgrid.com/support/w3-total-cache/image-service/?utm_source=w3tc&utm_medium=conversion_error&utm_campaign=imageservice#unable-to-connect-to-the-filesystem-error'
+ ) . '">',
+ '</a>'
+ )
+ );
+
global $wp_filesystem;
// Verify the image file exists.
@@ -1981,6 +2013,16 @@
$post_id = ! empty( $post_id_val ) ? $post_id_val : null;
if ( $post_id ) {
+ if ( ! current_user_can( 'edit_post', $post_id ) ) {
+ wp_send_json_error(
+ array(
+ 'error' => __( 'You do not have permission to access this image.', 'w3-total-cache' ),
+ ),
+ 403
+ );
+ return;
+ }
+
wp_send_json_success( (array) get_post_meta( $post_id, 'w3tc_imageservice', true ) );
} else {
wp_send_json_error(
@@ -2006,6 +2048,16 @@
$post_id = ! empty( $post_id_val ) ? $post_id_val : null;
if ( $post_id ) {
+ if ( ! current_user_can( 'edit_post', $post_id ) ) {
+ wp_send_json_error(
+ array(
+ 'error' => __( 'You do not have permission to revert this image.', 'w3-total-cache' ),
+ ),
+ 403
+ );
+ return;
+ }
+
// Check if there are any optimizations to revert.
$postmeta = (array) get_post_meta( $post_id, 'w3tc_imageservice', true );
$has_optimizations = false;
@@ -2051,6 +2103,15 @@
public function ajax_convert_all() {
check_ajax_referer( 'w3tc_imageservice_submit' );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error(
+ array(
+ 'error' => __( 'You do not have permission to perform bulk conversions.', 'w3-total-cache' ),
+ ),
+ 403
+ );
+ }
+
$results = $this->get_eligible_attachments();
$post_ids = array();
@@ -2079,6 +2140,15 @@
public function ajax_revert_all() {
check_ajax_referer( 'w3tc_imageservice_submit' );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error(
+ array(
+ 'error' => __( 'You do not have permission to perform bulk reverts.', 'w3-total-cache' ),
+ ),
+ 403
+ );
+ }
+
$results = $this->get_imageservice_attachments();
$revert_count = 0;
@@ -2106,6 +2176,15 @@
public function ajax_get_counts() {
check_ajax_referer( 'w3tc_imageservice_submit' );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error(
+ array(
+ 'error' => __( 'You do not have permission to view image counts.', 'w3-total-cache' ),
+ ),
+ 403
+ );
+ }
+
wp_send_json_success( $this->get_counts() );
}
@@ -2120,6 +2199,15 @@
public function ajax_get_usage() {
check_ajax_referer( 'w3tc_imageservice_submit' );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error(
+ array(
+ 'error' => __( 'You do not have permission to view API usage.', 'w3-total-cache' ),
+ ),
+ 403
+ );
+ }
+
wp_send_json_success( Extension_ImageService_Plugin::get_api()->get_usage( true ) );
}
--- a/w3-total-cache/Generic_Plugin.php
+++ b/w3-total-cache/Generic_Plugin.php
@@ -38,13 +38,38 @@
/**
* Frontend notice payload when redirecting back from admin actions.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @var ?array
*/
private $frontend_notice;
/**
+ * Output buffer nesting level recorded at ob_start() time.
+ *
+ * Used by ob_shutdown() to identify which buffer level belongs to W3TC.
+ *
+ * @since 2.9.2
+ *
+ * @var int
+ */
+ private $_ob_level = 0;
+
+ /**
+ * Guards against ob_shutdown() being invoked twice.
+ *
+ * Function `ob_shutdown()` is registered both as a WordPress 'shutdown' action
+ * (priority 0, so it runs before wp_ob_end_flush_all at priority 1) and as
+ * a PHP shutdown function (fallback for abnormal termination). This flag
+ * ensures the second invocation is a no-op.
+ *
+ * @since 2.9.2
+ *
+ * @var bool
+ */
+ private $_ob_shutdown_done = false;
+
+ /**
* Constructor
*
* @return void
@@ -102,7 +127,26 @@
add_filter( 'wp_die_xml_handler', array( $this, 'wp_die_handler' ) );
add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
- ob_start( array( $this, 'ob_callback' ) );
+ /*
+ * Register with no callback so no display-handler restriction applies.
+ * ob_callback() is invoked explicitly in ob_shutdown() instead.
+ * PHP's display-handler restriction (ob_start() forbidden inside a
+ * callback) made every ob flush mechanism unsafe for mfunc processing.
+ */
+ ob_start();
+ $this->_ob_level = ob_get_level();
+
+ /*
+ * Hook into WordPress 'shutdown' at priority 0 so ob_shutdown runs
+ * before wp_ob_end_flush_all (priority 1). wp_ob_end_flush_all calls
+ * ob_end_flush(), which would send the raw unprocessed buffer.
+ * By processing and echoing the buffer here first we ensure the
+ * processed output reaches the client.
+ * The PHP shutdown function below is a fallback for abnormal
+ * termination paths where the WordPress 'shutdown' action may not fire.
+ */
+ add_action( 'shutdown', array( $this, 'ob_shutdown' ), 0 );
+ register_shutdown_function( array( $this, 'ob_shutdown' ) );
}
$this->register_plugin_check_filters();
@@ -114,7 +158,7 @@
/**
* Removes dynamic fragment tags from comment content before storage.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $comment_data Comment data being processed.
*
@@ -131,7 +175,7 @@
/**
* Removes dynamic fragment tags from RSS/feed content.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $content Content to sanitize.
*
@@ -144,7 +188,7 @@
/**
* Sanitizes REST API responses to prevent dynamic fragment leakage.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param WP_REST_Response|mixed $result Response data.
* @param WP_REST_Server $server REST server instance.
@@ -170,7 +214,7 @@
/**
* Recursively removes dynamic fragment tags from REST data structures.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param mixed $data Response data.
*
@@ -203,7 +247,7 @@
/**
* Removes dynamic fragment tags from a text string.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $value Raw content to sanitize.
*
@@ -218,9 +262,13 @@
$original_value = $value;
// Remove dynamic fragment tags from the value.
+ // Use s*S+ (zero-or-more whitespace, then one-or-more non-whitespace) so that
+ // tags with no space between the keyword and token (e.g. <!-- mfuncTOKEN -->) are
+ // also caught and not passed through to the str_replace step below where a crafted
+ // token-containing name could otherwise be morphed into a valid mfunc tag.
$pattern = array(
- '~<!--s*mfuncs+[^s]+.*?-->(.*?)<!--s*/mfuncs+[^s]+.*?s*-->~Uis',
- '~<!--s*mcludes+[^s]+.*?-->(.*?)<!--s*/mcludes+[^s]+.*?s*-->~Uis',
+ '~<!--s*mfuncs*S+.*?-->(.*?)<!--s*/mfuncs*S+.*?s*-->~Uis',
+ '~<!--s*mcludes*S+.*?-->(.*?)<!--s*/mcludes*S+.*?s*-->~Uis',
);
$value = preg_replace_callback(
@@ -709,7 +757,7 @@
/**
* Loads a pending frontend message triggered during an admin redirect.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
@@ -1015,6 +1063,58 @@
}
/**
+ * Processes and outputs the W3TC output buffer at shutdown time.
+ *
+ * Registered both as a WordPress 'shutdown' action (priority 0, before
+ * wp_ob_end_flush_all at priority 1) and as a PHP shutdown function
+ * (fallback). The flag $_ob_shutdown_done prevents double-invocation.
+ *
+ * ob_callback() is invoked explicitly here rather than as a PHP display
+ * handler, because display handlers (callbacks invoked during ob_end_clean,
+ * ob_end_flush, or PHP's own buffer cleanup) set PHP's internal ob_lock
+ * flag, which forbids any nested ob_start() call. _parse_dynamic_mfunc()
+ * requires ob_start() to capture eval() output, so it must not run inside
+ * a display handler. The ob_start() in run() is registered with no callback
+ * to ensure no display handler is ever registered for our buffer.
+ *
+ * @since 2.9.2
+ *
+ * @return void
+ */
+ public function ob_shutdown() {
+ if ( $this->_ob_shutdown_done ) {
+ return;
+ }
+
+ $this->_ob_shutdown_done = true;
+
+ /*
+ * Flush any nested output buffers (added by WordPress or other plugins)
+ * down into ours so their content is included.
+ */
+ while ( ob_get_level() > $this->_ob_level ) {
+ ob_end_flush();
+ }
+
+ if ( ob_get_level() < $this->_ob_level ) {
+ // Our buffer was already closed (e.g. by a redirect or wp_die).
+ return;
+ }
+
+ /*
+ * Read the buffer and close it. Because ob_start() was registered with
+ * no callback, ob_get_clean() never triggers a display handler, so
+ * ob_start() calls inside ob_callback() (e.g. _parse_dynamic_mfunc)
+ * are permitted.
+ */
+ $buffer = (string) ob_get_clean();
+
+ $buffer = $this->ob_callback( $buffer );
+
+ echo $buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ /**
* User login hook. Check if current user is not listed in pgcache.reject.* rules
* If so, set a role cookie so the requests wont be cached
*
@@ -1193,7 +1293,7 @@
/**
* Registers Plugin Check filters so they run in all contexts.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @link https://github.com/WordPress/plugin-check/blob/1.6.0/includes/Utilities/Plugin_Request_Utility.php#L160
* @link https://github.com/WordPress/plugin-check/blob/1.6.0/includes/Utilities/Plugin_Request_Utility.php#L180
--- a/w3-total-cache/Licensing_Plugin_Admin.php
+++ b/w3-total-cache/Licensing_Plugin_Admin.php
@@ -36,7 +36,7 @@
/**
* Usermeta key for storing dismissed license notices.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @var string
*/
@@ -46,7 +46,7 @@
* Time in seconds after which a dismissed notice can reappear if conditions persist.
* Set to 6 days (automatic license check interval is 5 days).
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @var int
*/
@@ -58,7 +58,7 @@
* The HLT (Hosted Login Token) in the billing URL is only valid for 19 minutes,
* so we cache for 19 minutes to ensure users always get a valid token with some buffer.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @var int
*/
@@ -392,7 +392,7 @@
* This is called on non-W3TC admin pages where the lightbox isn't already loaded.
* Only enqueues if there's a license status that would display a notice.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
@@ -417,7 +417,7 @@
/**
* Enqueues lightbox JavaScript and CSS assets.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
@@ -459,7 +459,7 @@
* (lowercase letters, numbers, hyphens, underscores) and replaces
* dots with hyphens for readability.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $status The license status to sanitize.
*
@@ -559,7 +559,7 @@
* Called on admin_enqueue_scripts to register the script early,
* which can then be enqueued later when notices are displayed.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
@@ -616,7 +616,7 @@
/**
* Generates a license notice based on the provided license status and key.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $status The current status of the license (e.g., 'active', 'expired').
* @param string $license_key The license key associated with the plugin.
@@ -746,7 +746,7 @@
* Uses transient caching to avoid making HTTP requests on every page load.
* The URL is cached for 1 hour to balance freshness with performance.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $license_key The license key used to generate the billing URL.
*
@@ -779,7 +779,7 @@
/**
* Fetches the billing URL from the API.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $license_key The license key used to generate the billing URL.
*
@@ -998,7 +998,7 @@
*
* Saves the dismissal timestamp in usermeta for persistent per-user dismissal.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
@@ -1034,7 +1034,7 @@
*
* Forces an immediate license check and clears cached billing URL.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
@@ -1068,7 +1068,7 @@
* If the reset time has elapsed and the condition still persists, the dismissal
* is cleared and the notice will show again.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $notice_id The unique identifier for the notice.
*
@@ -1102,7 +1102,7 @@
* Uses a targeted query to only retrieve users who have the specific notice dismissed,
* rather than all users with any dismissed notices.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $notice_id The unique identifier for the notice to clear.
*
--- a/w3-total-cache/ObjectCache_WpObjectCache_Regular.php
+++ b/w3-total-cache/ObjectCache_WpObjectCache_Regular.php
@@ -1194,7 +1194,7 @@
/**
* Persist multiple cache entries using the most efficient method available.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param object $cache Cache engine instance.
* @param array $payload Map of cache_key => structured payload.
--- a/w3-total-cache/PageSpeed_Data.php
+++ b/w3-total-cache/PageSpeed_Data.php
@@ -65,7 +65,7 @@
/**
* Collect core web vital metrics in a consistent format.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $data PageSpeed data payload.
* @param string $metric Lighthouse audit identifier.
@@ -83,7 +83,7 @@
/**
* Log the raw metric keys and configured instruction keys when debugging is enabled.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $pagespeed_data Prepared PageSpeed data.
*
@@ -121,7 +121,7 @@
/**
* Collect audits belonging to the given Lighthouse category group.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $data Raw Lighthouse API payload.
* @param string $group Lighthouse category group identifier.
@@ -157,7 +157,7 @@
/**
* Format a single Lighthouse audit into the structure expected by the UI.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $audit_id Lighthouse audit identifier.
* @param array $audit Lighthouse audit payload.
@@ -191,7 +191,7 @@
/**
* Normalize Lighthouse audit details to a list structure.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param mixed $details Lighthouse audit details.
*
@@ -219,7 +219,7 @@
/**
* Determine which Core Web Vitals an audit influences.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $audit_id Lighthouse audit identifier.
* @param array $audit Lighthouse audit payload.
@@ -248,7 +248,7 @@
/**
* Normalize the network dependency tree insight payload.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $details Lighthouse network dependency tree details payload.
*
@@ -283,7 +283,7 @@
/**
* Normalize a network dependency chain node recursively.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $node Node payload.
*
@@ -310,7 +310,7 @@
/**
* Normalize preconnect insight sections.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $section Section payload from Lighthouse.
*
@@ -360,7 +360,7 @@
/**
* Provide a mapping of audit identifiers to Core Web Vital type tags.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return array
*/
@@ -409,7 +409,7 @@
/**
* Normalize score values to 0-100 scale while avoiding PHP warnings when score is missing.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param mixed $score Score from the Lighthouse payload.
*
@@ -426,7 +426,7 @@
/**
* Drop metrics that Google didn't include in the latest payload.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $metrics Raw metrics bucket.
*
@@ -448,7 +448,7 @@
/**
* Attach instructions for metrics that survived the filtering step.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $pagespeed_data Prepared PageSpeed data.
*
--- a/w3-total-cache/PgCache_ContentGrabber.php
+++ b/w3-total-cache/PgCache_ContentGrabber.php
@@ -105,18 +105,11 @@
/**
* Page key extension
*
- * @var string
+ * @var array
*/
private $_page_key_extension;
/**
- * Shutdown buffer
- *
- * @var string
- */
- private $_shutdown_buffer = '';
-
- /**
* Cache reject reason
*
* @var string
@@ -447,7 +440,7 @@
return true;
}
- $this->caching = false;
+ $this->_caching = false;
$this->cache_reject_reason = 'w3tc_page_extract_key filter result forced not to cache';
return false;
@@ -586,41 +579,20 @@
}
}
- // We can't capture output in ob_callback so we use shutdown function.
if ( $has_dynamic ) {
- $this->_shutdown_buffer = $buffer;
-
- $buffer = '';
+ $compression = $this->_page_key_extension['compression'];
+ if ( defined( 'W3TC_PAGECACHE_OUTPUT_COMPRESSION_OFF' ) ) {
+ $compression = false;
+ }
- register_shutdown_function(
- array(
- $this,
- 'shutdown',
- )
- );
+ $buffer = $this->_parse_dynamic( $buffer );
+ $buffer = $this->_compress( $buffer, $compression );
}
return $buffer;
}
/**
- * Handles the shutdown process for compressing and outputting the page buffer.
- *
- * @return void
- */
- public function shutdown() {
- $compression = $this->_page_key_extension['compression'];
-
- // Parse dynamic content.
- $buffer = $this->_parse_dynamic( $this->_shutdown_buffer );
-
- // Compress page according to headers already set.
- $compressed_buffer = $this->_compress( $buffer, $compression );
-
- echo $compressed_buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- }
-
- /**
* Determines if the cache can be read for the current request.
*
* @return bool True if cache can be read, false otherwise.
@@ -2094,8 +2066,10 @@
return $buffer;
}
+ $security = preg_quote( W3TC_DYNAMIC_SECURITY, '~' );
+
$buffer = preg_replace_callback(
- '~<!--s*mfuncs*' . W3TC_DYNAMIC_SECURITY . '(.*)-->(.*)<!--s*/mfuncs*' . W3TC_DYNAMIC_SECURITY . 's*-->~Uis',
+ '~<!--s*mfuncs+' . $security . '(.*)-->(.*)<!--s*/mfuncs+' . $security . 's*-->~Uis',
array(
$this,
'_parse_dynamic_mfunc',
@@ -2104,7 +2078,7 @@
);
$buffer = preg_replace_callback(
- '~<!--s*mcludes*' . W3TC_DYNAMIC_SECURITY . '(.*)-->(.*)<!--s*/mcludes*' . W3TC_DYNAMIC_SECURITY . 's*-->~Uis',
+ '~<!--s*mcludes+' . $security . '(.*)-->(.*)<!--s*/mcludes+' . $security . 's*-->~Uis',
array(
$this,
'_parse_dynamic_mclude',
@@ -2190,12 +2164,14 @@
* @return bool True if dynamic tags are present, false otherwise.
*/
public function _has_dynamic( $buffer ) {
- if ( ! defined( 'W3TC_DYNAMIC_SECURITY' ) ) {
+ if ( ! defined( 'W3TC_DYNAMIC_SECURITY' ) || empty( W3TC_DYNAMIC_SECURITY ) || 1 === (int) W3TC_DYNAMIC_SECURITY ) {
return false;
}
+ $security = preg_quote( W3TC_DYNAMIC_SECURITY, '~' );
+
return preg_match(
- '~<!--s*m(func|clude)s*' . W3TC_DYNAMIC_SECURITY . '(.*)-->(.*)<!--s*/m(func|clude)s*' . W3TC_DYNAMIC_SECURITY . 's*-->~Uis',
+ '~<!--s*m(func|clude)s+' . $security . '(.*)-->(.*)<!--s*/m(func|clude)s+' . $security . 's*-->~Uis',
$buffer
);
}
@@ -2356,7 +2332,7 @@
$compressions_to_store = array( false );
}
- // Right now dont return compressed buffer if we are dynamic that will happen on shutdown after processing dynamic stuff.
+ // For dynamic pages, return the unprocessed buffer so ob_callback can parse and compress it directly.
$compression_of_returned_content = ( $has_dynamic ? false : $compression_header );
$headers = $this->_get_cached_headers( $response_headers['plain'] );
--- a/w3-total-cache/SetupGuide_Plugin_Admin.php
+++ b/w3-total-cache/SetupGuide_Plugin_Admin.php
@@ -924,7 +924,7 @@
/**
* Build the SQL statements used for database cache benchmarking.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param wpdb $wpdb WordPress database object.
*
@@ -944,7 +944,7 @@
/**
* Execute a repeatable set of read-heavy queries to measure cache performance.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param wpdb $wpdb WordPress database object.
* @param array $queries List of SQL queries to execute.
@@ -967,7 +967,7 @@
/**
* Clear runtime caches to reduce contamination between timed runs.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
@@ -982,7 +982,7 @@
/**
* Clear dbcache reject state for the current request so tests can evaluate with fresh context.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return void
*/
@@ -1015,7 +1015,7 @@
/**
* Collect a set of post IDs to be used when benchmarking the object cache.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return array
*/
@@ -1047,7 +1047,7 @@
/**
* Run a representative workload that should benefit from a persistent object cache.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $post_ids IDs to request in the query.
* @param string $payload_key Cache key used to check persistence.
@@ -1095,7 +1095,7 @@
/**
* Clear runtime object cache data while preserving persistent stores when possible.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @return string Which flush method was used.
*/
--- a/w3-total-cache/Util_PageSpeed.php
+++ b/w3-total-cache/Util_PageSpeed.php
@@ -670,7 +670,7 @@
/**
* Render the specialized Network Dependency Tree insight.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $insight Insight payload.
* @param string $notice_class Notice classes.
@@ -736,7 +736,7 @@
/**
* Render the network chain list recursively.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $chains Chain list.
*
@@ -759,7 +759,7 @@
/**
* Render a single network chain node.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $node Node payload.
*
@@ -818,7 +818,7 @@
/**
* Render preconnect sections.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $section Section payload.
* @param string $default_name Fallback title.
@@ -856,7 +856,7 @@
/**
* Render a nested table cell from headings + sub-items.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $headings Table headings.
* @param array $rows Table rows.
@@ -899,7 +899,7 @@
/**
* Format a subitem value according to the heading definition.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $heading Heading definition.
* @param array $row Row data.
@@ -949,7 +949,7 @@
/**
* Format a source-location value into HTML.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param array $source Source location payload.
*
@@ -979,7 +979,7 @@
/**
* Append the document latency status icon for qualifying rows.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param string $audit_id Audit identifier.
* @param array $item Detail item.
@@ -1016,7 +1016,7 @@
/**
* Format bytes into readable strings.
*
- * @since 2.9.1
+ * @since 2.9.2
*
* @param int $bytes Byte value.
*
--- a/w3-total-cache/vendor/composer/installed.php
+++ b/w3-total-cache/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'boldgrid/w3-total-cache',
- 'pretty_version' => '2.9.1',
- 'version' => '2.9.1.0',
- 'reference' => 'c885b8e9fd3834983818cbbc2e7881d8859c7efd',
+ 'pretty_version' => '2.9.2',
+ 'version' => '2.9.2.0',
+ 'reference' => 'c94c7295e92553a8218201de93f4927b0ec56717',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -38,9 +38,9 @@
'dev_requirement' => false,
),
'boldgrid/w3-total-cache' => array(
- 'pretty_version' => '2.9.1',
- 'version' => '2.9.1.0',
- 'reference' => 'c885b8e9fd3834983818cbbc2e7881d8859c7efd',
+ 'pretty_version' => '2.9.2',
+ 'version' => '2.9.2.0',
+ 'reference' => 'c94c7295e92553a8218201de93f4927b0ec56717',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
--- a/w3-total-cache/w3-total-cache-api.php
+++ b/w3-total-cache/w3-total-cache-api.php
@@ -10,7 +10,7 @@
defined( 'ABSPATH' ) || die;
define( 'W3TC', true );
-define( 'W3TC_VERSION', '2.9.1' );
+define( 'W3TC_VERSION', '2.9.2' );
define( 'W3TC_POWERED_BY', 'W3 Total Cache' );
define( 'W3TC_EMAIL', 'w3tc@w3-edge.com' );
define( 'W3TC_TEXT_DOMAIN', 'w3-total-cache' );
--- a/w3-total-cache/w3-total-cache.php
+++ b/w3-total-cache/w3-total-cache.php
@@ -3,7 +3,7 @@
* Plugin Name: W3 Total Cache
* Plugin URI: https://www.boldgrid.com/totalcache/
* Description: The highest rated and most complete WordPress performance plugin. Dramatically improve the speed and user experience of your site. Add browser, page, object and database caching as well as minify and content delivery network (CDN) to WordPress.
- * Version: 2.9.1
+ * Version: 2.9.2
* Requires at least: 5.3
* Requires PHP: 7.2.5
* Author: BoldGrid