JezK
Edit File: cdn.cls.php
<?php /** * CDN handling for LiteSpeed Cache. * * Rewrites eligible asset URLs to configured CDN endpoints and integrates with WordPress filters. * * @since 1.2.3 * @package LiteSpeed */ namespace LiteSpeed; defined( 'WPINC' ) || exit(); /** * Class CDN * * Processes page content and WordPress asset URLs to map to CDN domains according to settings. */ class CDN extends Root { const LOG_TAG = '[CDN]'; const BYPASS = 'LITESPEED_BYPASS_CDN'; /** Maximum nested noscript/template levels re-parsed for CDN rewriting; deeper content stays untouched. */ const FRAGMENT_MAX_DEPTH = 8; /** * The working HTML/content buffer being processed. * * @var string */ private $content; /** * Whether CDN feature is enabled. * * @var bool */ private $_cfg_cdn; /** * List of original site URLs (may include wildcards) to be replaced. * * @var string[] */ private $_cfg_url_ori; /** * List of directories considered internal/original for CDN rewriting. * * @var string[] */ private $_cfg_ori_dir; /** * CDN mapping rules; keys include mapping kinds or file extensions, values are URL(s). * * @var array<string,string|string[]> */ private $_cfg_cdn_mapping = []; /** * List of URL substrings/regex used to exclude items from CDN. * * @var string[] */ private $_cfg_cdn_exclude; /** * Hosts used by CDN mappings for quick membership checks. * * @var string[] */ private $cdn_mapping_hosts = []; /** * Auto-managed CDN filetype map keyed by mapping toggle. * * Single source of truth for the filetypes implicitly handled by the * Include Images / Include CSS / Include JS toggles. Used by both * CDN::init() (auto-injection) and Admin_Settings::save() (auto-stripping) * so that the two sides stay in sync. * * @since 7.9 * @access public * @return array<string,string[]> */ public static function auto_managed_filetypes() { return apply_filters( 'litespeed_cdn_auto_managed_filetypes', [ Base::CDN_MAPPING_INC_IMG => [ '.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.avif' ], Base::CDN_MAPPING_INC_CSS => [ '.css' ], Base::CDN_MAPPING_INC_JS => [ '.js' ], ] ); } /** * Validate a CDN URL prefix without a DNS lookup or an outbound request. * * @since 7.9.2 * @param mixed $url URL prefix. * @return bool */ public static function valid_mapping_url( $url ) { if ( ! is_string( $url ) || preg_match( '/[\s\x00-\x1f\x7f\'"<>\\\\]/', $url ) ) { return false; } $parts = wp_parse_url( $url ); return is_array( $parts ) && isset( $parts['scheme'] ) && 'https' === strtolower( $parts['scheme'] ) && ! empty( $parts['host'] ) && ! isset( $parts['user'] ) && ! isset( $parts['pass'] ); } /** * List stored CDN mapping URLs that init() ignores because they fail valid_mapping_url(). * * Read-only: stored values are never migrated or changed. * * @since 7.9.2 * @return array<int,string> Unique non-empty invalid URLs, in stored order. */ public function invalid_mapping_urls() { $mapping = $this->conf( Base::O_CDN_MAPPING ); if ( ! is_array( $mapping ) ) { return []; } $invalid = []; foreach ( $mapping as $row ) { if ( ! is_array( $row ) || empty( $row[ Base::CDN_MAPPING_URL ] ) ) { continue; } $url = $row[ Base::CDN_MAPPING_URL ]; if ( ! self::valid_mapping_url( $url ) ) { $invalid[] = is_string( $url ) ? $url : ''; } } return array_values( array_unique( $invalid ) ); } /** * Build the escaped warning HTML listing ignored non-https CDN mapping URLs. * * @since 7.9.2 * @param array<int,string> $urls Invalid URLs from invalid_mapping_urls(). * @return string Escaped HTML, empty when there is nothing to report. */ public static function invalid_mapping_msg( $urls ) { if ( empty( $urls ) ) { return ''; } $list = []; foreach ( $urls as $url ) { $list[] = '<code>' . esc_html( '' === $url ? __( '(invalid value)', 'litespeed-cache' ) : $url ) . '</code>'; } return esc_html__( 'These CDN mapping URLs are ignored because they are not valid https:// URLs', 'litespeed-cache' ) . ': ' . implode( ', ', $list ) . '. ' . esc_html__( 'Change them to valid https:// URLs; CDN mappings cannot be saved until they are corrected.', 'litespeed-cache' ); } /** * Initialize CDN integration and register filters if enabled. * * @since 1.2.3 * @return void */ public function init() { self::debug2( 'init' ); if ( defined( self::BYPASS ) ) { self::debug2( 'CDN bypass' ); return; } if ( ! Router::can_cdn() ) { if ( ! defined( self::BYPASS ) ) { define( self::BYPASS, true ); } return; } $this->_cfg_cdn = $this->conf( Base::O_CDN ); if ( ! $this->_cfg_cdn ) { if ( ! defined( self::BYPASS ) ) { define( self::BYPASS, true ); } return; } $this->_cfg_url_ori = $this->conf( Base::O_CDN_ORI ); // Parse cdn mapping data to array( 'filetype' => 'url' ) $mapping_to_check = [ Base::CDN_MAPPING_INC_IMG, Base::CDN_MAPPING_INC_CSS, Base::CDN_MAPPING_INC_JS ]; $auto_managed = self::auto_managed_filetypes(); foreach ( $this->conf( Base::O_CDN_MAPPING ) as $v ) { if ( ! $v[ Base::CDN_MAPPING_URL ] ) { continue; } $this_url = $v[ Base::CDN_MAPPING_URL ]; if ( ! self::valid_mapping_url( $this_url ) ) { self::debug( 'Bypassed an invalid CDN mapping URL' ); continue; } $this_host = wp_parse_url( $this_url, PHP_URL_HOST ); // Check img/css/js foreach ( $mapping_to_check as $to_check ) { if ( $v[ $to_check ] ) { self::debug2( 'mapping ' . $to_check . ' -> ' . $this_url ); // If filetype to url is one to many, make url be an array $this->_append_cdn_mapping( $to_check, $this_url ); // Auto-inject extensions managed by this toggle via _append_cdn_mapping() to preserve multi-CDN one-to-many semantics. if ( ! empty( $auto_managed[ $to_check ] ) ) { foreach ( $auto_managed[ $to_check ] as $ext ) { $this->_append_cdn_mapping( $ext, $this_url ); } // Set the FILETYPE sentinel so _replace_file_types() runs even with an empty manual textarea, otherwise CSS/JS replacement is silently broken. $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] = true; } if ( ! in_array( $this_host, $this->cdn_mapping_hosts, true ) ) { $this->cdn_mapping_hosts[] = $this_host; } } } // Check file types if ( $v[ Base::CDN_MAPPING_FILETYPE ] ) { foreach ( $v[ Base::CDN_MAPPING_FILETYPE ] as $v2 ) { $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] = true; // If filetype to url is one to many, make url be an array $this->_append_cdn_mapping( $v2, $this_url ); if ( ! in_array( $this_host, $this->cdn_mapping_hosts, true ) ) { $this->cdn_mapping_hosts[] = $this_host; } } self::debug2( 'mapping ' . implode( ',', $v[ Base::CDN_MAPPING_FILETYPE ] ) . ' -> ' . $this_url ); } } if ( ! $this->_cfg_url_ori || ! $this->_cfg_cdn_mapping ) { if ( ! defined( self::BYPASS ) ) { define( self::BYPASS, true ); } return; } $this->_cfg_ori_dir = $this->conf( Base::O_CDN_ORI_DIR ); // In case user customized upload path if ( defined( 'UPLOADS' ) ) { $this->_cfg_ori_dir[] = UPLOADS; } // Check if need preg_replace $this->_cfg_url_ori = Utility::wildcard2regex( $this->_cfg_url_ori ); $this->_cfg_cdn_exclude = $this->conf( Base::O_CDN_EXC ); if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_IMG ] ) ) { // Hook to srcset if ( function_exists( 'wp_calculate_image_srcset' ) ) { add_filter( 'wp_calculate_image_srcset', [ $this, 'srcset' ], 999 ); } // Hook to mime icon add_filter( 'wp_get_attachment_image_src', [ $this, 'attach_img_src' ], 999 ); add_filter( 'wp_get_attachment_url', [ $this, 'url_img' ], 999 ); } if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_CSS ] ) ) { add_filter( 'style_loader_src', [ $this, 'url_css' ], 999 ); } if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_JS ] ) ) { add_filter( 'script_loader_src', [ $this, 'url_js' ], 999 ); } add_filter( 'litespeed_buffer_finalize', [ $this, 'finalize' ], 30 ); } /** * Associate all filetypes with CDN URL. * * @since 2.0 * @access private * * @param string $filetype Mapping key (e.g., extension or mapping constant). * @param string $url CDN base URL to use for this mapping. * @return void */ private function _append_cdn_mapping( $filetype, $url ) { // If filetype to url is one to many, make url be an array if ( empty( $this->_cfg_cdn_mapping[ $filetype ] ) ) { $this->_cfg_cdn_mapping[ $filetype ] = $url; } elseif ( is_array( $this->_cfg_cdn_mapping[ $filetype ] ) ) { // Append url to filetype $this->_cfg_cdn_mapping[ $filetype ][] = $url; } else { // Convert _cfg_cdn_mapping from string to array $this->_cfg_cdn_mapping[ $filetype ] = [ $this->_cfg_cdn_mapping[ $filetype ], $url ]; } } /** * Whether the given type is included in CDN mappings. * * @since 1.6.2.1 * * @param string $type 'css' or 'js'. * @return bool True if included in CDN. */ public function inc_type( $type ) { if ( 'css' === $type && ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_CSS ] ) ) { return true; } if ( 'js' === $type && ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_JS ] ) ) { return true; } return false; } /** * Run CDN processing on finalized buffer. * NOTE: After cache finalized, cannot change cache control. * * @since 1.2.3 * @access public * * @param string $content The HTML/content buffer. * @return string The processed content. */ public function finalize( $content ) { // Bypass REST JSON: HTML rewrites would break its escaping. Must check here, not init() — REST_REQUEST is undefined there. if ( $this->cls( 'REST' )->is_rest() ) { self::debug( 'bypassed: REST response' ); return $content; } $this->content = $content; $this->_finalize(); return $this->content; } /** * Replace eligible URLs with CDN URLs in the working buffer. * * @since 1.2.3 * @access private * @return void */ private function _finalize() { if ( defined( self::BYPASS ) ) { return; } self::debug( 'CDN _finalize' ); // Start replacing img src if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_IMG ] ) ) { $this->_replace_img_css(); } if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] ) ) { $this->_replace_file_types(); } } /** * Parse all file types and replace according to configured attributes. * * @since 1.2.3 * @access private * @return void */ private function _replace_file_types() { $ele_to_check = $this->conf( Base::O_CDN_ATTR ); foreach ( $ele_to_check as $v ) { if ( ! $v || false === strpos( $v, '.' ) ) { self::debug2( 'replace setting bypassed: no . attribute ' . $v ); continue; } self::debug2( 'replace attribute ' . $v ); $v = explode( '.', $v ); $attr = preg_quote( $v[1], '#' ); if ( $v[0] ) { $pattern = '#<' . preg_quote( $v[0], '#' ) . '([^>]+)' . $attr . '=([\'"])(.+)\g{2}#iU'; } else { $pattern = '# ' . $attr . '=([\'"])(.+)\g{1}#iU'; } preg_match_all( $pattern, $this->content, $matches ); if (empty($matches[$v[0] ? 3 : 2])) { continue; } foreach ($matches[$v[0] ? 3 : 2] as $k2 => $url) { // self::debug2( 'check ' . $url ); $postfix = '.' . pathinfo((string) wp_parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); if (!array_key_exists($postfix, $this->_cfg_cdn_mapping)) { // self::debug2( 'non-existed postfix ' . $postfix ); continue; } self::debug2( 'matched file_type ' . $postfix . ' : ' . $url ); $url2 = $this->rewrite( $url, Base::CDN_MAPPING_FILETYPE, $postfix ); if ( ! $url2 ) { continue; } $attr_str = str_replace( $url, $url2, $matches[0][ $k2 ] ); $this->content = str_replace( $matches[0][ $k2 ], $attr_str, $this->content ); } } } /** * Rewrite image src attributes and inline CSS url() values in one token-aware pass. * * @since 1.2.3 * @since 7.9.2 Merged image and inline CSS passes; also rewrites noscript bodies and template content. * @access private * @return void */ private function _replace_img_css() { $content = $this->_rewrite_fragment( $this->content, 0 ); if ( null !== $content ) { $this->content = $content; } else { self::debug( 'CDN image/CSS parsing skipped: ambiguous HTML or PCRE error ' . preg_last_error() ); } } /** * Rewrite img src and inline CSS url() inside an HTML fragment. * * Noscript bodies and template content are parsed again as HTML so fallback images still map; script, textarea, svg and math stay opaque. * * @since 7.9.2 * @access private * @param string $html HTML fragment. * @param int $depth Current noscript/template nesting depth. * @return string|null Rewritten fragment, or null when the fragment could not be tokenized. */ private function _rewrite_fragment( $html, $depth ) { return Utility::html_tokens( $html, function ( $token ) use ( $depth ) { if ( ! empty( $token['raw'] ) ) { $raw = strtolower( $token['raw'] ); if ( ! empty( $token['opaque'] ) || '' === $token['close'] ) { return $token[0]; } if ( 'style' === $raw ) { return '<' . $token['raw'] . $token['raw_attrs'] . '>' . $this->_rewrite_css_urls( $token['body'] ) . $token['close']; } if ( 'noscript' === $raw && $depth < self::FRAGMENT_MAX_DEPTH ) { $body = $this->_rewrite_fragment( $token['body'], $depth + 1 ); return null === $body ? $token[0] : '<' . $token['raw'] . $token['raw_attrs'] . '>' . $body . $token['close']; } if ( 'template' === $raw && $depth < self::FRAGMENT_MAX_DEPTH ) { return $this->_rewrite_template( $token[0], $token['close'], $depth ); } return $token[0]; } if ( empty( $token['tag'] ) || empty( $token['end'] ) ) { return $token[0]; } $is_img = 'img' === strtolower( $token['tag'] ); $src = false; // Consume every attribute, including non-style quoted values, before advancing; like browsers, whitespace, `/` runs or a just-closed quote separate attributes (`<img/src=x>`, `alt="x"src=y`). $attrs = preg_replace_callback( '~\G([\s/]+|(?<=["\']))([^\s=/>]+)(\s*=\s*)("[^"]*"|\'[^\']*\'|[^\s>]+)|\G(?:[\s/]+|(?<=["\']))[^\s=/>]++(?!\s*=)~', function ( $attr ) use ( $is_img, &$src ) { if ( empty( $attr[2] ) ) { return $attr[0]; } $name = strtolower( $attr[2] ); if ( 'style' !== $name && ( ! $is_img || 'src' !== $name || $src ) ) { return $attr[0]; } $value = $attr[4]; $quote = '"' === $value[0] || "'" === $value[0] ? $value[0] : ''; $inner = $quote ? substr( $value, 1, -1 ) : $value; if ( 'style' === $name ) { return $attr[1] . $attr[2] . $attr[3] . $quote . $this->_rewrite_css_urls( $inner ) . $quote; } // Only the first src counts, matching how browsers resolve duplicate attributes. $src = true; $url2 = $this->_rewrite_img_src( $inner ); return $url2 ? $attr[1] . $attr[2] . $attr[3] . $quote . $url2 . $quote : $attr[0]; }, $token['attrs'] ); return null === $attrs ? $token[0] : '<' . $token['tag'] . $attrs . '>'; } ); } /** * Rewrite the content of a complete template block, keeping its outer tags byte-exact. * * @since 7.9.2 * @access private * @param string $block Whole template block including its opening and closing tags. * @param string $close The block's closing tag. * @param int $depth Current nesting depth. * @return string */ private function _rewrite_template( $block, $close, $depth ) { if ( ! preg_match( '~\A<template' . Utility::HTML_ATTR_PATTERN . '>~i', $block, $open ) ) { return $block; } $inner_len = strlen( $block ) - strlen( $open[0] ) - strlen( $close ); if ( $inner_len < 0 ) { return $block; } $inner = $this->_rewrite_fragment( substr( $block, strlen( $open[0] ), $inner_len ), $depth + 1 ); return null === $inner ? $block : $open[0] . $inner . $close; } /** * Map one img src value, skipping data URIs and escaped or ambiguous values. * * @since 7.9.2 * @access private * @param string $url Attribute value without quotes. * @return string|false Mapped URL or false to keep the original. */ private function _rewrite_img_src( $url ) { // Backslashes mean an escaped JSON/JS context; rewriting would corrupt the escaping. if ( '' === $url || preg_match( '/[\s\\\\\'"<>]/', $url ) || false !== strpos( $url, 'data:image' ) ) { return false; } return $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ); } /** * Rewrite a CSS URL without deleting syntax from untrusted content. * * @param string $css Style element text or a style attribute value. * @return string CSS with only safe URL spans replaced. */ private function _rewrite_css_urls( $css ) { $result = preg_replace_callback( '~(\burl\(\s*([\'"]|"|'|�*34;|�*39;|�*22;|�*27;|))(.*?)(\2\s*\))~is', function ( $matches ) { $url = $matches[3]; // Reject ambiguous URLs rather than stripping quotes, entities, or real whitespace. if ( '' === $url || preg_match( '/[\s\x00-\x1f\x7f<>\'"\\\\`]/', html_entity_decode( $url, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) ) || 0 === stripos( $url, 'data:' ) ) { return $matches[0]; } $parsed_url = wp_parse_url( $url, PHP_URL_PATH ); if ( ! $parsed_url ) { return $matches[0]; } $postfix = '.' . pathinfo( $parsed_url, PATHINFO_EXTENSION ); if ( array_key_exists( $postfix, $this->_cfg_cdn_mapping ) ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_FILETYPE, $postfix ); } elseif ( in_array( substr( $postfix, 1 ), [ 'jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'avif' ], true ) ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ); } else { return $matches[0]; } return $url2 ? $matches[1] . $url2 . $matches[4] : $matches[0]; }, $css ); return null === $result ? $css : $result; } /** * Filter: wp_get_attachment_image_src. * * @since 1.2.3 * @since 1.7 Removed static from function. * @access public * * @param array{0:string,1:int,2:int} $img The URL of the attachment image src, the width, the height. * @return array{0:string,1:int,2:int} */ public function attach_img_src( $img ) { if ( $img ) { $url = $this->rewrite( $img[0], Base::CDN_MAPPING_INC_IMG ); if ( $url ) { $img[0] = $url; } } return $img; } /** * Try to rewrite one image URL with CDN. * * @since 1.7 * @access public * * @param string $url Original URL. * @return string URL after rewriting, or original if not applicable. */ public function url_img( $url ) { if ( $url ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ); if ( $url2 ) { $url = $url2; } } return $url; } /** * Try to rewrite one CSS URL with CDN. * * @since 1.7 * @access public * * @param string $url Original URL. * @return string URL after rewriting, or original if not applicable. */ public function url_css( $url ) { if ( $url ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_CSS ); if ( $url2 ) { $url = $url2; } } return $url; } /** * Try to rewrite one JS URL with CDN. * * @since 1.7 * @access public * * @param string $url Original URL. * @return string URL after rewriting, or original if not applicable. */ public function url_js( $url ) { if ( $url ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_JS ); if ( $url2 ) { $url = $url2; } } return $url; } /** * Filter responsive image sources for CDN. * * @since 1.2.3 * @since 1.7 Removed static from function. * @access public * * @param array<int,array{url:string}> $srcs Srcset array. * @return array<int,array{url:string}> */ public function srcset( $srcs ) { if ( $srcs ) { foreach ( $srcs as $w => $data ) { $url = $this->rewrite( $data['url'], Base::CDN_MAPPING_INC_IMG ); if ( ! $url ) { continue; } $srcs[ $w ]['url'] = $url; } } return $srcs; } /** * Replace an URL with mapped CDN URL, if applicable. * * @since 1.2.3 * @access public * * @param string $url Target URL. * @param string $mapping_kind Mapping kind (e.g., Base::CDN_MAPPING_INC_IMG or Base::CDN_MAPPING_FILETYPE). * @param string|false $postfix File extension (with dot) when mapping by file type. * @return string|false Replaced URL on success, false when not applicable. */ public function rewrite( $url, $mapping_kind, $postfix = false ) { self::debug2( 'rewrite ' . $url ); $url_parsed = wp_parse_url( $url ); if ( empty( $url_parsed['path'] ) ) { self::debug2( '-rewrite bypassed: no path' ); return false; } // Only images under wp-content/wp-includes can be replaced $is_internal_folder = Utility::str_hit_array( $url_parsed['path'], $this->_cfg_ori_dir ); if ( ! $is_internal_folder ) { self::debug2( '-rewrite failed: path not match: ' . LSCWP_CONTENT_FOLDER ); return false; } // Check if is external url if ( ! empty( $url_parsed['host'] ) ) { if ( ! Utility::internal( $url_parsed['host'] ) && ! $this->_is_ori_url( $url ) ) { self::debug2( '-rewrite failed: host not internal' ); return false; } } $exclude = Utility::str_hit_array( $url, $this->_cfg_cdn_exclude ); if ( $exclude ) { self::debug2( '-abort excludes ' . $exclude ); return false; } // Fill full url before replacement if ( empty( $url_parsed['host'] ) ) { $url = Utility::uri2url( $url ); self::debug2( '-fill before rewritten: ' . $url ); $url_parsed = wp_parse_url( $url ); } $scheme = ! empty( $url_parsed['scheme'] ) ? $url_parsed['scheme'] . ':' : ''; // Find the mapping url to be replaced to if ( empty( $this->_cfg_cdn_mapping[ $mapping_kind ] ) ) { return false; } if ( Base::CDN_MAPPING_FILETYPE !== $mapping_kind ) { $final_url = $this->_cfg_cdn_mapping[ $mapping_kind ]; } else { // select from file type $final_url = $this->_cfg_cdn_mapping[ $postfix ]; if ( ! $final_url ) { return false; } } // If filetype to url is one to many, need to random one if ( is_array( $final_url ) ) { $final_url = $final_url[ array_rand( $final_url ) ]; } // Now lets replace CDN url foreach ( $this->_cfg_url_ori as $v ) { if ( false !== strpos( $v, '*' ) ) { $url = preg_replace_callback( '#' . $scheme . $v . '#iU', static function () use ( $final_url ) { return $final_url; }, $url ); } else { $url = str_replace( $scheme . $v, $final_url, $url ); } } self::debug2( '-rewritten: ' . $url ); return $url; } /** * Check if the given URL matches any configured "original" URLs for CDN. * * @since 2.1 * @access private * * @param string $url URL to test. * @return bool True if URL is one of the originals. */ private function _is_ori_url( $url ) { $url_parsed = wp_parse_url( $url ); $scheme = ! empty( $url_parsed['scheme'] ) ? $url_parsed['scheme'] . ':' : ''; foreach ( $this->_cfg_url_ori as $v ) { $needle = $scheme . $v; if ( false !== strpos( $v, '*' ) ) { if ( preg_match( '#' . $needle . '#iU', $url ) ) { return true; } } elseif ( 0 === strpos( $url, $needle ) ) { return true; } } return false; } /** * Check if the host is one of the CDN mapping hosts. * * @since 1.2.3 * * @param string $host Hostname to check. * @return bool False when bypassed, otherwise true if internal CDN host. */ public static function internal( $host ) { if ( defined( self::BYPASS ) ) { return false; } $instance = self::cls(); return in_array( $host, $instance->cdn_mapping_hosts, true ); // todo: can add $this->_is_ori_url() check in future } }