File "class-prefetch.php"

Full Path: /home/theinspectionboy/public_html/suffolk/includes/class-prefetch.php
File size: 9.75 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * Class and methods to add DNS prefetch hints to your page.
 *
 * @link https://ewww.io/swis/
 * @package SWIS_Performance
 */

namespace SWIS;

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

/**
 * Searches the page for linked domains and adds prefetch headers for the top 5 non-local domains.
 */
final class Prefetch extends Base {

	/**
	 * The local domain.
	 *
	 * @access protected
	 * @var string $local_domain
	 */
	protected $local_domain = '';

	/**
	 * Existing prefetch hints.
	 *
	 * @access protected
	 * @var array $hints
	 */
	protected $hints = array(
		'dns-prefetch' => array(),
		'preconnect'   => array(),
	);

	/**
	 * Register actions and filters for DNS Prefetch.
	 */
	function __construct() {
		parent::__construct();
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );

		if ( ! is_admin() ) {
			$this->local_domain = $this->parse_url( get_home_url(), PHP_URL_HOST );

			// Start an output buffer before any output starts.
			add_filter( $this->prefix . 'filter_page_output', array( $this, 'filter_page_output' ) );

			// Get a list of existing prefetch hints.
			add_filter( 'wp_resource_hints', array( $this, 'get_resource_hints' ), PHP_INT_MAX, 2 );

			// Apply internal exclusions.
			add_filter( 'swis_skip_prefetch', array( $this, 'skip_prefetch' ), 10, 2 );
			add_filter( 'swis_skip_preconnect', array( $this, 'skip_prefetch' ), 10, 3 );
			add_filter( 'swis_preconnect_domains', array( $this, 'add_domains' ) );
			add_filter( 'swis_prefetch_domains', array( $this, 'add_domains' ) );
		}
	}

	/**
	 * Add domains specified by the user.
	 *
	 * @param array $domains A list of third-party domains found in the content.
	 * @return array The list of domains, possibly with more added.
	 */
	function add_domains( $domains ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		$user_domains = $this->get_option( 'pre_hint_domains' );
		if ( $this->is_iterable( $user_domains ) ) {
			$this->debug_message( 'have domains to add' );
			foreach ( $user_domains as $domain ) {
				$domain = filter_var( $domain, FILTER_VALIDATE_DOMAIN );
				$this->debug_message( "attempting to add $domain" );
				if ( $domain && ! isset( $domains[ $domain ] ) ) {
					$this->debug_message( 'adding domain that did not exist' );
					$domains = array_merge( array( $domain => 9999 ), $domains );
				} elseif ( $domain && isset( $domains[ $domain ] ) && count( $domains ) > 5 ) {
					$this->debug_message( 're-prioritizing user-defined domain' );
					unset( $domains[ $domain ] );
					$domains = array_merge( array( $domain => 9999 ), $domains );
				}
			}
		}
		return $domains;
	}

	/**
	 * Exclude domain from being prefetched.
	 *
	 * @param bool   $skip Whether SWIS should skip processing.
	 * @param string $domain The domain name to prefetch.
	 * @param string $relationship_type The type of hint being filtered: dns-prefetch, preconnect, etc. Defaults to 'dns-prefetch'.
	 * @return bool True to skip the domain, unchanged otherwise.
	 */
	function skip_prefetch( $skip, $domain, $relationship_type = 'dns-prefetch' ) {
		$skip_domains = apply_filters( 'swis_' . $relationship_type . '_skip_domains', array( $this->local_domain, 'gmpg.org', 'www.w3.org', 'api.w.org' ) );
		foreach ( $skip_domains as $skip_domain ) {
			if ( $skip_domain === $domain ) {
				return true;
			}
		}
		foreach ( $this->hints[ $relationship_type ] as $dns_hint ) {
			if ( false !== strpos( $dns_hint, $domain ) ) {
				return true;
			}
		}
		return $skip;
	}

	/**
	 * Checks existing DNS prefetch hints and stores the list for later.
	 *
	 * @param array  $hints A list of hints for a particular relationship type.
	 * @param string $relationship_type The type of hint being filtered: dns-prefetch, preconnect, etc.
	 * @return array The list of hints, unaltered.
	 */
	function get_resource_hints( $hints, $relationship_type ) {
		if ( is_array( $hints ) && ( 'dns-prefetch' === $relationship_type || 'preconnect' === $relationship_type ) ) {
			$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
			foreach ( $hints as $key => $hint ) {
				if ( $this->get_option( 'optimize_fonts_css' ) && is_string( $hint ) && false !== strpos( $hint, 'fonts.googleapis.com' ) ) {
					unset( $hints[ $key ] );
					continue;
				}
				if ( is_string( $hint ) ) {
					$this->debug_message( "$relationship_type hint: $hint" );
					$this->hints[ $relationship_type ][] = $hint;
				} elseif ( is_array( $hint ) && isset( $hint['href'] ) ) {
					$hint = $hint['href'];
					$this->debug_message( "$relationship_type hint: $hint" );
					$this->hints[ $relationship_type ][] = $hint;
				} elseif ( $this->function_exists( 'print_r' ) ) {
					$this->debug_message( print_r( $hint, true ) );
				}
			}
		}
		return $hints;
	}

	/**
	 * Get linked/resource domains from content.
	 *
	 * @param string $content The HTML content through which to search.
	 * @return array A list of domains found in the HTML content.
	 */
	function get_domains( $content ) {
		if ( empty( $content ) || ! is_string( $content ) ) {
			return array();
		}
		$domains = array();
		$links   = array();
		preg_match_all( '#\s(?>rel|src)\s*=\s*["\']https?:\/\/([^\/?\'"]{4,256})[/?\'"]#is', $content, $links );
		if ( $this->is_iterable( $links[1] ) ) {
			foreach ( $links[1] as $domain ) {
				$domains[] = $domain;
			}
		}
		$links = array();
		preg_match_all( '#[^a-zA-Z0-9]url\s*?\(\s*["\']?\s*?https?:\/\/([^\/?\'"]{4,256})[/?\'"]#is', $content, $links );
		if ( $this->is_iterable( $links[1] ) ) {
			foreach ( $links[1] as $domain ) {
				$domains[] = $domain;
			}
		}
		return $domains;
	}
	/**
	 * Parse page content looking for jQuery script tag to rewrite.
	 *
	 * @param string $content The HTML content to parse.
	 * @return string The filtered HTML content.
	 */
	function filter_page_output( $content ) {
		$this->debug_message( '<b>' . __METHOD__ . '()</b>' );
		if ( $this->is_json( $content ) ) {
			return $content;
		}
		if ( ! defined( 'SWIS_NO_PREFETCH' ) || ! SWIS_NO_PREFETCH ) {
			$preconnect_html    = '';
			$prefetch_html      = '';
			$preconnect_domains = array();
			$prefetch_domains   = array();
			$font_domains       = array(
				'use.fontawesome.com',
				'fonts.gstatic.com',
				'use.typekit.com',
				'use.typekit.net',
				'cloud.webtype.com',
			);

			$domains = $this->get_domains( $content );
			foreach ( $domains as $domain ) {
				if ( ! apply_filters( 'swis_skip_preconnect', false, $domain ) ) {
					if ( isset( $preconnect_domains[ $domain ] ) ) {
						$preconnect_domains[ $domain ]++;
					} else {
						$preconnect_domains[ $domain ] = 1;
					}
				}
				if ( ! apply_filters( 'swis_skip_prefetch', false, $domain ) ) {
					if ( isset( $prefetch_domains[ $domain ] ) ) {
						$prefetch_domains[ $domain ]++;
					} else {
						$prefetch_domains[ $domain ] = 1;
					}
				}
			}
			if ( strpos( $content, '//fonts.gstatic.com/' ) || strpos( $content, '//fonts.googleapis.com/' ) ) {
				$preconnect_domains['fonts.gstatic.com'] = 999;
				$prefetch_domains['fonts.gstatic.com']   = 999;
			}

			arsort( $preconnect_domains, SORT_NUMERIC );
			arsort( $prefetch_domains, SORT_NUMERIC );
			/**
			 * Allow adding or removing preconnect domains (or altering the priority).
			 * Increase the array value for a given domain to increase the priority.
			 *
			 * @param array $preconnect_domains Single-dimensional associative array.
			 *              The array keys are the domains found within the page.
			 *              Array values indicate how many times a domain occurred within the page.
			 */
			$preconnect_domains = apply_filters( 'swis_preconnect_domains', $preconnect_domains );
			/**
			 * Allow adding or removing dns-prefetch domains (or altering the priority).
			 * Increase the array value for a given domain to increase the priority.
			 *
			 * @param array $prefetch_domains Single-dimensional associative array.
			 *              The array keys are the domains found within the page.
			 *              Array values indicate how many times a domain occurred within the page.
			 */
			$prefetch_domains = apply_filters( 'swis_prefetch_domains', $prefetch_domains );

			/**
			 * Adjust how many DNS records may be prefetched.
			 *
			 * @param int 5 How many records will be prefetched, prioritized by the # of references within any given page.
			 */
			$max_prefetch_hints = apply_filters( 'swis_prefetch_max_domains', 5 );
			/**
			 * Adjust how many preconnect hints are allowed.
			 *
			 * @param int $max_prefetch_hints How many preconnect hints are allowed, defaults to the same # as the dns-prefetch hints.
			 */
			$max_preconnect_hints  = apply_filters( 'swis_preconnect_max_domains', $max_prefetch_hints );
			$max_prefetch_hints   -= count( $this->hints['dns-prefetch'] );
			$max_preconnect_hints -= count( $this->hints['preconnect'] );

			foreach ( $prefetch_domains as $domain => $count ) {
				if ( $max_prefetch_hints > 0 ) {
					$this->debug_message( "prefetching $domain" );
					$prefetch_html .= "<link rel='dns-prefetch' href='//$domain' />\n";
				} else {
					$this->debug_message( "not prefetching $domain" );
				}
				$max_prefetch_hints--;
			}
			foreach ( $preconnect_domains as $domain => $count ) {
				if ( $max_preconnect_hints > 0 ) {
					$this->debug_message( "preconnecting $domain" );
					if ( in_array( $domain, $font_domains, true ) ) {
						$preconnect_html .= "<link rel='preconnect' href='//$domain' crossorigin />\n";
					} else {
						$preconnect_html .= "<link rel='preconnect' href='//$domain' />\n";
					}
				} else {
					$this->debug_message( "not preconnecting $domain" );
				}
				$max_preconnect_hints--;
			}
			// 'preconnect' hints go first.
			if ( $prefetch_html || $preconnect_html ) {
				$pos = strpos( $content, '</title>' );
				if ( false !== $pos ) {
					$content = substr_replace( $content, "</title>\n$preconnect_html" . $prefetch_html, $pos, strlen( '</title>' ) );
				}
			}
		}
		return $content;
	}
}