oont-contents/plugins/wp-rocket/inc/Engine/CDN/CDN.php
2025-02-08 15:10:23 +01:00

455 lines
10 KiB
PHP

<?php
declare(strict_types=1);
namespace WP_Rocket\Engine\CDN;
use WP_Rocket\Admin\Options_Data;
/**
* CDN class
*
* @since 3.4
*/
class CDN {
/**
* WP Rocket Options instance
*
* @var Options_Data
*/
private $options;
/**
* Home URL host
*
* @var string
*/
private $home_host;
/**
* Constructor
*
* @param Options_Data $options WP Rocket Options instance.
*/
public function __construct( Options_Data $options ) {
$this->options = $options;
}
/**
* Search & Replace URLs with the CDN URLs in the provided content
*
* @since 3.4
*
* @param string $html HTML content.
* @return string
*/
public function rewrite( $html ) {
$relative_path_pattern = '';
$buffer = $html;
/**
* Filters the exclusion of CDN rewritting inside inline scripts
*
* @since 3.10.5
*
* @param bool $enable True to exclude, false otherwise.
*/
if ( apply_filters( 'rocket_cdn_exclude_inline_scripts', true ) ) {
$buffer = $this->remove_inline_scripts( $html );
}
/**
* Filters the CDN rewriting of relative paths
*
* @since 3.10.5
*
* @param bool $enable True to enable, false otherwise.
*/
if ( apply_filters( 'rocket_cdn_relative_paths', true ) ) {
$relative_path_pattern = '|\/[^/](?:[^"\')\s>]+\.[[:alnum:]]+)';
}
$pattern = '#[("\']\s*(?<url>(?:(?:https?:|)' . preg_quote( $this->get_base_url(), '#' ) . ')\/(?:(?:(?:' . $this->get_allowed_paths() . ')[^"\',)]+))' . $relative_path_pattern . ')\s*["\')]#i';
if ( ! preg_match_all( $pattern, $buffer, $matches, PREG_SET_ORDER ) ) {
return $html;
}
foreach ( $matches as $match ) {
$cdn_url = str_replace( $match['url'], $this->rewrite_url( $match['url'] ), $match[0] );
$html = str_replace( $match[0], $cdn_url, $html );
}
return $html;
}
/**
* Rewrites URLs in a srcset attribute using the CDN URL
*
* @since 3.4.0.4
*
* @param string $html HTML content.
* @return string
*/
public function rewrite_srcset( $html ) {
$pattern = '#\s+(?:' . $this->get_srcset_attributes() . ')?srcset\s*=\s*["\']\s*(?<sources>[^"\',\s]+\.[^"\',\s]+(?:\s+\d+[wx])?(?:\s*,\s*[^"\',\s]+\.[^"\',\s]+(?:\s+\d+[wx])?)*)\s*["\']#i';
if ( ! preg_match_all( $pattern, $html, $srcsets, PREG_SET_ORDER ) ) {
return $html;
}
foreach ( $srcsets as $srcset ) {
$sources = explode( ',', $srcset['sources'] );
$sources = array_unique( array_map( 'trim', $sources ) );
$cdn_srcset = $srcset['sources'];
foreach ( $sources as $source ) {
$url = preg_split( '#\s+#', trim( $source ) );
$cdn_source = str_replace( $url[0], $this->rewrite_url( $url[0] ), $source );
$cdn_srcset = str_replace( $source, $cdn_source, $cdn_srcset );
}
$cdn_srcsets = str_replace( $srcset['sources'], $cdn_srcset, $srcset[0] );
$html = str_replace( $srcset[0], $cdn_srcsets, $html );
}
return $html;
}
/**
* Rewrites an URL with the CDN URL
*
* @since 3.4
*
* @param string $url Original URL.
* @return string
*/
public function rewrite_url( $url ) {
if ( ! $this->options->get( 'cdn', 0 ) ) {
return $url;
}
if ( $this->is_excluded( $url ) ) {
return $url;
}
$cdn_urls = $this->get_cdn_urls( $this->get_zones_for_url( $url ) );
if ( ! $cdn_urls ) {
return $url;
}
$parsed_url = wp_parse_url( $url );
$cdn_url = untrailingslashit( $cdn_urls[ ( abs( crc32( $parsed_url['path'] ) ) % count( $cdn_urls ) ) ] );
if ( ! isset( $parsed_url['host'] ) ) {
return rocket_add_url_protocol( $cdn_url . '/' . ltrim( $url, '/' ) );
}
$home_host = $this->get_home_host();
if ( ! isset( $parsed_url['scheme'] ) ) {
return str_replace( $home_host, rocket_remove_url_protocol( $cdn_url ), $url );
}
$home_url = [
'http://' . $home_host,
'https://' . $home_host,
];
return str_replace( $home_url, rocket_add_url_protocol( $cdn_url ), $url );
}
/**
* Rewrites URLs to CDN URLs in CSS content
*
* @since 3.4
*
* @param string $content CSS content.
* @return string
*/
public function rewrite_css_properties( $content ) {
if ( ! preg_match_all( '#url\(\s*(\'|")?\s*(?![\'"]?data)(?<url>(?:https?:|)' . preg_quote( $this->get_base_url(), '#' ) . '\/[^"|\'|\)|\s]+)\s*#i', $content, $matches, PREG_SET_ORDER ) ) {
return $content;
}
foreach ( $matches as $property ) {
/**
* Filters the URL of the CSS property
*
* @since 2.8
*
* @param string $url URL of the CSS property.
*/
$cdn_url = $this->rewrite_url( apply_filters( 'rocket_cdn_css_properties_url', $property['url'] ) );
$replacement = str_replace( $property['url'], $cdn_url, $property[0] );
$content = str_replace( $property[0], $replacement, $content );
}
return $content;
}
/**
* Get all CDN URLs for one or more zones.
*
* @since 2.1
* @since 3.0 Don't check for WP Rocket CDN option activated to be able to use the function on Hosting with CDN auto-enabled.
*
* @param array $zones List of zones. Default is [ 'all' ].
* @return array
*/
public function get_cdn_urls( $zones = [ 'all' ] ) {
$hosts = [];
$zones = (array) $zones;
$cdn_urls = $this->options->get( 'cdn_cnames', [] );
if ( $cdn_urls ) {
$cdn_zones = $this->options->get( 'cdn_zone', [] );
foreach ( $cdn_urls as $k => $urls ) {
if ( ! in_array( $cdn_zones[ $k ], $zones, true ) ) {
continue;
}
$urls = explode( ',', $urls );
$urls = array_map( 'trim', $urls );
foreach ( $urls as $url ) {
$hosts[] = $url;
}
}
}
/**
* Filter all CDN URLs.
*
* @since 2.7
* @since 3.4 Added $zone parameter.
*
* @param array $hosts List of CDN URLs.
* @param array $zones List of zones. Default is [ 'all' ].
*/
$hosts = (array) apply_filters( 'rocket_cdn_cnames', $hosts, $zones );
$hosts = array_filter( $hosts );
$hosts = array_flip( array_flip( $hosts ) );
$hosts = array_values( $hosts );
return $hosts;
}
/**
* Gets the base URL for the website
*
* @since 3.4
*
* @return string
*/
private function get_base_url() {
return '//' . $this->get_home_host();
}
/**
* Gets the allowed paths as a regex pattern for the CDN rewrite
*
* @since 3.4
*
* @return string
*/
private function get_allowed_paths() {
$wp_content_dirname = ltrim( trailingslashit( wp_parse_url( content_url(), PHP_URL_PATH ) ), '/' );
$wp_includes_dirname = ltrim( trailingslashit( wp_parse_url( includes_url(), PHP_URL_PATH ) ), '/' );
$upload_dirname = '';
$uploads_info = wp_upload_dir();
if ( ! empty( $uploads_info['baseurl'] ) ) {
$upload_dirname = '|' . ltrim( trailingslashit( wp_parse_url( $uploads_info['baseurl'], PHP_URL_PATH ) ), '/' );
}
return $wp_content_dirname . $upload_dirname . '|' . $wp_includes_dirname;
}
/**
* Checks if the provided URL can be rewritten with the CDN URL
*
* @since 3.4
*
* @param string $url URL to check.
* @return boolean
*/
public function is_excluded( $url ) {
$path = wp_parse_url( $url, PHP_URL_PATH );
$excluded_extensions = [
'php',
'html',
'htm',
'cfm',
];
if ( in_array( pathinfo( $path, PATHINFO_EXTENSION ), $excluded_extensions, true ) ) {
return true;
}
if ( ! $path ) {
return true;
}
if ( '/' === $path ) {
return true;
}
if ( preg_match( '#^(' . $this->get_excluded_files( '#' ) . ')$#', $path ) ) {
return true;
}
return false;
}
/**
* Gets the home URL host
*
* @since 3.5.5
*
* @return string
*/
private function get_home_host() {
if ( empty( $this->home_host ) ) {
$this->home_host = wp_parse_url( home_url(), PHP_URL_HOST );
}
return $this->home_host;
}
/**
* Gets the CDN zones for the provided URL
*
* @since 3.4
*
* @param string $url URL to check.
* @return array
*/
private function get_zones_for_url( $url ) {
$zones = [ 'all' ];
$ext = pathinfo( wp_parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION );
$image_types = [
'jpg',
'jpeg',
'jpe',
'png',
'gif',
'webp',
'bmp',
'tiff',
'svg',
];
if ( 'css' === $ext || 'js' === $ext ) {
$zones[] = 'css_and_js';
}
if ( 'css' === $ext ) {
$zones[] = 'css';
}
if ( 'js' === $ext ) {
$zones[] = 'js';
}
if ( in_array( $ext, $image_types, true ) ) {
$zones[] = 'images';
}
return $zones;
}
/**
* Get all files we don't allow to get in CDN.
*
* @since 2.5
*
* @param string $delimiter RegEx delimiter.
* @return string A pipe-separated list of excluded files.
*/
private function get_excluded_files( $delimiter ) {
$files = $this->options->get( 'cdn_reject_files', [] );
/**
* Filter the excluded files.
*
* @since 2.5
*
* @param array $files List of excluded files.
*/
$files = (array) apply_filters( 'rocket_cdn_reject_files', $files );
$files = array_filter( $files );
if ( ! $files ) {
return '';
}
$files = array_flip( array_flip( $files ) );
$files = array_map(
function ( $file ) use ( $delimiter ) {
return str_replace( $delimiter, '\\' . $delimiter, $file );
},
$files
);
return implode( '|', $files );
}
/**
* Get srcset attributes to rewrite to the CDN.
*
* @since 3.8.7
*
* @return string A pipe-separated list of srcset attributes.
*/
private function get_srcset_attributes() {
/**
* Filter the srcset attributes.
*
* @since 3.8.7
*
* @param array $srcset_attributes List of srcset attributes.
*/
$srcset_attributes = (array) apply_filters(
'rocket_cdn_srcset_attributes',
[
'data-lazy-',
'data-',
]
);
return implode( '|', $srcset_attributes );
}
/**
* Removes inline scripts from the HTML
*
* @since 3.10.5
*
* @param string $html HTML content.
*
* @return string
*/
private function remove_inline_scripts( $html ): string {
if ( ! preg_match_all( '#<script(?:[^>]*)>(?<content>[\s\S]*?)</script>#msi', $html, $matches, PREG_SET_ORDER ) ) {
return $html;
}
if ( empty( $matches ) ) {
return $html;
}
foreach ( $matches as $inline_js ) {
if ( empty( $inline_js['content'] ) ) {
continue;
}
$html = str_replace( $inline_js[0], '', $html );
}
return $html;
}
}