File "class-defer-css.php"

Full Path: /home/theinspectionboy/public_html/suffolk/includes-20250622113618/class-defer-css.php
File size: 7.22 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * Class and methods to defer CSS and include critical CSS.
 *
 * @link https://ewww.io/swis/
 * @package SWIS_Performance
 */

namespace SWIS;
use MatthiasMullie\Minify;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Enables plugin to filter CSS tags and defer them.
 */
final class Defer_CSS extends Page_Parser {

	/**
	 * A list of user-defined exclusions, populated by validate_user_exclusions().
	 *
	 * @access protected
	 * @var array $user_exclusions
	 */
	protected $user_exclusions = array();

	/**
	 * Register actions and filters for CSS Defer.
	 */
	function __construct() {
		if ( $this->get_option( 'critical_css' ) ) {
			add_filter( 'wp_head', array( $this, 'inline_critical_css' ), 1 );
			add_filter( 'swis_filter_page_output', array( $this, 'inline_critical_js' ) );
		}
		if ( ! $this->get_option( 'defer_css' ) ) {
			return;
		}
		parent::__construct();
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		$uri = add_query_arg( null, null );
		$this->debug_message( "request uri is $uri" );

		/**
		 * Allow pre-empting CSS defer by page.
		 *
		 * @param bool Whether to skip parsing the page.
		 * @param string $uri The URL of the page.
		 */
		if ( apply_filters( 'swis_skip_css_defer_by_page', false, $uri ) ) {
			return;
		}

		// Overrides for user exclusions.
		add_filter( 'swis_skip_css_defer', array( $this, 'skip_css_defer' ), 10, 2 );

		if ( ! defined( 'SWIS_KEEP_DASHICONS' ) || ! SWIS_KEEP_DASHICONS ) {
			// Make sure dashicons are not loaded on front-end for visitors.
			add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_dashicons' ), 11 );
		}

		// Get all the script/css urls and rewrite them (if enabled).
		add_filter( 'style_loader_tag', array( $this, 'defer_css' ), 20 );
		add_filter( 'swis_elements_link_tag', array( $this, 'defer_css' ) );

		$this->validate_user_exclusions();
	}

	/**
	 * Is there a place for this (or maybe also for emoji)?
	 */
	function dequeue_dashicons() {
		if ( ! is_user_logged_in() ) {
			wp_deregister_style( 'dashicons' );
		}
	}

	/**
	 * Validate the user-defined exclusions.
	 */
	function validate_user_exclusions() {
		$user_exclusions = $this->get_option( 'defer_css_exclude' );
		if ( ! empty( $user_exclusions ) ) {
			if ( is_string( $user_exclusions ) ) {
				$user_exclusions = array( $user_exclusions );
			}
			if ( is_array( $user_exclusions ) ) {
				foreach ( $user_exclusions as $exclusion ) {
					if ( ! is_string( $exclusion ) ) {
						continue;
					}
					$this->user_exclusions[] = $exclusion;
				}
			}
		}
	}

	/**
	 * Exclude CSS from being processed based on user specified list.
	 *
	 * @param boolean $skip Whether SWIS should skip processing.
	 * @param string  $tag The CSS link tag HTML.
	 * @return boolean True to skip the resource, unchanged otherwise.
	 */
	function skip_css_defer( $skip, $tag ) {
		if ( $this->user_exclusions ) {
			foreach ( $this->user_exclusions as $exclusion ) {
				if ( false !== strpos( $tag, $exclusion ) ) {
					$this->debug_message( __METHOD__ . "(); user excluded $tag via $exclusion" );
					return true;
				}
			}
		}
		return $skip;
	}

	/**
	 * Rewrites a CSS link tag to be deferred.
	 *
	 * @param string $tag HTML for the CSS resource.
	 * @return string The deferred version of the resource, if it was allowed.
	 */
	function defer_css( $tag ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		if ( ! $this->is_frontend() ) {
			return $tag;
		}
		if ( strpos( $tag, 'admin-bar.min.css' ) ) {
			return $tag;
		}
		if ( false !== strpos( $tag, 'async' ) ) {
			return $tag;
		}
		if ( false !== strpos( $tag, 'defer' ) ) {
			return $tag;
		}
		if ( false !== strpos( $tag, 'asset-clean' ) ) {
			return $tag;
		}
		if ( apply_filters( 'swis_skip_css_defer', false, $tag ) ) {
			return $tag;
		}
		if ( false === strpos( $tag, 'preload' ) && false === strpos( $tag, 'data-swis' ) ) {
			$this->debug_message( trim( $tag ) );
			$async_tag = str_replace( " media='all'", " media='print' data-swis='loading' onload='this.media=\"all\";this.dataset.swis=\"loaded\"'", $tag );
			if ( $tag === $async_tag ) {
				$async_tag = str_replace( " media=''", " media='print' data-swis='loading' onload='this.media=\"all\";this.dataset.swis=\"loaded\"'", $tag );
			}
			// Run it through for preloading if possible.
			$async_tag = $this->preload_css( $async_tag, $tag );
			// If we got a new tag, let's go!
			if ( $tag !== $async_tag ) {
				$this->debug_message( trim( $async_tag ) );
				return $async_tag . '<noscript>' . trim( $tag ) . "</noscript>\n";
			}
		}
		return $tag;
	}

	/**
	 * Modify an async link/CSS tag for preloading.
	 *
	 * @param string $async_tag The async version of a <link...> tag.
	 * @param string $tag The original version of a <link...> tag.
	 * @return string The tag with a preloader added, if applicable.
	 */
	function preload_css( $async_tag, $tag ) {
		if ( false !== strpos( $tag, "rel='stylesheet'" ) ) {
			$allowed_to_preload = array(
				'avada-styles/',              // Avada dynamic CSS.
				'bb-plugin/cache/',           // Beaver Builder dynamic CSS.
				'bb-plugin/css/',             // Beaver Builder plugin CSS.
				'brizy/public/',              // Brizy dynamic CSS.
				'brizy-pro/public/',          // Brizy Pro dynamic CSS.
				'dist/block-library/',        // Gutenberg (stock WP) CSS.
				'build/block-library/',       // Gutenberg (plugin) CSS.
				'elementor/assets/css',       // Elementor plugin stock CSS.
				'elementor/css',              // Elementor dynamic CSS.
				'elementor-pro/assets/css',   // Elementor Pro stock CSS.
				'fusion-builder/',            // Avada Builder stock CSS.
				'fusion-core/',               // Avada Core stock CSS.
				'fusion-styles/',             // Avada dynamic CSS.
				'generateblocks/style',       // GenerateBlocks dynamic CSS.
				'component-framework/oxygen', // Oxygen plugin CSS.
				'/oxygen/css/',               // Oxygen dynamic CSS.
				'siteorigin-panels/css/',     // SiteOrigin plugin CSS.
				'td-composer/assets',         // TagDiv Composer (builder from Newspaper Theme).
				'wp-content/themes/',         // Theme CSS.
			);
			$allowed_to_preload = apply_filters( 'swis_defer_css_preload_list', $allowed_to_preload );
			foreach ( $allowed_to_preload as $allowed ) {
				if ( empty( $allowed ) ) {
					continue;
				}
				if ( false !== strpos( $tag, $allowed ) ) {
					$async_tag = str_replace( array( " rel='stylesheet'", " id='" ), array( " rel='preload' as='style'", " data-id='" ), $tag ) . $async_tag;
				}
			}
		}
		return $async_tag;
	}

	/**
	 * Insert cricital CSS rules in to the header to prevent FOUC.
	 */
	function inline_critical_css() {
		$minifier = new Minify\CSS( $this->get_option( 'critical_css' ) );
		echo "<style id='swis-critical-css'>\n" . wp_kses( $minifier->minify(), 'strip' ) . "\n</style>\n";
	}

	/**
	 * Insert the JS to remove the Critical CSS section once all the CSS has loaded.
	 *
	 * @param string $buffer The HTML content of the page.
	 * @return string The altered HTML.
	 */
	function inline_critical_js( $buffer ) {
		$script_name   = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? 'critical-css-remove.js' : 'critical-css-remove.min.js';
		$inline_script = file_get_contents( SWIS_PLUGIN_PATH . 'assets/' . $script_name );
		return preg_replace( '#</body>#i', '<script>' . $inline_script . '</script></body>', $buffer, 1 );
	}
}