words in their markup. */ const UNLOAD = array( 'contact-form-7' => array( 'wpcf7' ), 'wpforms-lite' => array( 'wpforms' ), 'wpforms' => array( 'wpforms' ), 'formidable' => array( 'frm_forms', 'frm-show-form', 'frm_form' ), 'ninja-forms' => array( 'nf-form-cont', 'ninja-forms' ), 'revslider' => array( 'rs-module', 'rev_slider', 'revslider', 'rs-slide' ), 'smart-slider-3' => array( 'n2-section-smartslider', 'smartslider', 'n2-ss' ), 'ml-slider' => array( 'metaslider', 'ml-slider' ), 'jeg-elementor-kit' => array( 'jkit', 'jeg-elementor-kit', 'jeg_' ), 'elementskit-lite' => array( 'ekit', 'elementskit' ), 'royal-elementor-addons' => array( 'wpr-', 'royal-elementor' ), 'ultimate-elementor' => array( 'uael' ), 'qi-addons-for-elementor' => array( 'qodef', 'qi-addons' ), 'rometheme-for-elementor' => array( 'rkit', 'rtm-', 'rometheme' ), 'gum-elementor-addon' => array( 'gum' ), 'essential-addons-for-elementor-lite' => array( 'eael' ), 'premium-addons-for-elementor' => array( 'premium-' ), 'happy-elementor-addons' => array( 'ha-', 'happy' ), ); /** Settings of other plugins that already do one of these parts: [plugin file, option, key path or '', part]. */ public static function handled() { $out = array(); $active = (array) get_option( 'active_plugins', array() ); $on = function ( $file ) use ( $active ) { return in_array( $file, $active, true ); }; if ( $on( 'wp-rocket/wp-rocket.php' ) ) { $r = get_option( 'wp_rocket_settings', array() ); if ( ! empty( $r['delay_js'] ) ) { $out['delay'] = 'WP Rocket'; } } if ( $on( 'perfmatters/perfmatters.php' ) ) { $p = get_option( 'perfmatters_options', array() ); if ( ! empty( $p['assets']['delay_js'] ) ) { $out['delay'] = 'Perfmatters'; } if ( ! empty( $p['disable_emojis'] ) ) { $out['extras'] = 'Perfmatters'; } $out['unload'] = 'Perfmatters'; } if ( $on( 'flying-scripts/flying-scripts.php' ) ) { $out['delay'] = 'Flying Scripts'; } if ( $on( 'flying-press/flying-press.php' ) ) { $out['delay'] = 'FlyingPress'; } if ( $on( 'litespeed-cache/litespeed-cache.php' ) ) { if ( 2 === (int) get_option( 'litespeed.conf.optm-js_defer' ) ) { $out['delay'] = 'LiteSpeed Cache'; } if ( get_option( 'litespeed.conf.optm-emoji_rm' ) ) { $out['extras'] = 'LiteSpeed Cache'; } } if ( $on( 'wp-asset-clean-up/wpacu.php' ) || $on( 'wp-asset-clean-up-pro/wpacu.php' ) ) { $out['unload'] = 'Asset CleanUp'; } return $out; } /** WordPress extras: emoji detection script and styles, and the oEmbed script. Hooked on init when the part is on. */ public static function remove_extras() { remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); remove_action( 'wp_print_styles', 'print_emoji_styles' ); remove_action( 'wp_enqueue_scripts', 'wp_enqueue_emoji_styles' ); remove_filter( 'the_content_feed', 'wp_staticize_emoji' ); add_filter( 'emoji_svg_url', '__return_false' ); add_action( 'wp_footer', function () { wp_dequeue_script( 'wp-embed' ); }, 1 ); } /** The page's markup without scripts, styles and links: what is actually on the page. */ private static function markup( $html ) { $pos = stripos( $html, '' ); $body = false === $pos ? $html : substr( $html, $pos ); $body = preg_replace( '#]*>.*?#is', '', $body ); $body = preg_replace( '#]*>.*?#is', '', (string) $body ); $body = preg_replace( '#]*>#i', '', (string) $body ); return strtolower( (string) $body ); } private static function host_of( $src ) { $src = html_entity_decode( $src, ENT_QUOTES ); if ( 0 === strpos( $src, '//' ) ) { $src = 'https:' . $src; } $host = wp_parse_url( $src, PHP_URL_HOST ); return is_string( $host ) ? strtolower( $host ) : ''; } private static function host_listed( $host ) { foreach ( self::DELAY_HOSTS as $h ) { if ( $host === $h || substr( $host, -strlen( '.' . $h ) ) === '.' . $h ) { return true; } } return false; } /** Trackers and chat widgets switched to load after the first interaction, in their original order. */ public static function delay( $html ) { $count = 0; $out = preg_replace_callback( '#]*)>(.*?)#is', function ( $m ) use ( &$count ) { $attrs = $m[1]; $code = $m[2]; if ( preg_match( '#\btype\s*=\s*["\']?([^"\'\s>]+)#i', $attrs, $t ) ) { $type = strtolower( $t[1] ); if ( ! in_array( $type, array( 'text/javascript', 'application/javascript', 'module' ), true ) ) { return $m[0]; } } if ( preg_match( '#data-(no-delay|gralus-keep)#i', $attrs ) ) { return $m[0]; } $src = preg_match( '#\bsrc\s*=\s*["\']([^"\']+)["\']#i', $attrs, $s ) ? $s[1] : ''; if ( '' !== $src ) { if ( ! self::host_listed( self::host_of( $src ) ) ) { return $m[0]; } } else { $hit = false; foreach ( self::DELAY_INLINE as $word ) { if ( false !== stripos( $code, $word ) ) { $hit = true; break; } } if ( ! $hit || strlen( $code ) > 6000 ) { return $m[0]; } } ++$count; $kept = ''; if ( isset( $type ) && 'module' === $type ) { $kept = ' data-gralus-type="module"'; } $attrs = preg_replace( '#\btype\s*=\s*["\']?[^"\'\s>]+["\']?#i', '', $attrs ); $attrs = preg_replace( '#\bsrc\s*=#i', 'data-gralus-src=', (string) $attrs ); return ''; }, $html ); if ( ! is_string( $out ) || ! $count ) { return $html; } // Code that is not delayed may still push to dataLayer before Tag Manager arrives: keep the array there from the start. $out = preg_replace( '#]*>#i', '$0', $out, 1 ); $loader = ''; $pos = strripos( $out, '' ); return false === $pos ? $out . $loader : substr( $out, 0, $pos ) . $loader . substr( $out, $pos ); } /** Files of plugins with nothing on this page (see UNLOAD), with their inline before/after scripts. */ public static function unload( $html ) { $markup = self::markup( $html ); $out = $html; $handles = array(); foreach ( self::UNLOAD as $slug => $words ) { if ( false === stripos( $html, '/wp-content/plugins/' . $slug . '/' ) ) { continue; } $used = false; foreach ( $words as $w ) { if ( false !== strpos( $markup, $w ) ) { $used = true; break; } } if ( $used ) { continue; } $path = preg_quote( '/wp-content/plugins/' . $slug . '/', '#' ); $out = preg_replace_callback( '#]*\bsrc\s*=\s*["\'][^"\']*' . $path . '[^"\']*["\'][^>]*>\s*\s*#i', function ( $m ) use ( &$handles ) { if ( preg_match( '#\bid\s*=\s*["\']([^"\']+)-js["\']#i', $m[0], $id ) ) { $handles[] = $id[1]; } return ''; }, (string) $out ); $out = preg_replace( '#]*\bhref\s*=\s*["\'][^"\']*' . $path . '[^"\']*["\'][^>]*>\s*#i', '', (string) $out ); } foreach ( array_unique( $handles ) as $h ) { $out = preg_replace( '#]*\bid\s*=\s*["\']' . preg_quote( $h, '#' ) . '-js-(before|after)["\'][^>]*>.*?\s*#is', '', (string) $out ); } return is_string( $out ) && '' !== $out ? $out : $html; } /** The block editor's CSS when the page has no block-editor content (pages built with Elementor or the classic editor). */ public static function blocks( $html ) { if ( false !== strpos( self::markup( $html ), 'wp-block-' ) ) { return $html; } $out = preg_replace( '#]*\bid\s*=\s*["\'](wp-block-library|wp-block-library-theme|classic-theme-styles)-css["\'][^>]*>\s*#i', '', $html ); $out = preg_replace( '#]*\bid\s*=\s*["\'](wp-block-library|wp-block-library-theme|classic-theme-styles)-inline-css["\'][^>]*>.*?\s*#is', '', (string) $out ); return is_string( $out ) && '' !== $out ? $out : $html; } }