# xspeed/1.0.1/includes/class-minify-filters.php

xSpeed Cache: AI-Powered Performance Hub with MCP, Caching &amp; CDN, version 1.0.1. 260 lines.

- Page: https://pluginprobe.com/plugins/xspeed/1.0.1/code/includes/class-minify-filters.php
- Raw: https://pluginprobe.com/plugins/xspeed/1.0.1/raw/includes/class-minify-filters.php
- Modified: 2026-06-01T17:33:22+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/xspeed/1.0.1/code/includes/class-minify-filters.php#L10-L20`.

```php
<?php
/**
 * Minify_Filters — frontend HTML rewriters for the "smarter minifier"
 * sub-features (Phase 4.1a): defer JS, delay JS, async CSS, remove
 * query strings.
 *
 * Each method is a WordPress filter callback. None of them touch the
 * file system — they're pure tag rewrites or src-string rewrites
 * applied to enqueued asset URLs / tags.
 *
 * The heavier combine-CSS / combine-JS engine lands in Phase 4.1b
 * with its own class; keeping the filter-only logic isolated here
 * makes that future split clean.
 *
 * @package XSpeed
 */

declare(strict_types=1);

namespace XSpeed;

defined( 'ABSPATH' ) || exit;

final class Minify_Filters {

	/**
	 * Settings cache (one read per request).
	 *
	 * @var array|null
	 */
	private static $opts = null;

	/**
	 * Has the delay-JS bootstrap snippet been printed? Guards against
	 * duplicate emission in pages that hit wp_footer multiple times.
	 */
	private static $delay_bootstrap_printed = false;

	/**
	 * Filter: `script_loader_tag` — add defer="defer" to non-excluded
	 * scripts. WordPress passes the full <script> tag string, the
	 * handle, and the src. We bail when:
	 *   - the user excluded this handle / src substring,
	 *   - the tag already has defer or async (don't double-set),
	 *   - the tag has no src (inline scripts can't be deferred — would
	 *     execute synchronously regardless).
	 *
	 * @param string $tag
	 * @param string $handle
	 * @param string $src
	 */
	public static function defer_script_tag( $tag, $handle, $src ): string {
		if ( ! is_string( $tag ) || '' === $tag ) {
			return (string) $tag;
		}
		if ( '' === (string) $src ) {
			return $tag;
		}
		if ( self::is_excluded_script( (string) $handle, (string) $src ) ) {
			return $tag;
		}
		if ( false !== stripos( $tag, ' defer' ) || false !== stripos( $tag, ' async' ) ) {
			return $tag;
		}
		return (string) preg_replace( '#<script\b#i', '<script defer="defer"', $tag, 1 );
	}

	/**
	 * Filter: `script_loader_tag` — rewrite src= to data-xs-src= so the
	 * browser ignores it until the bootstrap (printed once on
	 * wp_footer) swaps it back on first user interaction. Same
	 * exclusion rules as defer. Inline scripts (no src) are also
	 * deferred until the first interaction.
	 *
	 * @param string $tag
	 * @param string $handle
	 * @param string $src
	 */
	public static function delay_script_tag( $tag, $handle, $src ): string {
		if ( ! is_string( $tag ) || '' === $tag ) {
			return (string) $tag;
		}
		if ( self::is_excluded_script( (string) $handle, (string) $src ) ) {
			return $tag;
		}
		// src= variant: swap src → data-xs-src and add data-xs-delay marker.
		if ( '' !== (string) $src ) {
			return (string) preg_replace(
				'#\bsrc\s*=\s*(["\'][^"\']*["\'])#i',
				'data-xs-src=$1 data-xs-delay="1"',
				$tag,
				1
			);
		}
		// Inline script: change type to text/plain so the browser
		// doesn't execute, mark for bootstrap rewriter.
		return (string) preg_replace(
			'#<script\b([^>]*)>#i',
			'<script$1 type="text/xspeed-delayed" data-xs-delay="1">',
			$tag,
			1
		);
	}

	/**
	 * Inline bootstrap that flips delayed scripts on the first user
	 * interaction. Printed once on wp_footer priority 1000.
	 */
	public static function print_delay_bootstrap(): void {
		if ( self::$delay_bootstrap_printed ) {
			return;
		}
		self::$delay_bootstrap_printed = true;
		// Tiny vanilla bootstrap; keep it self-contained so the page
		// has no JS dependencies before the first interaction.
		?>
<script id="xspeed-delay-bootstrap">
(function(){
  var events=['mousemove','keydown','touchstart','scroll','wheel'];
  var fired=false;
  function load(){
    if(fired)return;fired=true;
    events.forEach(function(e){window.removeEventListener(e,load,{passive:true,capture:true});});
    var delayed=document.querySelectorAll('script[data-xs-delay]');
    delayed.forEach(function(s){
      var n=document.createElement('script');
      Array.prototype.slice.call(s.attributes).forEach(function(a){
        if(a.name==='data-xs-src'){n.setAttribute('src',a.value);return;}
        if(a.name==='data-xs-delay'||a.name==='type')return;
        n.setAttribute(a.name,a.value);
      });
      if(!s.hasAttribute('data-xs-src')){n.text=s.text;}
      s.parentNode.replaceChild(n,s);
    });
  }
  events.forEach(function(e){window.addEventListener(e,load,{passive:true,capture:true});});
  setTimeout(load,8000);
})();
</script>
		<?php
	}

	/**
	 * Filter: `style_loader_tag` — wrap stylesheets in the
	 * print → onload="all" pattern so they download non-blocking.
	 * Pairs with critical CSS workflows. Adds a <noscript> fallback so
	 * users with JS disabled still get styles applied (via media="all").
	 *
	 * @param string $tag
	 * @param string $handle
	 */
	public static function async_style_tag( $tag, $handle ): string {
		if ( ! is_string( $tag ) || '' === $tag ) {
			return (string) $tag;
		}
		// Only operate on <link rel=stylesheet> with a media attribute
		// we can swap. Skip anything custom (preload, etc.) — we don't
		// want to fight with explicit author intent.
		if ( false === stripos( $tag, 'rel=\'stylesheet\'' ) && false === stripos( $tag, 'rel="stylesheet"' ) ) {
			return $tag;
		}
		// Avoid double-wrapping.
		if ( false !== stripos( $tag, 'data-xs-async' ) ) {
			return $tag;
		}
		$async = (string) preg_replace_callback(
			'#\bmedia\s*=\s*(["\'])([^"\']*)\1#i',
			static function ( $m ) {
				$orig = $m[2];
				return 'media="print" onload="this.media=\'' . esc_attr( $orig ) . '\'" data-xs-async="' . esc_attr( $orig ) . '"';
			},
			$tag,
			1
		);
		// If no media= was present (rare), inject one.
		if ( $async === $tag ) {
			$async = (string) preg_replace(
				'#<link\b#i',
				'<link media="print" onload="this.media=\'all\'" data-xs-async="all"',
				$tag,
				1
			);
		}
		// Fallback for noscript users — re-emit the original tag inside <noscript>.
		return $async . '<noscript>' . $tag . '</noscript>';
	}

	/**
	 * Filter: `style_loader_src` + `script_loader_src` — strip the
	 * ?ver=X.Y query string that WP appends for cache busting. Some
	 * CDNs / reverse proxies cache better when the URL has no query.
	 *
	 * Skip URLs whose query carries non-ver params — those might be
	 * intentional (e.g. a CDN providing per-image transforms).
	 *
	 * @param string $src
	 */
	public static function strip_version_query( $src ): string {
		if ( ! is_string( $src ) || '' === $src ) {
			return (string) $src;
		}
		$parts = wp_parse_url( $src );
		if ( ! is_array( $parts ) || empty( $parts['query'] ) ) {
			return $src;
		}
		parse_str( $parts['query'], $query );
		if ( ! is_array( $query ) ) {
			return $src;
		}
		// Only strip 'ver' — keep anything else the asset URL needs.
		unset( $query['ver'] );
		$new_query = http_build_query( $query );
		$new_url   = ( $parts['scheme'] ?? 'http' ) . '://' . ( $parts['host'] ?? '' );
		if ( isset( $parts['port'] ) ) {
			$new_url .= ':' . $parts['port'];
		}
		$new_url .= $parts['path'] ?? '';
		if ( '' !== $new_query ) {
			$new_url .= '?' . $new_query;
		}
		if ( ! empty( $parts['fragment'] ) ) {
			$new_url .= '#' . $parts['fragment'];
		}
		return $new_url;
	}

	private static function is_excluded_script( string $handle, string $src ): bool {
		$opts     = self::opts();
		$excluded = is_array( $opts['defer_js_excluded'] ?? null ) ? $opts['defer_js_excluded'] : array();
		if ( empty( $excluded ) ) {
			return false;
		}
		foreach ( $excluded as $needle ) {
			$needle = (string) $needle;
			if ( '' === $needle ) {
				continue;
			}
			if ( $handle === $needle || false !== stripos( $src, $needle ) ) {
				return true;
			}
		}
		return false;
	}

	private static function opts(): array {
		if ( null === self::$opts ) {
			self::$opts = Settings_Manager::get( 'minify' );
		}
		return self::$opts;
	}

	/**
	 * Test-only — clear cached opts + bootstrap-printed flag.
	 */
	public static function reset_state(): void {
		self::$opts                    = null;
		self::$delay_bootstrap_printed = false;
	}
}

```
