for the first large image, added to . */ private $lcp_preload = null; private $noscript = array(); private $stats = array( 'img' => 0, 'css' => 0, 'js' => 0, 'delay' => 0, 'critical' => false ); /** URL path prefix => local directory, for version tokens. */ private $roots = null; public function __construct() { $this->home_host = strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) ); $this->site_id = (string) ZeroConfig_Options::get( 'site_id' ); $this->cdn = rtrim( (string) ZeroConfig_Options::get( 'cdn_base' ), '/' ); $this->rules = ZeroConfig_Compat::rules(); } public static function should_skip() { if ( isset( $_GET['zc_off'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- presence of a flag, nothing is processed return true; } if ( is_admin() || is_user_logged_in() || is_feed() || is_preview() || is_customize_preview() ) { return true; } if ( ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { return true; } if ( function_exists( 'amp_is_request' ) && amp_is_request() ) { return true; } if ( defined( 'DONOTROCKETOPTIMIZE' ) && DONOTROCKETOPTIMIZE ) { return true; } // Builder editors and their preview frames (Elementor, Divi, Beaver, Bricks, Oxygen, WPBakery): // the frame may load without the login cookie, so being logged out says nothing here. foreach ( array( 'elementor-preview', 'et_fb', 'fl_builder', 'bricks', 'ct_builder', 'vc_editable' ) as $q ) { if ( isset( $_GET[ $q ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return true; } } $referer = isset( $_SERVER['HTTP_REFERER'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; if ( false !== strpos( $referer, 'elementor-preview' ) ) { return true; } return apply_filters( 'zeroconfig_skip_rewrite', false ); } public function process( $html ) { if ( ! is_string( $html ) || strlen( $html ) < 255 || ! preg_match( '/]/i', $html ) || false === stripos( $html, '' ) ) { return $html; } $out = $this->rewrite( $html ); return is_string( $out ) && false !== stripos( $out, '' ) ? $out : $html; } /** Insert markup right before the first closing tag (case-insensitive); the page is unchanged without one. */ private static function before_tag( $html, $tag, $insert ) { $at = stripos( (string) $html, $tag ); return false === $at ? $html : substr_replace( $html, $insert, $at, 0 ); } /** Insert markup right after the first tag matching $re; the page is unchanged without one. */ private static function after_tag( $html, $re, $insert ) { if ( ! preg_match( $re, (string) $html, $m, PREG_OFFSET_CAPTURE ) ) { return $html; } return substr_replace( $html, $insert, $m[0][1] + strlen( $m[0][0] ), 0 ); } private function rewrite( $html ) { $force = isset( $_GET['zc_test'] ) ? sanitize_text_field( wp_unslash( $_GET['zc_test'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- selects which features render, no state change $html = $this->stash_noscript( $html ); $cdn_ok = '' !== $this->cdn && '' !== $this->site_id && ZeroConfig_API::edge_healthy(); if ( $cdn_ok && ! ZeroConfig_Options::get( 'cdn_paused' ) && $this->on( 'images', $force ) ) { $html = $this->images( $html ); } if ( $cdn_ok && $this->on( 'assets', $force ) ) { $html = $this->styles( $html ); } if ( $this->on( 'js', $force ) ) { $html = $this->scripts( $html, $cdn_ok && $this->on( 'assets', $force ), $this->on( 'delay', $force ) ); } if ( $this->on( 'critical', $force ) ) { $html = $this->critical( $html ); } $html = $this->restore_noscript( $html ); $html = $this->preconnect_edge( $html ); if ( '' === $force ) { $html = $this->badge( $html ); } if ( ! ZeroConfig_Options::is_white_label() && apply_filters( 'zeroconfig_footer_comment', true ) ) { $html .= sprintf( "\n", $this->stats['img'], $this->stats['css'], $this->stats['js'], $this->stats['delay'], $this->stats['critical'] ? 'yes' : 'no' ); } return $html; } /** * Opt-out attributes other optimisers taught the ecosystem. Theme and plugin * authors already mark fragile tags with these; we honour all of them. */ private static function opted_out( array $attrs ) { foreach ( array( 'data-zc-skip', 'data-no-minify', 'data-no-optimize', 'data-no-defer', 'data-no-delay', 'nowprocket', 'data-noptimize', 'data-nowprocket' ) as $a ) { if ( isset( $attrs[ $a ] ) ) { return true; } } return isset( $attrs['data-cfasync'] ) && 'false' === strtolower( $attrs['data-cfasync'] ); } /** * Feature gate. During a Safety Check (?zc_test=) ONLY that feature * runs, so a difference against ?zc_off=1 is attributable to it alone. */ private function on( $feature, $force ) { if ( '' !== $force ) { $o = ZeroConfig_Options::all(); // "all", one feature, or several joined by "-" (images-assets-critical): the Safety Check // leaves out features it has already ruled out when it tests the rest together. return ( 'all' === $force || in_array( $feature, explode( '-', $force ), true ) ) && ! empty( $o['features'][ $feature ] ); } return ZeroConfig_Options::feature_on( $feature ); } /** * Images, styles and scripts now come from the edge, another host. Opening that connection * early saves a DNS + TCP + TLS round trip on the critical path (about 0.5 s on mobile). */ private function preconnect_edge( $html ) { if ( ! ( $this->stats['img'] || $this->stats['css'] || $this->stats['js'] ) ) { return $html; } $edge = (string) wp_parse_url( $this->cdn, PHP_URL_HOST ); $here = (string) wp_parse_url( home_url(), PHP_URL_HOST ); if ( '' === $edge || $edge === $here || false !== stripos( $html, 'rel="preconnect" href="' . $this->cdn . '"' ) ) { return $html; } $hint = ''; return self::after_tag( $html, '/]*>/i', $hint ); } /* ---------- images ---------- */ private function images( $html ) { $html = preg_replace_callback( '/]*>/i', array( $this, 'img_tag' ), $html ); $html = preg_replace_callback( '/]*\bsrcset=[^>]*>/i', array( $this, 'source_tag' ), $html ); $html = preg_replace_callback( '/]*\bdata-thumb=[^>]*>/i', array( $this, 'gallery_tag' ), $html ); if ( $this->lcp_preload && false === stripos( $html, 'rel="preload" as="image"' ) ) { $html = self::after_tag( $html, '/]*>/i', $this->lcp_preload ); } return $html; } /** * WooCommerce product gallery: thumbnails and the zoom image are built by its JavaScript from * data-thumb / data-thumb-srcset / data-large_image on each slide. Sending those through the * edge too keeps them in the same URL space as the main image (the browser reuses the large file * it already has for a thumbnail instead of fetching a too-small one) and optimises the zoom image. */ private function gallery_tag( $m ) { $tag = $m[0]; $attrs = $this->attrs( $tag ); if ( self::opted_out( $attrs ) || ZeroConfig_Compat::matches( $tag, (array) $this->rules['img_exclude'] ) ) { return $tag; } if ( ! empty( $attrs['data-thumb'] ) && $this->is_local_image( $attrs['data-thumb'] ) ) { $w = self::known_width( $attrs['data-thumb'], '' ); $tag = $this->set_attr( $tag, 'data-thumb', $this->img_url( $attrs['data-thumb'], $w ? $w : self::MAX_UNKNOWN_WIDTH ) ); } if ( ! empty( $attrs['data-thumb-srcset'] ) ) { $tag = $this->set_attr( $tag, 'data-thumb-srcset', $this->rewrite_srcset( $attrs['data-thumb-srcset'] ) ); } return $tag; } private function img_tag( $m ) { $tag = $m[0]; $attrs = $this->attrs( $tag ); $src = isset( $attrs['src'] ) ? $attrs['src'] : ''; $this->img_index++; if ( '' === $src || ! $this->is_local_image( $src ) || ZeroConfig_Compat::matches( $tag, (array) $this->rules['img_exclude'] ) || self::opted_out( $attrs ) ) { return $tag; } // Only a plain pixel count is a width. "100%", "auto" or "50vw" would otherwise become w_100 / w_50 and a blurry image. $width = isset( $attrs['width'] ) && ctype_digit( trim( $attrs['width'] ) ) ? (int) trim( $attrs['width'] ) : 0; if ( ! $width ) { // No width attribute (cover blocks, galleries, page builders). Without one the edge would // only convert the format and send a 4000 px camera original to a phone. $width = self::known_width( $src, isset( $attrs['class'] ) ? $attrs['class'] : '' ); } $new = $this->img_url( $src, $width ? $width : self::MAX_UNKNOWN_WIDTH ); $tag = $this->set_attr( $tag, 'src', $new ); // WooCommerce zoom / lightbox image (and lazy loaders' data-src): same edge treatment. $large_w = isset( $attrs['data-large_image_width'] ) && ctype_digit( (string) $attrs['data-large_image_width'] ) ? (int) $attrs['data-large_image_width'] : 0; foreach ( array( 'data-large_image', 'data-src' ) as $a ) { if ( ! empty( $attrs[ $a ] ) && $this->is_local_image( $attrs[ $a ] ) ) { $w = ( isset( $attrs['data-large_image'] ) && $attrs[ $a ] === $attrs['data-large_image'] && $large_w ) ? $large_w : self::known_width( $attrs[ $a ], '' ); $tag = $this->set_attr( $tag, $a, $this->img_url( $attrs[ $a ], $w ? $w : self::MAX_UNKNOWN_WIDTH ) ); } } if ( isset( $attrs['srcset'] ) ) { $tag = $this->set_attr( $tag, 'srcset', $this->rewrite_srcset( $attrs['srcset'] ) ); } elseif ( $width >= 400 ) { $set = array(); foreach ( array( 320, 480, 640, 960, 1280, 1600, 1920 ) as $w ) { if ( $w < $width ) { $set[] = $this->img_url( $src, $w ) . ' ' . $w . 'w'; } } $set[] = $new . ' ' . $width . 'w'; $tag = $this->set_attr( $tag, 'srcset', implode( ', ', $set ) ); if ( ! isset( $attrs['sizes'] ) ) { $tag = $this->set_attr( $tag, 'sizes', '(max-width: ' . $width . 'px) 100vw, ' . $width . 'px' ); } } // LCP-aware lazy loading: the first two images are likely above the fold. if ( $this->img_index <= 2 ) { if ( ! isset( $attrs['fetchpriority'] ) ) { $tag = $this->set_attr( $tag, 'fetchpriority', 'high' ); } $tag = preg_replace( '/\sloading=("|\')lazy\1/i', '', $tag ); // The first large image is the likely LCP: start fetching it from , before CSS and // scripts. Otherwise a gallery script can run first and Chrome (and Lighthouse) count the // image as painted only after it. Small images (logos, icons) are not worth a preload. if ( null === $this->lcp_preload && ( $width >= 300 || ( ! $width && $this->img_index === 1 ) ) ) { $t = $this->attrs( $tag ); $this->lcp_preload = ''; } } elseif ( ! isset( $attrs['loading'] ) && 'high' !== strtolower( (string) ( $attrs['fetchpriority'] ?? '' ) ) ) { // Not when the theme marked it fetchpriority="high": lazy and high together contradict // each other, and Chrome warns about it. $tag = $this->set_attr( $tag, 'loading', 'lazy' ); } if ( ! isset( $attrs['decoding'] ) ) { $tag = $this->set_attr( $tag, 'decoding', 'async' ); } $this->stats['img']++; return $tag; } /** Width used when nothing tells us the real one: large enough for any layout, small enough to matter. */ const MAX_UNKNOWN_WIDTH = 1920; /** * The pixel width of a local image when the tag does not say: the "-800x600" size suffix * WordPress puts in resized file names, or the attachment's metadata (class "wp-image-123"). */ private static function known_width( $src, $class ) { $path = (string) wp_parse_url( $src, PHP_URL_PATH ); if ( preg_match( '/-(\d{2,5})x\d{2,5}\.(?:jpe?g|png|gif|webp|avif)$/i', $path, $m ) ) { return (int) $m[1]; } if ( preg_match( '/(?:^|\s)wp-image-(\d+)(?:\s|$)/', $class, $m ) ) { $meta = wp_get_attachment_metadata( (int) $m[1] ); // The tag may show the upload itself while WordPress keeps a "-scaled" copy (originals over // 2560 px) as the attachment file. Either way the scaled width is the most we ever serve. $names = is_array( $meta ) ? array_filter( array( basename( (string) ( $meta['file'] ?? '' ) ), (string) ( $meta['original_image'] ?? '' ) ) ) : array(); if ( ! empty( $meta['width'] ) && in_array( basename( $path ), $names, true ) ) { return (int) $meta['width']; } } return 0; } private function source_tag( $m ) { $attrs = $this->attrs( $m[0] ); if ( empty( $attrs['srcset'] ) || ( isset( $attrs['type'] ) && 0 !== strpos( $attrs['type'], 'image/' ) && '' !== $attrs['type'] ) ) { return $m[0]; } $new = $this->rewrite_srcset( $attrs['srcset'] ); if ( $new === $attrs['srcset'] ) { return $m[0]; } return $this->set_attr( $m[0], 'srcset', $new ); } private function rewrite_srcset( $srcset ) { $out = array(); foreach ( explode( ',', $srcset ) as $cand ) { $cand = trim( $cand ); if ( '' === $cand ) { continue; } $parts = preg_split( '/\s+/', $cand ); $url = $parts[0]; $desc = isset( $parts[1] ) ? $parts[1] : ''; $w = preg_match( '/^(\d+)w$/', $desc, $wm ) ? (int) $wm[1] : 0; $out[] = ( $this->is_local_image( $url ) ? $this->img_url( $url, $w ) : $url ) . ( $desc ? ' ' . $desc : '' ); } return implode( ', ', $out ); } private function is_local_image( $url ) { if ( 0 === strpos( $url, 'data:' ) || 0 === strpos( $url, '#' ) ) { return false; } $path = (string) wp_parse_url( $url, PHP_URL_PATH ); if ( ! preg_match( '/\.(jpe?g|png|webp|avif)$/i', $path ) ) { return false; } return $this->is_local( $url ); } private function is_local( $url ) { if ( 0 === strpos( $url, '//' ) ) { $url = 'https:' . $url; } if ( 0 === strpos( $url, '/' ) ) { return true; } $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ); return '' !== $host && ( $host === $this->home_host || 'www.' . $host === $this->home_host || $host === 'www.' . $this->home_host ); } /** * /img/{site}/{opts}/{base64url(path?query)}/{original-filename} * The filename segment is ignored by the edge; it keeps the URL readable * for humans, logs and Google Images. The original file on the customer's * server is never touched or renamed. */ private function img_url( $src, $width ) { $rel = $this->relative( $src ); $opts = 'w_' . max( 0, (int) $width ) . ',f_auto'; $ver = $this->version( $src ); if ( $ver ) { $opts .= ',v_' . $ver; // A replaced file gets a new address, so the year-long CDN copy is never stale. } $name = rawurlencode( basename( (string) wp_parse_url( $src, PHP_URL_PATH ) ) ); return $this->cdn . '/img/' . $this->site_id . '/' . $opts . '/' . $this->b64( $rel ) . ( $name ? '/' . $name : '' ); } /* ---------- stylesheets ---------- */ /** True only for rel="stylesheet" (token list), not for text that merely mentions it, and not alternate styles. */ private static function is_stylesheet( array $attrs ) { if ( empty( $attrs['rel'] ) ) { return false; } $rel = preg_split( '/\s+/', strtolower( trim( $attrs['rel'] ) ) ); return in_array( 'stylesheet', $rel, true ) && ! in_array( 'alternate', $rel, true ); } private function styles( $html ) { $fonts = false; $html = preg_replace_callback( '/]*>/i', function ( $m ) use ( &$fonts ) { $attrs = $this->attrs( $m[0] ); if ( ! self::is_stylesheet( $attrs ) || empty( $attrs['href'] ) || self::opted_out( $attrs ) ) { return $m[0]; } // Google Fonts: ensure display=swap (text visible before the font arrives) and a preconnect. if ( false !== stripos( $attrs['href'], 'fonts.googleapis.com' ) ) { $fonts = true; if ( false === stripos( $attrs['href'], 'display=' ) ) { return $this->set_attr( $m[0], 'href', $attrs['href'] . ( false === strpos( $attrs['href'], '?' ) ? '?' : '&' ) . 'display=swap' ); } return $m[0]; } if ( ! $this->is_local( $attrs['href'] ) || ! preg_match( '/\.css(\?|$)/i', $attrs['href'] ) ) { return $m[0]; } if ( ZeroConfig_Compat::matches( $attrs['href'], (array) $this->rules['css_exclude'] ) ) { return $m[0]; } $this->stats['css']++; return $this->set_attr( $m[0], 'href', $this->asset_url( 'css', $attrs['href'] ) ); }, $html ); if ( $fonts && false === stripos( $html, 'href="https://fonts.gstatic.com"' ) ) { $html = preg_replace( '/]*>/i', '$0', $html, 1 ); } return $html; } /* ---------- scripts ---------- */ private function scripts( $html, $minify, $delay ) { $html = preg_replace_callback( '/]*)>/i', function ( $m ) use ( $minify, $delay ) { $tag = $m[0]; $attrs = $this->attrs( $tag ); if ( empty( $attrs['src'] ) || self::opted_out( $attrs ) ) { return $tag; } if ( isset( $attrs['type'] ) && ! preg_match( '#^(text/javascript|application/javascript|module)?$#i', $attrs['type'] ) ) { return $tag; } $src = $attrs['src']; // Known third parties: load after first interaction (or 5 s). // Consent managers must load first and on time, or trackers run before consent (GDPR) and banners pop late. if ( $delay && ! $this->is_local( $src ) && ZeroConfig_Compat::matches( $src, (array) $this->rules['delay_allow'] ) && ! ZeroConfig_Compat::matches( $src, (array) ( isset( $this->rules['delay_deny'] ) ? $this->rules['delay_deny'] : array() ) ) ) { $tag = preg_replace( '/\ssrc=("|\')[^"\']*\1/i', '', $tag ); $tag = preg_replace( '/\stype=("|\')[^"\']*\1/i', '', $tag ); $tag = $this->set_attr( $tag, 'data-zc-src', $src ); $tag = $this->set_attr( $tag, 'type', 'zc/delay' ); $this->stats['delay']++; return $tag; } if ( ! $this->is_local( $src ) ) { return $tag; } if ( ZeroConfig_Compat::matches( $src, (array) $this->rules['js_exclude'] ) ) { return $tag; } if ( $minify && preg_match( '/\.js(\?|$)/i', $src ) ) { $tag = $this->set_attr( $tag, 'src', $this->asset_url( 'js', $src ) ); } if ( ! isset( $attrs['async'] ) && ! isset( $attrs['defer'] ) && ( ! isset( $attrs['type'] ) || 'module' !== strtolower( $attrs['type'] ) ) ) { $tag = substr( $tag, 0, -1 ) . ' defer>'; } $this->stats['js']++; return $tag; }, $html ); // Inline tag snippets (Facebook Pixel, Hotjar, TikTok…) load their library themselves, so // delaying only #is', function ( $m ) use ( $allow, $deny ) { $attrs = $this->attrs( '' ); $code = $m[2]; if ( isset( $attrs['src'] ) || '' === trim( $code ) || self::opted_out( $attrs ) ) { return $m[0]; } if ( isset( $attrs['type'] ) && ! preg_match( '#^(text/javascript|application/javascript)?$#i', $attrs['type'] ) ) { return $m[0]; // JSON-LD, templates, already delayed, modules } $calls = preg_match( '/(?stats['delay']++; $tag = preg_replace( '/\stype=("|\')[^"\']*\1/i', '', '' ); return $this->set_attr( $tag, 'type', 'zc/delay' ) . $code . ''; }, $html ); } if ( $this->stats['delay'] > 0 ) { // Runs the delayed scripts in document order on first interaction (or after 5 s). External // ones keep their order (async=false); inline ones run as soon as they are put back. $loader = wp_get_inline_script_tag( implode( "\n", array( '( function () {', ' var started = false;', ' var events = [ "mousemove", "keydown", "touchstart", "wheel", "click", "scroll" ];', ' function runDelayedScripts() {', ' if ( started ) { return; }', ' started = true;', ' events.forEach( function ( name ) { removeEventListener( name, runDelayedScripts, { passive: true } ); } );', ' document.querySelectorAll( \'script[type="zc/delay"]\' ).forEach( function ( held ) {', ' var script = document.createElement( "script" );', ' for ( var i = 0; i < held.attributes.length; i++ ) {', ' var attr = held.attributes[ i ];', ' if ( "type" !== attr.name && "data-zc-src" !== attr.name ) { script.setAttribute( attr.name, attr.value ); }', ' }', ' var src = held.getAttribute( "data-zc-src" );', ' if ( src ) {', ' script.async = false; // external scripts keep document order', ' script.src = src;', ' } else {', ' script.text = held.text;', ' }', ' held.parentNode.replaceChild( script, held );', ' } );', ' }', ' events.forEach( function ( name ) { addEventListener( name, runDelayedScripts, { passive: true } ); } );', ' setTimeout( runDelayedScripts, 5000 );', '} )();', ) ), array( 'data-zc-skip' => true ) ); $html = self::before_tag( $html, '', $loader ); } return $html; } /* ---------- critical CSS ---------- */ public static function template_key() { if ( is_front_page() ) { return 'front'; } if ( is_home() ) { return 'home'; } if ( function_exists( 'is_product' ) && is_product() ) { return 'product'; } if ( function_exists( 'is_shop' ) && ( is_shop() || is_product_category() ) ) { return 'shop'; } if ( is_singular( 'post' ) ) { return 'single'; } if ( is_page() ) { return 'page'; } if ( is_archive() ) { return 'archive'; } return 'other'; } /** This page's critical CSS, or '' when there is none or this address is on the skip list. */ private static function critical_css( array $rules ) { $all = (array) ZeroConfig_Options::get( 'critical_css' ); $key = self::template_key(); $css = isset( $all[ $key ] ) ? (string) $all[ $key ] : ( isset( $all['front'] ) && 'home' === $key ? $all['front'] : '' ); $uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; return '' !== $css && ! ZeroConfig_Compat::matches( $uri, (array) $rules['critical_skip'] ) ? $css : ''; } /** * wp_enqueue_scripts: the critical CSS goes out as an enqueued inline style. critical() below then * only moves that tag in front of the first stylesheet it defers, or drops it when it defers none. */ public static function enqueue() { // Only when this page goes through end_buffer (engine on, page not skipped). if ( ! has_filter( 'wp_template_enhancement_output_buffer', array( ZeroConfig_Engine::instance(), 'end_buffer' ) ) || self::should_skip() ) { return; } $rewriter = new self(); $force = isset( $_GET['zc_test'] ) ? sanitize_text_field( wp_unslash( $_GET['zc_test'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- selects which features render, no state change $css = $rewriter->on( 'critical', $force ) ? self::critical_css( $rewriter->rules ) : ''; if ( '' !== $css ) { wp_register_style( 'zeroconfig-critical', false, array(), ZEROCONFIG_VERSION ); wp_enqueue_style( 'zeroconfig-critical' ); wp_add_inline_style( 'zeroconfig-critical', $css ); } } private function critical( $html ) { $tag_re = '#]*\bid=(["\'])zeroconfig-critical-inline-css\1[^>]*>.*?\s*#is'; if ( ! preg_match( $tag_re, $html, $found ) ) { return $html; // nothing enqueued for this page (or the theme has no wp_head) } $html = str_replace( $found[0], '', $html ); $links = array(); $html = preg_replace_callback( '/]*>/i', function ( $m ) use ( &$links ) { $attrs = $this->attrs( $m[0] ); // Only real stylesheets. Leave alone: preload/loadCSS patterns (they already have onload), opted-out tags, // and print/none stylesheets that never apply to the first paint. if ( ! self::is_stylesheet( $attrs ) || isset( $attrs['onload'] ) || self::opted_out( $attrs ) ) { return $m[0]; } $media = isset( $attrs['media'] ) ? trim( $attrs['media'] ) : ''; if ( in_array( strtolower( $media ), array( 'print', 'none' ), true ) ) { return $m[0]; } $links[] = $m[0]; $tag = preg_replace( '/\smedia\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $m[0] ); $tag = $this->set_attr( $tag, 'media', 'print' ); if ( '' !== $media && 'all' !== strtolower( $media ) ) { // Keep e.g. "(max-width: 768px)": restoring plain "all" would apply mobile-only CSS on desktop. $tag = $this->set_attr( $tag, 'data-zc-media', $media ); } $tag = $this->set_attr( $tag, 'onload', "this.media=this.getAttribute('data-zc-media')||'all';this.onload=null" ); // Mark the first deferred sheet: the critical