add wp-rocket
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
|
||||
/**
|
||||
* Base abstract class for files optimization
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*/
|
||||
abstract class AbstractOptimization {
|
||||
|
||||
/**
|
||||
* Plugin options.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Minify key.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $minify_key;
|
||||
|
||||
/**
|
||||
* Concatenated list of excluded files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $excluded_files;
|
||||
|
||||
/**
|
||||
* Minify base path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $minify_base_path;
|
||||
|
||||
/**
|
||||
* Minify base URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $minify_base_url;
|
||||
|
||||
/**
|
||||
* Initializes the minify base path and URL.
|
||||
*/
|
||||
protected function init_base_path_and_url() {
|
||||
$site_id = get_current_blog_id() . '/';
|
||||
$this->minify_base_path = rocket_get_constant( 'WP_ROCKET_MINIFY_CACHE_PATH' ) . $site_id;
|
||||
$this->minify_base_url = rocket_get_constant( 'WP_ROCKET_MINIFY_CACHE_URL' ) . $site_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds nodes matching the pattern in the HTML.
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $pattern Pattern to match.
|
||||
* @param string $html HTML content.
|
||||
* @return bool|array
|
||||
*/
|
||||
protected function find( $pattern, $html ) {
|
||||
preg_match_all( '/' . $pattern . '/Umsi', $html, $matches, PREG_SET_ORDER );
|
||||
|
||||
if ( empty( $matches ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the file is external.
|
||||
*
|
||||
* @since 2.11
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $url URL of the file.
|
||||
* @return bool True if external, false otherwise
|
||||
*/
|
||||
protected function is_external_file( $url ) {
|
||||
$file = get_rocket_parse_url( $url );
|
||||
|
||||
if ( empty( $file['path'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$wp_content = wp_parse_url( content_url() );
|
||||
|
||||
if ( empty( $wp_content['host'] ) || empty( $wp_content['path'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the allowed hosts for optimization
|
||||
*
|
||||
* @since 3.4
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param array $hosts Allowed hosts.
|
||||
* @param array $zones Zones to check available hosts.
|
||||
*/
|
||||
$hosts = (array) apply_filters( 'rocket_cdn_hosts', [], $this->get_zones() );
|
||||
$hosts[] = $wp_content['host'];
|
||||
$langs = get_rocket_i18n_uri();
|
||||
|
||||
// Get host for all langs.
|
||||
foreach ( $langs as $lang ) {
|
||||
$url_host = wp_parse_url( $lang, PHP_URL_HOST );
|
||||
|
||||
if ( ! isset( $url_host ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hosts[] = $url_host;
|
||||
}
|
||||
|
||||
$hosts = array_unique( $hosts );
|
||||
|
||||
if ( empty( $hosts ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// URL has domain and domain is part of the internal domains.
|
||||
if ( ! empty( $file['host'] ) ) {
|
||||
foreach ( $hosts as $host ) {
|
||||
if ( false !== strpos( $url, $host ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// URL has no domain and doesn't contain the WP_CONTENT path or wp-includes.
|
||||
return ! preg_match( '#(' . $wp_content['path'] . '|wp-includes)#', $file['path'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the content to a file
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $content Content to write.
|
||||
* @param string $file Path to the file to write in.
|
||||
* @return bool
|
||||
*/
|
||||
protected function write_file( $content, $file ) {
|
||||
if ( rocket_direct_filesystem()->is_readable( $file ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! rocket_mkdir_p( dirname( $file ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( function_exists( 'gzencode' ) ) {
|
||||
// This filter is documented in inc/classes/Buffer/class-cache.php.
|
||||
$gzip_content = gzencode( $content, apply_filters( 'rocket_gzencode_level_compression', 6 ) );
|
||||
|
||||
if ( $gzip_content ) {
|
||||
rocket_put_content( $file . '.gz', $gzip_content );
|
||||
}
|
||||
}
|
||||
|
||||
return rocket_put_content( $file, $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path from an URL
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $url File URL.
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function get_file_path( $url ) {
|
||||
return rocket_url_to_path( strtok( $url, '?' ), $this->get_zones() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets content of a file
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $file File path.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_file_content( $file ) {
|
||||
return rocket_direct_filesystem()->get_contents( $file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides unwanted blocks from the HTML to be parsed for optimization
|
||||
*
|
||||
* @since 3.1.4
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
protected function hide_comments( $html ) {
|
||||
$html = preg_replace( '#<!--\s*noptimize\s*-->.*?<!--\s*/\s*noptimize\s*-->#is', '', $html );
|
||||
$html = preg_replace( '/<!--(.*)-->/Uis', '', $html );
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization;
|
||||
|
||||
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
|
||||
|
||||
/**
|
||||
* Service provider for the WP Rocket optimizations
|
||||
*
|
||||
* @since 3.5.3
|
||||
*/
|
||||
class AdminServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The provides array is a way to let the container
|
||||
* know that a service is provided by this service
|
||||
* provider. Every service that is registered via
|
||||
* this service provider must have an alias added
|
||||
* to this array or it will be ignored.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = [
|
||||
'minify_css_admin_subscriber',
|
||||
'google_fonts_settings',
|
||||
'google_fonts_admin_subscriber',
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the option array in the container
|
||||
*
|
||||
* @since 3.5.3
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$this->getContainer()->share( 'minify_css_admin_subscriber', 'WP_Rocket\Engine\Optimization\Minify\CSS\AdminSubscriber' );
|
||||
$this->getContainer()->add( 'google_fonts_settings', 'WP_Rocket\Engine\Optimization\GoogleFonts\Admin\Settings' )
|
||||
->withArgument( $this->getContainer()->get( 'options' ) )
|
||||
->withArgument( $this->getContainer()->get( 'beacon' ) )
|
||||
->withArgument( $this->getContainer()->get( 'template_path' ) );
|
||||
$this->getContainer()->share( 'google_fonts_admin_subscriber', 'WP_Rocket\Engine\Optimization\GoogleFonts\Admin\Subscriber' )
|
||||
->withArgument( $this->getContainer()->get( 'google_fonts_settings' ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization;
|
||||
|
||||
use WP_Filesystem_Direct;
|
||||
|
||||
/**
|
||||
* Cache locally 3rd party assets.
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class AssetsLocalCache {
|
||||
/**
|
||||
* 3rd party assets cache folder path
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_path;
|
||||
|
||||
/**
|
||||
* Filesystem instance.
|
||||
*
|
||||
* @var WP_Filesystem_Direct
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* Cache contents with url.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $url_cache = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $cache_path 3rd party assets cache folder path.
|
||||
* @param WP_Filesystem_Direct $filesystem Filesysten instance.
|
||||
*/
|
||||
public function __construct( $cache_path, $filesystem ) {
|
||||
$this->cache_path = "{$cache_path}3rd-party/";
|
||||
$this->filesystem = $filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote file contents.
|
||||
*
|
||||
* @param string $url Url of the file to get contents for.
|
||||
*
|
||||
* @return string Raw file contents.
|
||||
*/
|
||||
private function get_raw_content( $url ) {
|
||||
$cache_key = md5( $url );
|
||||
|
||||
if ( ! isset( $this->url_cache[ $cache_key ] ) ) {
|
||||
$this->url_cache[ $cache_key ] = wp_remote_retrieve_body( wp_remote_get( $url ) );
|
||||
}
|
||||
|
||||
return $this->url_cache[ $cache_key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets content for the provided URL.
|
||||
* Use the local cache file if it exists, else get it from the 3rd party URL and save it locally for future use.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $url URL to get the content from.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content( $url ) {
|
||||
$filepath = $this->get_filepath( $url );
|
||||
|
||||
if ( empty( $filepath ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( $this->filesystem->is_readable( $filepath ) ) {
|
||||
return $this->filesystem->get_contents( $filepath );
|
||||
}
|
||||
|
||||
$content = $this->get_raw_content( $url );
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->write_file( $content, $filepath );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filepath of the local copy for the given URL
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $url URL to get filepath for.
|
||||
* @return string
|
||||
*/
|
||||
public function get_filepath( $url ) {
|
||||
$parts = wp_parse_url( $url );
|
||||
|
||||
if ( empty( $parts['path'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$filename = $parts['host'] . str_replace( '/', '-', $parts['path'] );
|
||||
|
||||
return $this->cache_path . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the content to a file
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $content Content to write.
|
||||
* @param string $file Path to the file to write in.
|
||||
* @return bool
|
||||
*/
|
||||
protected function write_file( $content, $file ) {
|
||||
if ( $this->filesystem->is_readable( $file ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! rocket_mkdir_p( dirname( $file ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rocket_put_content( $file, $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this link HTML has integrity attribute or not?
|
||||
*
|
||||
* @since 3.7.5
|
||||
*
|
||||
* @param string $asset Link HTML to be tested.
|
||||
*
|
||||
* @return array|false Matched array with integrityhashmethod, integrityhash keys.
|
||||
*/
|
||||
private function has_integrity( $asset ) {
|
||||
if ( ! preg_match( '#\s*integrity\s*=[\'"](?<integrityhashmethod>.*)-(?<integrityhash>.*)[\'"]#Ui', $asset, $integrity_matches ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $integrity_matches['integrityhashmethod'], $integrity_matches['integrityhash'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $integrity_matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the integrity attribute if the content matches with the hashed integrity value.
|
||||
*
|
||||
* @param array $asset_matched the matched array which has 0, url keys.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate_integrity( $asset_matched ) {
|
||||
$integrity_matches = $this->has_integrity( $asset_matched[0] );
|
||||
|
||||
if ( false === $integrity_matches ) {
|
||||
return $asset_matched[0];
|
||||
}
|
||||
|
||||
// validate the hash algorithm.
|
||||
if ( ! in_array( $integrity_matches['integrityhashmethod'], hash_algos(), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = $this->get_raw_content( $asset_matched['url'] );
|
||||
$content_hash = base64_encode( hash( $integrity_matches['integrityhashmethod'], $content, true ) );// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
|
||||
if ( $integrity_matches['integrityhash'] !== $content_hash ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return str_replace( $integrity_matches[0], '', $asset_matched[0] );
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization;
|
||||
|
||||
use WP_Rocket\Dependencies\PathConverter\ConverterInterface;
|
||||
use WP_Rocket\Dependencies\PathConverter\Converter;
|
||||
|
||||
trait CSSTrait {
|
||||
/**
|
||||
* Rewrites the paths inside the CSS file content
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $source Source filepath.
|
||||
* @param string $target Target filepath.
|
||||
* @param string $content File content.
|
||||
* @return string
|
||||
*/
|
||||
public function rewrite_paths( $source, $target, $content ) {
|
||||
/**
|
||||
* Filters the source path for an asset inside a CSS file
|
||||
*
|
||||
* @since 3.3.1
|
||||
*
|
||||
* @param string $source Source filepath.
|
||||
*/
|
||||
$source = apply_filters( 'rocket_css_asset_source_path', $source );
|
||||
|
||||
/**
|
||||
* Filters the target path for an asset inside a CSS file
|
||||
*
|
||||
* @since 3.3.1
|
||||
*
|
||||
* @param string $target Target filepath.
|
||||
*/
|
||||
$target = apply_filters( 'rocket_css_asset_target_path', $target );
|
||||
|
||||
/**
|
||||
* Filters the content of a CSS file
|
||||
*
|
||||
* @since 3.4
|
||||
*
|
||||
* @param string $content CSS content.
|
||||
* @param string $source Source filepath.
|
||||
* @param string $target Target filepath.
|
||||
*/
|
||||
return apply_filters( 'rocket_css_content', $this->move( $this->get_converter( $source, $target ), $content, $source ), $source, $target );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the Converter class
|
||||
*
|
||||
* @param string $source Source filepath.
|
||||
* @param string $target Destination filepath.
|
||||
* @return Converter
|
||||
*/
|
||||
protected function get_converter( $source, $target ) {
|
||||
return new Converter( $source, $target );
|
||||
}
|
||||
|
||||
/**
|
||||
* Moving a css file should update all relative urls.
|
||||
* Relative references (e.g. ../images/image.gif) in a certain css file,
|
||||
* will have to be updated when a file is being saved at another location
|
||||
* (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
|
||||
*
|
||||
* Method copied from WP_Rocket\Dependencies\Minify\CSS;
|
||||
*
|
||||
* @param ConverterInterface $converter Relative path converter.
|
||||
* @param string $content The CSS content to update relative urls for.
|
||||
* @param string $source The source path or URL for the CSS file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function move( ConverterInterface $converter, $content, $source ) {
|
||||
/*
|
||||
* Relative path references will usually be enclosed by url(). @import
|
||||
* is an exception, where url() is not necessary around the path (but is
|
||||
* allowed).
|
||||
* This *could* be 1 regular expression, where both regular expressions
|
||||
* in this array are on different sides of a |. But we're using named
|
||||
* patterns in both regexes, the same name on both regexes. This is only
|
||||
* possible with a (?J) modifier, but that only works after a fairly
|
||||
* recent PCRE version. That's why I'm doing 2 separate regular
|
||||
* expressions & combining the matches after executing of both.
|
||||
*/
|
||||
$relative_regexes = [
|
||||
// url(xxx).
|
||||
'/
|
||||
# open url()
|
||||
url\(
|
||||
|
||||
\s*
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])?
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure
|
||||
(?(quotes)(?P=quotes))
|
||||
|
||||
\s*
|
||||
|
||||
# close url()
|
||||
\)
|
||||
|
||||
/ix',
|
||||
|
||||
// @import "xxx"
|
||||
'/
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# we don\'t have to check for @import url(), because the
|
||||
# condition above will already catch these
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
/ix',
|
||||
];
|
||||
|
||||
// find all relative urls in css.
|
||||
$matches = [];
|
||||
foreach ( $relative_regexes as $relative_regex ) {
|
||||
if ( preg_match_all( $relative_regex, $content, $regex_matches, PREG_SET_ORDER ) ) {
|
||||
$matches = array_merge( $matches, $regex_matches );
|
||||
}
|
||||
}
|
||||
|
||||
$search = [];
|
||||
$replace = [];
|
||||
|
||||
// loop all urls.
|
||||
foreach ( $matches as $match ) {
|
||||
// determine if it's a url() or an @import match.
|
||||
$type = ( strpos( $match[0], '@import' ) === 0 ? 'import' : 'url' );
|
||||
|
||||
$url = $match['path'];
|
||||
if ( ! preg_match( '/^(data:|https?:|\\/)/', $url ) ) {
|
||||
// attempting to interpret GET-params makes no sense, so let's discard them for awhile.
|
||||
$params = strrchr( $url, '?' );
|
||||
$url = $params ? substr( $url, 0, -strlen( $params ) ) : $url;
|
||||
|
||||
// fix relative url.
|
||||
$url = filter_var( $source, FILTER_VALIDATE_URL ) ? dirname( $source ) . '/' . ltrim( $url, '/' ) : $converter->convert( $url );
|
||||
|
||||
// now that the path has been converted, re-apply GET-params.
|
||||
$url .= $params;
|
||||
}
|
||||
|
||||
/*
|
||||
* Urls with control characters above 0x7e should be quoted.
|
||||
* According to Mozilla's parser, whitespace is only allowed at the
|
||||
* end of unquoted urls.
|
||||
* Urls with `)` (as could happen with data: uris) should also be
|
||||
* quoted to avoid being confused for the url() closing parentheses.
|
||||
* And urls with a # have also been reported to cause issues.
|
||||
* Urls with quotes inside should also remain escaped.
|
||||
*
|
||||
* @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
|
||||
* @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
|
||||
* @see https://github.com/matthiasmullie/minify/issues/193
|
||||
*/
|
||||
$url = trim( $url );
|
||||
if ( preg_match( '/[\s\)\'"#\x{7f}-\x{9f}]/u', $url ) ) {
|
||||
$url = $match['quotes'] . $url . $match['quotes'];
|
||||
}
|
||||
|
||||
// build replacement.
|
||||
$search[] = $match[0];
|
||||
if ( 'url' === $type ) {
|
||||
$replace[] = 'url(' . $url . ')';
|
||||
} elseif ( 'import' === $type ) {
|
||||
$replace[] = '@import "' . $url . '"';
|
||||
}
|
||||
}
|
||||
|
||||
// replace urls.
|
||||
return str_replace( $search, $replace, $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies font-display:swap to all font-family rules without a previously set font-display property.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $css_file_content CSS file content to modify.
|
||||
*
|
||||
* @return string Modified CSS content.
|
||||
*/
|
||||
private function apply_font_display_swap( $css_file_content ) {
|
||||
$css_file_content = (string) $css_file_content;
|
||||
|
||||
return preg_replace_callback(
|
||||
'/(?:@font-face)\s*{(?<value>[^}]+)}/',
|
||||
function ( $matches ) {
|
||||
if ( false !== strpos( $matches['value'], 'font-display' ) ) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$swap = "font-display:swap;{$matches['value']}";
|
||||
|
||||
return str_replace( $matches['value'], $swap, $matches[0] );
|
||||
},
|
||||
$css_file_content
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
/**
|
||||
* Create a static file for CSS/JS generated by a PHP file
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class CacheDynamicResource extends AbstractOptimization implements Subscriber_Interface {
|
||||
use CSSTrait;
|
||||
|
||||
/**
|
||||
* Plugin options instance.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Cache busting base path
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $busting_path;
|
||||
|
||||
/**
|
||||
* Cache busting base URL
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $busting_url;
|
||||
|
||||
/**
|
||||
* Excluded files from optimization
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $excluded_files;
|
||||
|
||||
/**
|
||||
* Extension to use for the CDN zones selection
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $extension = '';
|
||||
|
||||
/**
|
||||
* Creates an instance of CacheDynamicResource.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param Options_Data $options Plugin options instance.
|
||||
* @param string $busting_path Base cache busting files path.
|
||||
* @param string $busting_url Base cache busting files URL.
|
||||
*/
|
||||
public function __construct( Options_Data $options, $busting_path, $busting_url ) {
|
||||
$site_id = get_current_blog_id();
|
||||
$this->options = $options;
|
||||
$this->busting_path = "{$busting_path}{$site_id}/";
|
||||
$this->busting_url = "{$busting_url}{$site_id}/";
|
||||
|
||||
/**
|
||||
* Filters files to exclude from static dynamic resources
|
||||
*
|
||||
* @since 2.9.3
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param array $excluded_files An array of filepath to exclude.
|
||||
*/
|
||||
$this->excluded_files = (array) apply_filters( 'rocket_exclude_static_dynamic_resources', [] );
|
||||
$this->excluded_files[] = '/wp-admin/admin-ajax.php';
|
||||
|
||||
foreach ( $this->excluded_files as $i => $excluded_file ) {
|
||||
// Escape character for future use in regex pattern.
|
||||
$this->excluded_files[ $i ] = str_replace( '#', '\#', $excluded_file );
|
||||
}
|
||||
|
||||
$this->excluded_files = implode( '|', $this->excluded_files );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
return [
|
||||
'style_loader_src' => [ 'cache_dynamic_resource', 16 ],
|
||||
'script_loader_src' => [ 'cache_dynamic_resource', 16 ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the source dynamic php file to replace it with a static file
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $src source URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function cache_dynamic_resource( $src ) {
|
||||
if ( ! $this->is_allowed() ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
switch ( current_filter() ) {
|
||||
case 'script_loader_src':
|
||||
$this->set_extension( 'js' );
|
||||
break;
|
||||
case 'style_loader_src':
|
||||
$this->set_extension( 'css' );
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $this->is_excluded_file( $src ) ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
return $this->replace_url( $src );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the dynamic URL by the static file URL
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $src Source URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function replace_url( $src ) {
|
||||
$path = ltrim( rocket_extract_url_component( $src, PHP_URL_PATH ), '/' );
|
||||
|
||||
/**
|
||||
* Filters the dynamic resource cache filename
|
||||
*
|
||||
* @since 2.9
|
||||
*
|
||||
* @param string $filename filename for the cache file
|
||||
*/
|
||||
$filename = apply_filters( 'rocket_dynamic_resource_cache_filename', preg_replace( '/\.php$/', '-' . $this->minify_key . '.' . $this->extension, $path ) );
|
||||
$filename = ltrim( rocket_realpath( rtrim( str_replace( [ ' ', '%20' ], '-', $filename ) ) ), '/' );
|
||||
$filepath = $this->busting_path . $filename;
|
||||
|
||||
if ( ! rocket_direct_filesystem()->is_readable( $filepath ) ) {
|
||||
$content = $this->get_url_content( $src );
|
||||
|
||||
if ( ! $content ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
if ( 'css' === $this->extension ) {
|
||||
$content = $this->rewrite_paths( $this->get_file_path( $src ), $filepath, $content );
|
||||
$content = $this->apply_font_display_swap( $content );
|
||||
}
|
||||
|
||||
if ( ! $this->write_file( $content, $filepath ) ) {
|
||||
return $src;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->get_cache_url( $filename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if we can optimize
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_allowed() {
|
||||
global $pagenow;
|
||||
|
||||
if ( rocket_bypass() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_user_logged_in() && ! $this->options->get( 'cache_logged_user' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'wp-login.php' === $pagenow ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the file is excluded from optimization
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $src source URL.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_excluded_file( $src ) {
|
||||
$file = get_rocket_parse_url( $src );
|
||||
|
||||
if ( isset( $file['path'] ) && ! preg_match( '#\.php$#', $file['path'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $this->is_external_file( $src ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( preg_match( '#^' . $this->excluded_files . '$#', $file['path'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! isset( $file['query'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file['query'] = remove_query_arg( 'ver', $file['query'] );
|
||||
|
||||
return (bool) $file['query'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current file extension and minify key
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $extension Current file extension.
|
||||
*/
|
||||
public function set_extension( $extension ) {
|
||||
$this->extension = $extension;
|
||||
$this->minify_key = $this->options->get( 'minify_' . $this->extension . '_key' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CDN zones.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_zones() {
|
||||
return [ 'all', 'css_and_js', $this->extension ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache URL for the static file
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $filename Filename for the static file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_cache_url( $filename ) {
|
||||
$cache_url = $this->busting_url . $filename;
|
||||
|
||||
switch ( $this->extension ) {
|
||||
case 'css':
|
||||
// This filter is documented in inc/classes/optimization/css/class-abstract-css-optimization.php.
|
||||
$cache_url = apply_filters( 'rocket_css_url', $cache_url );
|
||||
break;
|
||||
case 'js':
|
||||
// This filter is documented in inc/classes/optimization/css/class-abstract-js-optimization.php.
|
||||
$cache_url = apply_filters( 'rocket_js_url', $cache_url );
|
||||
break;
|
||||
}
|
||||
|
||||
return $cache_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets content from an URL
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $url URL to get the content from.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function get_url_content( $url ) {
|
||||
$content = wp_remote_retrieve_body( wp_remote_get( $url ) );
|
||||
|
||||
if ( ! $content ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\DelayJS\Admin;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
|
||||
class Settings {
|
||||
/**
|
||||
* Array of defaults scripts to delay
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $defaults = [
|
||||
'getbutton.io',
|
||||
'//a.omappapi.com/app/js/api.min.js',
|
||||
'feedbackcompany.com/includes/widgets/feedback-company-widget.min.js',
|
||||
'snap.licdn.com/li.lms-analytics/insight.min.js',
|
||||
'static.ads-twitter.com/uwt.js',
|
||||
'platform.twitter.com/widgets.js',
|
||||
'twq(',
|
||||
'/sdk.js#xfbml',
|
||||
'static.leadpages.net/leadbars/current/embed.js',
|
||||
'translate.google.com/translate_a/element.js',
|
||||
'widget.manychat.com',
|
||||
'xfbml.customerchat.js',
|
||||
'static.hotjar.com/c/hotjar-',
|
||||
'smartsuppchat.com/loader.js',
|
||||
'grecaptcha.execute',
|
||||
'Tawk_API',
|
||||
'shareaholic',
|
||||
'sharethis',
|
||||
'simple-share-buttons-adder',
|
||||
'addtoany',
|
||||
'font-awesome',
|
||||
'wpdiscuz',
|
||||
'cookie-law-info',
|
||||
'pinit.js',
|
||||
'/gtag/js',
|
||||
'gtag(',
|
||||
'/gtm.js',
|
||||
'/gtm-',
|
||||
'fbevents.js',
|
||||
'fbq(',
|
||||
'google-analytics.com/analytics.js',
|
||||
'ga( \'',
|
||||
'ga(\'',
|
||||
'adsbygoogle.js',
|
||||
'ShopifyBuy',
|
||||
'widget.trustpilot.com/bootstrap',
|
||||
'ft.sdk.min.js',
|
||||
'apps.elfsight.com/p/platform.js',
|
||||
'livechatinc.com/tracking.js',
|
||||
'LiveChatWidget',
|
||||
'/busting/facebook-tracking/',
|
||||
'olark',
|
||||
'pixel-caffeine/build/frontend.js',
|
||||
];
|
||||
|
||||
/**
|
||||
* Instance of options handler.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Creates an instance of the class.
|
||||
*
|
||||
* @param Options_Data $options WP Rocket Options instance.
|
||||
*/
|
||||
public function __construct( Options_Data $options ) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the delay JS options to the WP Rocket options array
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param array $options WP Rocket options array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_options( $options ) {
|
||||
$options = (array) $options;
|
||||
|
||||
$options['delay_js'] = 1;
|
||||
$options['delay_js_scripts'] = $this->defaults;
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data to populate the view for the restore defaults button
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_button_data() {
|
||||
return [
|
||||
'type' => 'button',
|
||||
'action' => 'rocket_delay_js_restore_defaults',
|
||||
'attributes' => [
|
||||
'label' => __( 'Restore Defaults', 'rocket' ),
|
||||
'attributes' => [
|
||||
'class' => 'wpr-button wpr-button--icon wpr-button--purple wpr-icon-refresh',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delay_js option to zero when updating to 3.7
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $old_version Previous plugin version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_option_on_update( $old_version ) {
|
||||
if ( version_compare( $old_version, '3.7', '>' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$options = get_option( 'wp_rocket_settings', [] );
|
||||
|
||||
$options['delay_js'] = 0;
|
||||
$options['delay_js_scripts'] = $this->defaults;
|
||||
|
||||
update_option( 'wp_rocket_settings', $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update delay_js options when updating to ver 3.7.4
|
||||
*
|
||||
* @since 3.7.4
|
||||
*
|
||||
* @param string $old_version Old plugin version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function option_update_3_7_4( $old_version ) {
|
||||
if ( version_compare( $old_version, '3.7.4', '>' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$options = get_option( 'wp_rocket_settings', [] );
|
||||
$delay_js_scripts = array_flip( $options['delay_js_scripts'] );
|
||||
|
||||
if ( isset( $delay_js_scripts['adsbygoogle'] ) ) {
|
||||
$delay_js_scripts['adsbygoogle.js'] = $delay_js_scripts['adsbygoogle'];
|
||||
|
||||
unset( $delay_js_scripts['adsbygoogle'] );
|
||||
}
|
||||
|
||||
$options['delay_js_scripts'] = array_values( array_flip( $delay_js_scripts ) );
|
||||
|
||||
update_option( 'wp_rocket_settings', $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update delay_js options when updating to ver 3.7.2.
|
||||
*
|
||||
* @since 3.7.2
|
||||
*
|
||||
* @param string $old_version Old plugin version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function option_update_3_7_2( $old_version ) {
|
||||
if ( version_compare( $old_version, '3.7.2', '>' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$options = get_option( 'wp_rocket_settings', [] );
|
||||
|
||||
$delay_js_scripts = array_flip( $options['delay_js_scripts'] );
|
||||
|
||||
if (
|
||||
isset( $delay_js_scripts['fbq('] )
|
||||
&&
|
||||
! isset( $delay_js_scripts['pixel-caffeine/build/frontend.js'] )
|
||||
) {
|
||||
$delay_js_scripts['pixel-caffeine/build/frontend.js'] = '';
|
||||
}
|
||||
|
||||
if ( isset( $delay_js_scripts['google.com/recaptcha/api.js'] ) ) {
|
||||
unset( $delay_js_scripts['google.com/recaptcha/api.js'] );
|
||||
}
|
||||
|
||||
if ( isset( $delay_js_scripts['widget.trustpilot.com'] ) ) {
|
||||
$delay_js_scripts['widget.trustpilot.com/bootstrap'] = $delay_js_scripts['widget.trustpilot.com'];
|
||||
|
||||
unset( $delay_js_scripts['widget.trustpilot.com'] );
|
||||
}
|
||||
|
||||
$options['delay_js_scripts'] = array_values( array_flip( $delay_js_scripts ) );
|
||||
|
||||
update_option( 'wp_rocket_settings', $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the delay_js_scripts option to the default value
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function restore_defaults() {
|
||||
if ( ! current_user_can( 'rocket_manage_options' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return implode( "\n", $this->defaults );
|
||||
}
|
||||
}
|
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\DelayJS\Admin;
|
||||
|
||||
use WP_Rocket\Abstract_Render;
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
class Subscriber extends Abstract_Render implements Subscriber_Interface {
|
||||
/**
|
||||
* Settings instance
|
||||
*
|
||||
* @var Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* Instantiate the class
|
||||
*
|
||||
* @param Settings $settings Settings instance.
|
||||
* @param string $template_path Template path.
|
||||
*/
|
||||
public function __construct( Settings $settings, $template_path ) {
|
||||
parent::__construct( $template_path );
|
||||
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of events that this subscriber listens to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
return [
|
||||
'rocket_first_install_options' => 'add_options',
|
||||
'rocket_after_textarea_field_delay_js_scripts' => 'display_restore_defaults_button',
|
||||
'wp_rocket_upgrade' => [
|
||||
[ 'set_option_on_update', 13, 2 ],
|
||||
[ 'option_update_3_7_2', 13, 2 ],
|
||||
[ 'option_update_3_7_4', 13, 2 ],
|
||||
],
|
||||
'wp_ajax_rocket_restore_delay_js_defaults' => 'restore_defaults',
|
||||
'rocket_safe_mode_reset_options' => 'add_options',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the delay JS options to the WP Rocket options array
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param array $options WP Rocket options array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_options( $options ) {
|
||||
return $this->settings->add_options( $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the restore defaults button under the textarea field
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_restore_defaults_button() {
|
||||
$data = $this->settings->get_button_data();
|
||||
|
||||
$this->render_action_button(
|
||||
$data['type'],
|
||||
$data['action'],
|
||||
$data['attributes']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delay_js option to zero when updating to 3.7
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $new_version New plugin version.
|
||||
* @param string $old_version Previous plugin version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_option_on_update( $new_version, $old_version ) {
|
||||
$this->settings->set_option_on_update( $old_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the delay_js options when updating to 3.7.2.
|
||||
*
|
||||
* @since 3.7.2
|
||||
*
|
||||
* @param string $new_version New plugin version.
|
||||
* @param string $old_version Old plugin version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function option_update_3_7_2( $new_version, $old_version ) {
|
||||
$this->settings->option_update_3_7_2( $old_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the delay_js options when updating to 3.7.4
|
||||
*
|
||||
* @since 3.7.4
|
||||
*
|
||||
* @param string $new_version New plugin version.
|
||||
* @param string $old_version Old plugin version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function option_update_3_7_4( $new_version, $old_version ) {
|
||||
$this->settings->option_update_3_7_4( $old_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback to restore the default value for the delay JS scripts
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function restore_defaults() {
|
||||
check_ajax_referer( 'rocket-ajax', 'nonce', true );
|
||||
|
||||
$result = $this->settings->restore_defaults();
|
||||
|
||||
if ( false === $result ) {
|
||||
wp_send_json_error();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
wp_send_json_success( $result );
|
||||
}
|
||||
}
|
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\DelayJS;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
|
||||
class HTML {
|
||||
|
||||
/**
|
||||
* Plugin options instance.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Allowed scripts regex.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $allowed_scripts = '';
|
||||
|
||||
/**
|
||||
* Creates an instance of HTML.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param Options_Data $options Plugin options instance.
|
||||
*/
|
||||
public function __construct( Options_Data $options ) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust HTML to have delay js structure.
|
||||
*
|
||||
* @param string $html Buffer html for the page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function delay_js( $html ) {
|
||||
|
||||
if ( ! $this->is_allowed() ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$this->allowed_scripts = $this->prepare_allowed_scripts_regex();
|
||||
|
||||
if ( empty( $this->allowed_scripts ) ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return $this->parse( $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is allowed to Delay JS.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_allowed() {
|
||||
if ( rocket_bypass() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_rocket_post_excluded_option( 'delay_js' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->options->get( 'delay_js', 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the html and add/remove attributes from specific scripts.
|
||||
*
|
||||
* @param string $html Buffer html for the page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function parse( $html ) {
|
||||
$replaced_html = preg_replace_callback( '/<script\s*(?<attr>[^>]*)?>(?<content>.*)?<\/script>/Uims', [ $this, 'replace_scripts' ], $html );
|
||||
|
||||
if ( empty( $replaced_html ) ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return $replaced_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for preg_replace_callback that is used to adjust attributes for specific scripts.
|
||||
*
|
||||
* @param array $matches Matches array for scripts regex.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function replace_scripts( $matches ) {
|
||||
if (
|
||||
empty( $this->allowed_scripts )
|
||||
||
|
||||
(
|
||||
! empty( $this->allowed_scripts )
|
||||
&&
|
||||
! preg_match( '#(' . $this->allowed_scripts . ')#', $matches[0] )
|
||||
)
|
||||
) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$src = '';
|
||||
$matches['attr'] = trim( $matches['attr'] );
|
||||
|
||||
if ( ! empty( $matches['attr'] ) ) {
|
||||
if ( preg_match( '/src=(["\'])(.*?)\1/', $matches['attr'], $src_matches ) ) {
|
||||
$src = $src_matches[2];
|
||||
|
||||
// Remove the src attribute.
|
||||
$matches['attr'] = str_replace( $src_matches[0], '', $matches['attr'] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $src ) ) {
|
||||
// Get the JS content.
|
||||
if ( ! empty( $matches['content'] ) ) {
|
||||
$src = 'data:text/javascript;base64,' . base64_encode( $matches['content'] );// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $src ) ) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
return "<script data-rocketlazyloadscript='{$src}' {$matches['attr']}></script>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare allowed scripts to be used as regex.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function prepare_allowed_scripts_regex() {
|
||||
$delay_js_scripts = $this->options->get( 'delay_js_scripts', [] );
|
||||
|
||||
/**
|
||||
* Filters JS files to included into delay JS.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param array $delay_js_scripts List of allowed JS files.
|
||||
*/
|
||||
$delay_js_scripts = (array) apply_filters( 'rocket_delay_js_scripts', $delay_js_scripts );
|
||||
|
||||
if ( empty( $delay_js_scripts ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach ( $delay_js_scripts as $i => $delay_js_script ) {
|
||||
$delay_js_scripts[ $i ] = preg_quote( str_replace( '#', '\#', $delay_js_script ), '#' );
|
||||
}
|
||||
|
||||
return implode( '|', $delay_js_scripts );
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\DelayJS;
|
||||
|
||||
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
|
||||
|
||||
/**
|
||||
* Service provider for the WP Rocket Delay JS
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
class ServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The provides array is a way to let the container
|
||||
* know that a service is provided by this service
|
||||
* provider. Every service that is registered via
|
||||
* this service provider must have an alias added
|
||||
* to this array or it will be ignored.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = [
|
||||
'delay_js_settings',
|
||||
'delay_js_admin_subscriber',
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the option array in the container
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$this->getContainer()->add( 'delay_js_settings', 'WP_Rocket\Engine\Optimization\DelayJS\Admin\Settings' )
|
||||
->withArgument( $this->getContainer()->get( 'options' ) );
|
||||
$this->getContainer()->share( 'delay_js_admin_subscriber', 'WP_Rocket\Engine\Optimization\DelayJS\Admin\Subscriber' )
|
||||
->withArgument( $this->getContainer()->get( 'delay_js_settings' ) )
|
||||
->withArgument( $this->getContainer()->get( 'template_path' ) . '/settings' );
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\DelayJS;
|
||||
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
class Subscriber implements Subscriber_Interface {
|
||||
|
||||
/**
|
||||
* HTML instance.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @var HTML
|
||||
*/
|
||||
private $html;
|
||||
|
||||
/**
|
||||
* WP_Filesystem_Direct instance.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @var \WP_Filesystem_Direct
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* Script enqueued status.
|
||||
*
|
||||
* @since 3.7
|
||||
* @var bool
|
||||
*/
|
||||
private $is_enqueued = false;
|
||||
|
||||
/**
|
||||
* Subscriber constructor.
|
||||
*
|
||||
* @param HTML $html HTML Instance.
|
||||
* @param \WP_Filesystem_Direct $filesystem The Filesystem object.
|
||||
*/
|
||||
public function __construct( HTML $html, $filesystem ) {
|
||||
$this->html = $html;
|
||||
$this->filesystem = $filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
return [
|
||||
'rocket_buffer' => [
|
||||
[ 'delay_js', 21 ],
|
||||
],
|
||||
'wp_enqueue_scripts' => 'add_delay_js_script',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Using html buffer get scripts to be delayed and adjust their html.
|
||||
*
|
||||
* @param string $buffer_html Html for the page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function delay_js( $buffer_html ) {
|
||||
return $this->html->delay_js( $buffer_html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the inline script to the footer when the option is enabled.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_delay_js_script() {
|
||||
if ( $this->is_enqueued ) {
|
||||
return;
|
||||
}
|
||||
if ( ! $this->html->is_allowed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$js_assets_path = rocket_get_constant( 'WP_ROCKET_PATH' ) . 'assets/js/';
|
||||
|
||||
if ( ! wp_script_is( 'rocket-browser-checker' ) ) {
|
||||
$checker_filename = rocket_get_constant( 'SCRIPT_DEBUG' ) ? 'browser-checker.js' : 'browser-checker.min.js';
|
||||
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion
|
||||
wp_register_script(
|
||||
'rocket-browser-checker',
|
||||
'',
|
||||
[],
|
||||
'',
|
||||
true
|
||||
);
|
||||
wp_enqueue_script( 'rocket-browser-checker' );
|
||||
wp_add_inline_script(
|
||||
'rocket-browser-checker',
|
||||
$this->filesystem->get_contents( "{$js_assets_path}{$checker_filename}" )
|
||||
);
|
||||
}
|
||||
|
||||
// Register handle with no src to add the inline script after.
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion
|
||||
wp_register_script(
|
||||
'rocket-delay-js',
|
||||
'',
|
||||
[
|
||||
'rocket-browser-checker',
|
||||
],
|
||||
'',
|
||||
true
|
||||
);
|
||||
wp_enqueue_script( 'rocket-delay-js' );
|
||||
|
||||
$script_filename = rocket_get_constant( 'SCRIPT_DEBUG' ) ? 'lazyload-scripts.js' : 'lazyload-scripts.min.js';
|
||||
|
||||
wp_add_inline_script(
|
||||
'rocket-delay-js',
|
||||
$this->filesystem->get_contents( "{$js_assets_path}{$script_filename}" )
|
||||
);
|
||||
|
||||
$this->is_enqueued = true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\GoogleFonts\Admin;
|
||||
|
||||
use WP_Rocket\Abstract_Render;
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Engine\Admin\Beacon\Beacon;
|
||||
|
||||
class Settings extends Abstract_Render {
|
||||
/**
|
||||
* WP Rocket options instance
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Beacon instance
|
||||
*
|
||||
* @var Beacon
|
||||
*/
|
||||
private $beacon;
|
||||
|
||||
/**
|
||||
* Instantiate the class
|
||||
*
|
||||
* @param Options_Data $options WP Rocket options instance.
|
||||
* @param Beacon $beacon Beacon instance.
|
||||
* @param string $template_path Path to template files.
|
||||
*/
|
||||
public function __construct( Options_Data $options, Beacon $beacon, $template_path ) {
|
||||
parent::__construct( $template_path );
|
||||
|
||||
$this->options = $options;
|
||||
$this->beacon = $beacon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Google Fonts Optimization section in the tools tab
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_google_fonts_enabler() {
|
||||
if ( ! current_user_can( 'rocket_manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! apply_filters( 'pre_get_rocket_option_minify_google_fonts', true ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->options->get( 'minify_google_fonts', 0 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'beacon' => $this->beacon->get_suggest( 'google_fonts' ),
|
||||
];
|
||||
|
||||
echo $this->generate( 'settings/enable-google-fonts', $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for the AJAX request to enable Google Fonts Optimization
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enable_google_fonts() {
|
||||
check_ajax_referer( 'rocket-ajax', 'nonce', true );
|
||||
|
||||
if ( ! current_user_can( 'rocket_manage_options' ) ) {
|
||||
wp_send_json_error();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->options->set( 'minify_google_fonts', 1 );
|
||||
update_option( rocket_get_constant( 'WP_ROCKET_SLUG', 'wp_rocket_settings' ), $this->options->get_options() );
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\GoogleFonts\Admin;
|
||||
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
class Subscriber implements Subscriber_Interface {
|
||||
/**
|
||||
* Google Fonts Settings instance
|
||||
*
|
||||
* @var Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* Instantiate the class
|
||||
*
|
||||
* @param Settings $settings Google Fonts Settings instance.
|
||||
*/
|
||||
public function __construct( Settings $settings ) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
return [
|
||||
'rocket_settings_tools_content' => 'display_google_fonts_enabler',
|
||||
'wp_ajax_rocket_enable_google_fonts' => 'enable_google_fonts',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Google Fonts Optimization section in the tools tab
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_google_fonts_enabler() {
|
||||
$this->settings->display_google_fonts_enabler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for the AJAX request to enable Google Fonts Optimization
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enable_google_fonts() {
|
||||
$this->settings->enable_google_fonts();
|
||||
}
|
||||
}
|
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\GoogleFonts;
|
||||
|
||||
use WP_Rocket\Logger\Logger;
|
||||
use WP_Rocket\Engine\Optimization\AbstractOptimization;
|
||||
|
||||
/**
|
||||
* Combine Google Fonts
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*/
|
||||
class Combine extends AbstractOptimization {
|
||||
/**
|
||||
* Found fonts
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fonts = '';
|
||||
|
||||
/**
|
||||
* Found subsets
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $subsets = '';
|
||||
|
||||
/**
|
||||
* Combines multiple Google Fonts links into one
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function optimize( $html ) {
|
||||
Logger::info( 'GOOGLE FONTS COMBINE PROCESS STARTED.', [ 'GF combine process' ] );
|
||||
|
||||
$html_nocomments = $this->hide_comments( $html );
|
||||
$fonts = $this->find( '<link(?:\s+(?:(?!href\s*=\s*)[^>])+)?(?:\s+href\s*=\s*([\'"])(?<url>(?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>', $html_nocomments );
|
||||
|
||||
if ( ! $fonts ) {
|
||||
Logger::debug( 'No Google Fonts found.', [ 'GF combine process' ] );
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
$num_fonts = count( $fonts );
|
||||
|
||||
Logger::debug(
|
||||
"Found {$num_fonts} Google Fonts.",
|
||||
[
|
||||
'GF combine process',
|
||||
'tags' => $fonts,
|
||||
]
|
||||
);
|
||||
|
||||
if ( 1 === $num_fonts ) {
|
||||
return str_replace( $fonts[0][0], $this->get_font_with_display( $fonts[0] ), $html );
|
||||
}
|
||||
|
||||
$this->parse( $fonts );
|
||||
|
||||
if ( empty( $this->fonts ) ) {
|
||||
Logger::debug( 'No Google Fonts left to combine.', [ 'GF combine process' ] );
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
$html = preg_replace( '@<\/title>@i', '$0' . $this->get_combine_tag(), $html, 1 );
|
||||
|
||||
foreach ( $fonts as $font ) {
|
||||
$html = str_replace( $font[0], '', $html );
|
||||
}
|
||||
|
||||
Logger::info(
|
||||
'Google Fonts successfully combined.',
|
||||
[
|
||||
'GF combine process',
|
||||
'url' => $this->fonts . $this->subsets,
|
||||
]
|
||||
);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds links to Google fonts
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $pattern Pattern to search for.
|
||||
* @param string $html HTML content.
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
protected function find( $pattern, $html ) {
|
||||
$result = preg_match_all( '/' . $pattern . '/Umsi', $html, $matches, PREG_SET_ORDER );
|
||||
|
||||
if ( empty( $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses found matches to extract fonts and subsets.
|
||||
*
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param array $matches Found matches for the pattern.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parse( array $matches ) {
|
||||
$fonts_array = [];
|
||||
$subsets_array = [];
|
||||
foreach ( $matches as $match ) {
|
||||
$url = html_entity_decode( $match[2] );
|
||||
$query = wp_parse_url( $url, PHP_URL_QUERY );
|
||||
if ( empty( $query ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$font = wp_parse_args( $query );
|
||||
if ( isset( $font['family'] ) ) {
|
||||
$font_family = $font['family'];
|
||||
$font_family = rtrim( $font_family, '%7C' );
|
||||
$font_family = rtrim( $font_family, '|' );
|
||||
// Add font to the collection.
|
||||
$fonts_array[] = rawurlencode( htmlentities( $font_family ) );
|
||||
}
|
||||
|
||||
// Add subset to collection.
|
||||
if ( isset( $font['subset'] ) ) {
|
||||
$subsets_array[] = rawurlencode( htmlentities( $font['subset'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Concatenate fonts tag.
|
||||
$this->subsets = ! empty( $subsets_array ) ? '&subset=' . implode( ',', array_filter( array_unique( $subsets_array ) ) ) : '';
|
||||
$this->fonts = ! empty( $fonts_array ) ? implode( '%7C', array_filter( array_unique( $fonts_array ) ) ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the combined Google fonts link tag
|
||||
*
|
||||
* @since 3.3.5 Add support for the display parameter
|
||||
* @since 3.1
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_combine_tag() {
|
||||
$display = $this->get_font_display_value();
|
||||
|
||||
return sprintf(
|
||||
'<link rel="stylesheet" href="%s" />', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
|
||||
esc_url( "https://fonts.googleapis.com/css?family={$this->fonts}{$this->subsets}&display={$display}" )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns font with display value.
|
||||
*
|
||||
* @since 3.5.1
|
||||
* @author Soponar Cristina
|
||||
*
|
||||
* @param array $font Array containing font tag and matches.
|
||||
*
|
||||
* @return string Google Font tag with display param.
|
||||
*/
|
||||
protected function get_font_with_display( array $font ) {
|
||||
$font_url = html_entity_decode( $font['url'] );
|
||||
$query = wp_parse_url( $font_url, PHP_URL_QUERY );
|
||||
|
||||
if ( empty( $query ) ) {
|
||||
return $font[0];
|
||||
}
|
||||
|
||||
$display = $this->get_font_display_value();
|
||||
$parsed_font = wp_parse_args( $query );
|
||||
|
||||
$font_url = ! empty( $parsed_font['display'] )
|
||||
? str_replace( "&display={$parsed_font['display']}", "&display={$display}", $font_url )
|
||||
: "{$font_url}&display={$display}";
|
||||
|
||||
return str_replace( $font['url'], esc_url( $font_url ), $font[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font display value.
|
||||
*
|
||||
* @since 3.5.1
|
||||
*
|
||||
* @return string font display value.
|
||||
*/
|
||||
protected function get_font_display_value() {
|
||||
$allowed_values = [
|
||||
'auto' => 1,
|
||||
'block' => 1,
|
||||
'swap' => 1,
|
||||
'fallback' => 1,
|
||||
'optional' => 1,
|
||||
];
|
||||
|
||||
/**
|
||||
* Filters the combined Google Fonts display parameter value
|
||||
*
|
||||
* @since 3.3.5
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @param string $display Display value. Can be either auto, block, swap, fallback or optional.
|
||||
*/
|
||||
$display = apply_filters( 'rocket_combined_google_fonts_display', 'swap' );
|
||||
if ( ! is_string( $display ) ) {
|
||||
return 'swap';
|
||||
}
|
||||
|
||||
return isset( $allowed_values[ $display ] ) ? $display : 'swap';
|
||||
}
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\GoogleFonts;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
/**
|
||||
* Combine Google Fonts subscriber
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class Subscriber implements Subscriber_Interface {
|
||||
/**
|
||||
* Plugin options.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Combine instance.
|
||||
*
|
||||
* @var Combine
|
||||
*/
|
||||
private $combine;
|
||||
|
||||
/**
|
||||
* Instantiate the subscirber
|
||||
*
|
||||
* @param Combine $combine Combine instance.
|
||||
* @param Options_Data $options Options_Data instance.
|
||||
*/
|
||||
public function __construct( Combine $combine, Options_Data $options ) {
|
||||
$this->combine = $combine;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
return [
|
||||
'wp_resource_hints' => [ 'preconnect', 10, 2 ],
|
||||
'rocket_buffer' => [ 'process', 18 ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds google fonts URL to preconnect
|
||||
*
|
||||
* @since 3.5.3
|
||||
*
|
||||
* @param array $urls URLs to print for resource hints.
|
||||
* @param string $relation_type The relation type the URLs are printed for, e.g. 'preconnect' or 'prerender'.
|
||||
* @return array
|
||||
*/
|
||||
public function preconnect( array $urls, $relation_type ) {
|
||||
if ( ! $this->is_allowed() ) {
|
||||
return $urls;
|
||||
}
|
||||
|
||||
if ( 'preconnect' !== $relation_type ) {
|
||||
return $urls;
|
||||
}
|
||||
|
||||
$urls[] = [
|
||||
'href' => 'https://fonts.gstatic.com',
|
||||
1 => 'crossorigin',
|
||||
];
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the HTML to combine found Google fonts
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
public function process( $html ) {
|
||||
if ( ! $this->is_allowed() ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return $this->combine->optimize( $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if files can combine found Google fonts.
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
protected function is_allowed() {
|
||||
if ( rocket_bypass() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->options->get( 'minify_google_fonts', 0 );
|
||||
}
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization;
|
||||
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
/**
|
||||
* Handles IE conditionals comments in the HTML to prevent their content from being processed during optimization.
|
||||
*
|
||||
* @since 3.6 Changes template tag to be an HTML comment.
|
||||
* @since 3.1
|
||||
*/
|
||||
class IEConditionalSubscriber implements Subscriber_Interface {
|
||||
|
||||
/**
|
||||
* Stores IE conditionals.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $conditionals = [];
|
||||
|
||||
/**
|
||||
* HTML IE conditional pattern.
|
||||
*
|
||||
* @since 3.6.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IE_PATTERN = '/<!--\[if[^\]]*?\]>.*?<!\[endif\]-->/is';
|
||||
|
||||
/**
|
||||
* HTML IE conditional template tag.
|
||||
*
|
||||
* @since 3.6.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const WP_ROCKET_CONDITIONAL = '<!--{{WP_ROCKET_CONDITIONAL}}-->';
|
||||
|
||||
/**
|
||||
* Return an array of events that this subscriber listens to.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
return [
|
||||
'rocket_buffer' => [
|
||||
[ 'extract_ie_conditionals', 1 ],
|
||||
[ 'inject_ie_conditionals', 34 ],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts IE conditionals tags and replace them with placeholders.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function extract_ie_conditionals( $html ) {
|
||||
preg_match_all( self::IE_PATTERN, $html, $conditionals_match );
|
||||
|
||||
if ( ! $conditionals_match ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
foreach ( $conditionals_match[0] as $conditional ) {
|
||||
$this->conditionals[] = $conditional;
|
||||
}
|
||||
|
||||
return preg_replace( self::IE_PATTERN, self::WP_ROCKET_CONDITIONAL, $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces WP Rocket placeholders with IE conditional tags.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function inject_ie_conditionals( $html ) {
|
||||
if ( ! $this->has_conditional_tag( $html ) ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
foreach ( $this->conditionals as $conditional ) {
|
||||
// Prevent scripts containing things like "\\s" to be striped of a backslash when put back in content.
|
||||
if ( preg_match( '@^(?<opening><!--\[if[^\]]*?\]>\s*?(?:<!-->)?\s*<script(?:\s[^>]*?>))\s*(?<content>.*?)\s*(?<closing></script>\s*(?:<!--)?\s*?<!\[endif\]-->)$@is', $conditional, $matches ) ) {
|
||||
$conditional = $matches['opening'] . preg_replace( '#(?<!\\\\)(\\$|\\\\)#', '\\\\$1', $matches['content'] ) . $matches['closing'];
|
||||
}
|
||||
|
||||
$html = $this->replace_conditional_tag( $html, $conditional );
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the template tag for the IE conditional exists in the given HTML string.
|
||||
*
|
||||
* @since 3.6.2
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
*
|
||||
* @return bool true if at least one exists; else false.
|
||||
*/
|
||||
private function has_conditional_tag( $html ) {
|
||||
return ( false !== strpos( $html, self::WP_ROCKET_CONDITIONAL ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the template tag with the original IE conditional HTML.
|
||||
*
|
||||
* @since 3.6.2
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @param string $original Original IE conditional HTML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function replace_conditional_tag( $html, $original ) {
|
||||
$template_tag_position = strpos( $html, self::WP_ROCKET_CONDITIONAL );
|
||||
|
||||
return substr_replace(
|
||||
$html,
|
||||
$original,
|
||||
$template_tag_position,
|
||||
strlen( self::WP_ROCKET_CONDITIONAL )
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\Minify;
|
||||
|
||||
use WP_Filesystem_Direct;
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
/**
|
||||
* Parent class for minify subscribers.
|
||||
*/
|
||||
abstract class AbstractMinifySubscriber implements Subscriber_Interface {
|
||||
/**
|
||||
* Plugin options.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Processor instance.
|
||||
*
|
||||
* @var ProcessorInterface
|
||||
*/
|
||||
protected $processor;
|
||||
|
||||
/**
|
||||
* Filesystem instance
|
||||
*
|
||||
* @var WP_Filesystem_Direct
|
||||
*/
|
||||
protected $filesystem;
|
||||
|
||||
/**
|
||||
* Creates an instance of inheriting class.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param Options_Data $options Plugin options.
|
||||
* @param WP_Filesystem_Direct $filesystem Filesystem instance.
|
||||
*/
|
||||
public function __construct( Options_Data $options, $filesystem ) {
|
||||
$this->options = $options;
|
||||
$this->filesystem = $filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of processor to use
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param ProcessorInterface $processor Processor instance.
|
||||
* @return void
|
||||
*/
|
||||
protected function set_processor_type( ProcessorInterface $processor ) {
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the HTML to perform an optimization and return the new content
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
abstract public function process( $html );
|
||||
|
||||
/**
|
||||
* Checks if files can be optimized
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
abstract protected function is_allowed();
|
||||
|
||||
/**
|
||||
* Fix issue with SSL and minification
|
||||
*
|
||||
* @since 2.3
|
||||
*
|
||||
* @param string $url An url to filter to set the scheme to https if needed.
|
||||
* @return string
|
||||
*/
|
||||
public function fix_ssl_minify( $url ) {
|
||||
if ( ! is_ssl() ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
if ( 0 === strpos( $url, 'https://' ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// This filter is documented in inc/Engine/Admin/Settings/Settings.php.
|
||||
if ( in_array( wp_parse_url( $url, PHP_URL_HOST ), apply_filters( 'rocket_cdn_hosts', [], ( $this->get_zones() ) ), true ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return str_replace( 'http://', 'https://', $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility with multilingual plugins & multidomain configuration
|
||||
*
|
||||
* @since 2.6.13 Regression Fix: Apply CDN on minified CSS and JS files by checking the CNAME host
|
||||
* @since 2.6.8
|
||||
*
|
||||
* @param string $url Minified file URL.
|
||||
* @return string Updated minified file URL
|
||||
*/
|
||||
public function i18n_multidomain_url( $url ) {
|
||||
if ( ! rocket_has_i18n() ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$url_host = wp_parse_url( $url, PHP_URL_HOST );
|
||||
|
||||
if ( isset( $_SERVER['HTTP_HOST'] ) && $url_host === $_SERVER['HTTP_HOST'] ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
if ( ! in_array( $_SERVER['HTTP_HOST'], get_rocket_i18n_host(), true ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// This filter is documented in inc/Engine/Admin/Settings/Settings.php.
|
||||
$cdn_hosts = apply_filters( 'rocket_cdn_hosts', [], ( $this->get_zones() ) );
|
||||
|
||||
if ( in_array( $url_host, $cdn_hosts, true ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return str_replace( $url_host, sanitize_text_field( $_SERVER['HTTP_HOST'] ), $url ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\CSS;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Engine\Optimization\AbstractOptimization;
|
||||
use WP_Rocket\Engine\Optimization\AssetsLocalCache;
|
||||
|
||||
/**
|
||||
* Abstract class for CSS Optimization
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
abstract class AbstractCSSOptimization extends AbstractOptimization {
|
||||
const FILE_TYPE = 'css';
|
||||
|
||||
/**
|
||||
* Assets local cache instance
|
||||
*
|
||||
* @var AssetsLocalCache
|
||||
*/
|
||||
protected $local_cache;
|
||||
|
||||
/**
|
||||
* Creates an instance of inheriting class.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param Options_Data $options Options instance.
|
||||
* @param AssetsLocalCache $local_cache AssetsLocalCache instance.
|
||||
*/
|
||||
public function __construct( Options_Data $options, AssetsLocalCache $local_cache ) {
|
||||
$this->options = $options;
|
||||
$this->local_cache = $local_cache;
|
||||
$this->minify_key = $this->options->get( 'minify_css_key', create_rocket_uniqid() );
|
||||
$this->excluded_files = $this->get_excluded_files();
|
||||
$this->init_base_path_and_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all files to exclude from minification/concatenation.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_excluded_files() {
|
||||
$excluded_files = $this->options->get( 'exclude_css', [] );
|
||||
|
||||
/**
|
||||
* Filters CSS files to exclude from minification/concatenation.
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param array $excluded_files List of excluded CSS files.
|
||||
*/
|
||||
$excluded_files = (array) apply_filters( 'rocket_exclude_css', $excluded_files );
|
||||
|
||||
if ( empty( $excluded_files ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach ( $excluded_files as $i => $excluded_file ) {
|
||||
$excluded_files[ $i ] = str_replace( '#', '\#', $excluded_file );
|
||||
}
|
||||
|
||||
return implode( '|', $excluded_files );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CDN zones.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_zones() {
|
||||
return [ 'all', 'css_and_js', self::FILE_TYPE ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minify URL
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $filename Minified filename.
|
||||
* @param string $original_url Original URL for this file. Optional.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_minify_url( $filename, $original_url = '' ) {
|
||||
$minify_url = $this->minify_base_url . $filename;
|
||||
|
||||
/**
|
||||
* Filters CSS file URL with CDN hostname
|
||||
*
|
||||
* @since 2.1
|
||||
*
|
||||
* @param string $minify_url Minified file URL.
|
||||
* @param string $original_url Original URL for this file.
|
||||
*/
|
||||
return apply_filters( 'rocket_css_url', $minify_url, $original_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if it is a file excluded from minification
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param array $tag Tag corresponding to a CSS file.
|
||||
*
|
||||
* @return bool True if it is a file excluded, false otherwise
|
||||
*/
|
||||
protected function is_minify_excluded_file( array $tag ) {
|
||||
if ( ! isset( $tag[0], $tag['url'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// File should not be minified.
|
||||
if ( false !== strpos( $tag[0], 'data-minify=' ) || false !== strpos( $tag[0], 'data-no-minify=' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( false !== strpos( $tag[0], 'media=' ) && ! preg_match( '/media=["\'](?:\s*|[^"\']*?\b(all|screen)\b[^"\']*?)["\']/i', $tag[0] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( false !== strpos( $tag[0], 'only screen and' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$file_path = wp_parse_url( $tag['url'], PHP_URL_PATH );
|
||||
|
||||
// File extension is not css.
|
||||
if ( pathinfo( $file_path, PATHINFO_EXTENSION ) !== self::FILE_TYPE ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! empty( $this->excluded_files ) ) {
|
||||
// File is excluded from minification/concatenation.
|
||||
if ( preg_match( '#^(' . $this->excluded_files . ')$#', $file_path ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\CSS;
|
||||
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
/**
|
||||
* Minify/Combine CSS Admin subscriber
|
||||
*
|
||||
* @since 3.5.4
|
||||
*/
|
||||
class AdminSubscriber implements Subscriber_Interface {
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.5.4
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
$slug = rocket_get_constant( 'WP_ROCKET_SLUG', 'wp_rocket_settings' );
|
||||
|
||||
return [
|
||||
"update_option_{$slug}" => [ 'clean_minify', 10, 2 ],
|
||||
"pre_update_option_{$slug}" => [ 'regenerate_minify_css_key', 10, 2 ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean minify CSS files when options change.
|
||||
*
|
||||
* @since 3.5.4
|
||||
*
|
||||
* @param array $old An array of previous settings.
|
||||
* @param array $new An array of submitted settings.
|
||||
*/
|
||||
public function clean_minify( $old, $new ) {
|
||||
if ( ! is_array( $old ) || ! is_array( $new ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->maybe_minify_regenerate( $new, $old ) ) {
|
||||
return;
|
||||
}
|
||||
// Purge all minify cache files.
|
||||
rocket_clean_minify( 'css' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the minify key if CSS files have been modified.
|
||||
*
|
||||
* @since 3.5.4
|
||||
*
|
||||
* @param array $new An array of submitted settings.
|
||||
* @param array $old An array of previous settings.
|
||||
*
|
||||
* @return array Updates 'minify_css_key' setting when regenerated; else, original submitted settings.
|
||||
*/
|
||||
public function regenerate_minify_css_key( $new, $old ) {
|
||||
if ( ! is_array( $old ) || ! is_array( $new ) ) {
|
||||
return $new;
|
||||
}
|
||||
|
||||
if ( ! $this->maybe_minify_regenerate( $new, $old ) ) {
|
||||
return $new;
|
||||
}
|
||||
|
||||
$new['minify_css_key'] = create_rocket_uniqid();
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks minify CSS condition when options change.
|
||||
*
|
||||
* @since 3.5.4
|
||||
*
|
||||
* @param array $new An array of submitted settings.
|
||||
* @param array $old An array of previous settings.
|
||||
*
|
||||
* @return bool true when should regenerate; else false.
|
||||
*/
|
||||
protected function maybe_minify_regenerate( array $new, array $old ) {
|
||||
$settings_to_check = [
|
||||
'minify_css',
|
||||
'exclude_css',
|
||||
'cdn',
|
||||
];
|
||||
|
||||
foreach ( $settings_to_check as $setting ) {
|
||||
if ( $this->did_setting_change( $setting, $new, $old ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
array_key_exists( 'cdn', $new )
|
||||
&&
|
||||
1 === (int) $new['cdn']
|
||||
&&
|
||||
$this->did_setting_change( 'cdn_cnames', $new, $old )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given setting's value changed.
|
||||
*
|
||||
* @since 3.5.4
|
||||
*
|
||||
* @param string $setting The settings's value to check in the old and new values.
|
||||
* @param array $new An array of submitted settings.
|
||||
* @param array $old An array of previous settings.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function did_setting_change( $setting, array $new, array $old ) {
|
||||
return (
|
||||
array_key_exists( $setting, $old )
|
||||
&&
|
||||
array_key_exists( $setting, $new )
|
||||
&&
|
||||
$old[ $setting ] !== $new[ $setting ]
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\CSS;
|
||||
|
||||
use WP_Rocket\Dependencies\Minify\CSS as MinifyCSS;
|
||||
use WP_Rocket\Engine\Optimization\CSSTrait;
|
||||
use WP_Rocket\Engine\Optimization\Minify\ProcessorInterface;
|
||||
use WP_Rocket\Logger\Logger;
|
||||
|
||||
/**
|
||||
* Minify & Combine CSS files
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class Combine extends AbstractCSSOptimization implements ProcessorInterface {
|
||||
use CSSTrait;
|
||||
|
||||
/**
|
||||
* Array of styles
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $styles = [];
|
||||
|
||||
/**
|
||||
* Combined CSS filename
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $filename;
|
||||
|
||||
/**
|
||||
* Minifies and combines all CSS files into one
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
public function optimize( $html ) {
|
||||
Logger::info( 'CSS COMBINE PROCESS STARTED.', [ 'css combine process' ] );
|
||||
|
||||
$html_nocomments = $this->hide_comments( $html );
|
||||
$styles = $this->find( '<link\s+([^>]+[\s"\'])?href\s*=\s*[\'"]\s*?(?<url>[^\'"]+\.css(?:\?[^\'"]*)?)\s*?[\'"]([^>]+)?\/?>', $html_nocomments );
|
||||
|
||||
if ( ! $styles ) {
|
||||
Logger::debug( 'No `<link>` tags found.', [ 'css combine process' ] );
|
||||
return $html;
|
||||
}
|
||||
|
||||
Logger::debug(
|
||||
'Found ' . count( $styles ) . ' `<link>` tag(s).',
|
||||
[
|
||||
'css combine process',
|
||||
'tags' => $styles,
|
||||
]
|
||||
);
|
||||
|
||||
$styles = $this->parse( $styles );
|
||||
|
||||
if ( empty( $styles ) ) {
|
||||
Logger::debug( 'No `<link>` tags to optimize.', [ 'css combine process' ] );
|
||||
return $html;
|
||||
}
|
||||
|
||||
Logger::debug(
|
||||
count( $styles ) . ' `<link>` tag(s) remaining.',
|
||||
[
|
||||
'css combine process',
|
||||
'tags' => $styles,
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! $this->combine() ) {
|
||||
Logger::error( 'CSS combine process failed.', [ 'css combine process' ] );
|
||||
return $html;
|
||||
}
|
||||
|
||||
return $this->insert_combined_css( $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses all found styles tag to keep only the ones to combine
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param array $styles Array of matched styles.
|
||||
* @return array
|
||||
*/
|
||||
private function parse( array $styles ) {
|
||||
foreach ( $styles as $key => $style ) {
|
||||
if ( $this->is_external_file( $style['url'] ) ) {
|
||||
if ( $this->is_excluded_external( $style['url'] ) ) {
|
||||
unset( $styles[ $key ] );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->styles[ $style['url'] ] = [
|
||||
'type' => 'external',
|
||||
'tag' => $style[0],
|
||||
'url' => rocket_add_url_protocol( strtok( $style['url'], '?' ) ),
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $this->is_minify_excluded_file( $style ) ) {
|
||||
Logger::debug(
|
||||
'Style is excluded.',
|
||||
[
|
||||
'css combine process',
|
||||
'tag' => $style[0],
|
||||
]
|
||||
);
|
||||
|
||||
unset( $styles[ $key ] );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->styles[ $style['url'] ] = [
|
||||
'type' => 'internal',
|
||||
'tag' => $style[0],
|
||||
'url' => strtok( $style['url'], '?' ),
|
||||
];
|
||||
}
|
||||
|
||||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided external URL is excluded from combine
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $url External URL to check.
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_excluded_external( $url ) {
|
||||
foreach ( $this->get_excluded_externals() as $excluded ) {
|
||||
if ( false !== strpos( $url, $excluded ) ) {
|
||||
Logger::debug(
|
||||
'Style is external.',
|
||||
[
|
||||
'css combine process',
|
||||
'url' => $url,
|
||||
]
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets external URLs excluded from combine
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_excluded_externals() {
|
||||
/**
|
||||
* Filters CSS external URLs to exclude from the combine process
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param array $pattern Patterns to match.
|
||||
*/
|
||||
$excluded_externals = (array) apply_filters( 'rocket_combine_css_excluded_external', [] );
|
||||
|
||||
return array_merge( $excluded_externals, $this->options->get( 'exclude_css', [] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the CSS content into one file and save it
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
protected function combine() {
|
||||
if ( empty( $this->styles ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_hash = implode( ',', array_column( $this->styles, 'url' ) );
|
||||
$this->filename = md5( $file_hash . $this->minify_key ) . '.css';
|
||||
|
||||
$combined_file = $this->minify_base_path . $this->filename;
|
||||
|
||||
if ( rocket_direct_filesystem()->exists( $combined_file ) ) {
|
||||
Logger::debug(
|
||||
'Combined CSS file already exists.',
|
||||
[
|
||||
'css combine process',
|
||||
'path' => $combined_file,
|
||||
]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$combined_content = $this->get_content( $combined_file );
|
||||
$combined_content = $this->apply_font_display_swap( $combined_content );
|
||||
|
||||
if ( empty( $combined_content ) ) {
|
||||
Logger::error(
|
||||
'No combined content.',
|
||||
[
|
||||
'css combine process',
|
||||
'path' => $combined_file,
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->write_file( $combined_content, $combined_file ) ) {
|
||||
Logger::error(
|
||||
'Combined CSS file could not be created.',
|
||||
[
|
||||
'css combine process',
|
||||
'path' => $combined_file,
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger::debug(
|
||||
'Combined CSS file successfully created.',
|
||||
[
|
||||
'css combine process',
|
||||
'path' => $combined_file,
|
||||
]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the combined CSS file and remove the original CSS tags
|
||||
*
|
||||
* The combined CSS file is added after the closing </title> tag, and the replacement occurs only once. The original CSS tags are then removed from the HTML.
|
||||
*
|
||||
* @since 3.3.3
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
protected function insert_combined_css( $html ) {
|
||||
foreach ( $this->styles as $style ) {
|
||||
$html = str_replace( $style['tag'], '', $html );
|
||||
}
|
||||
|
||||
$minify_url = $this->get_minify_url( $this->filename );
|
||||
|
||||
Logger::info(
|
||||
'Combined CSS file successfully added.',
|
||||
[
|
||||
'css combine process',
|
||||
'url' => $minify_url,
|
||||
]
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
|
||||
return preg_replace( '/<\/title>/i', '$0<link rel="stylesheet" href="' . esc_url( $minify_url ) . '" media="all" data-minify="1" />', $html, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers the content from all styles to combine & minify it if needed
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $combined_file Absolute path to the combined file.
|
||||
* @return string
|
||||
*/
|
||||
private function get_content( $combined_file ) {
|
||||
$content = '';
|
||||
|
||||
foreach ( $this->styles as $key => $style ) {
|
||||
if ( 'internal' === $style['type'] ) {
|
||||
$filepath = $this->get_file_path( $style['url'] );
|
||||
$file_content = $this->get_file_content( $filepath );
|
||||
$file_content = $this->rewrite_paths( $filepath, $combined_file, $file_content );
|
||||
} elseif ( 'external' === $style['type'] ) {
|
||||
$file_content = $this->local_cache->get_content( $style['url'] );
|
||||
$file_content = $this->rewrite_paths( $style['url'], $combined_file, $file_content );
|
||||
}
|
||||
|
||||
if ( empty( $file_content ) ) {
|
||||
unset( $this->styles[ $key ] );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$content .= $file_content;
|
||||
}
|
||||
|
||||
$content = $this->minify( $content );
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
Logger::debug( 'No CSS content.', [ 'css combine process' ] );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minifies the content
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $content Content to minify.
|
||||
* @return string
|
||||
*/
|
||||
protected function minify( $content ) {
|
||||
$minifier = new MinifyCSS( $content );
|
||||
|
||||
return $minifier->minify();
|
||||
}
|
||||
}
|
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\CSS;
|
||||
|
||||
use WP_Rocket\Dependencies\Minify as Minifier;
|
||||
use WP_Rocket\Engine\Optimization\CSSTrait;
|
||||
use WP_Rocket\Engine\Optimization\Minify\ProcessorInterface;
|
||||
use WP_Rocket\Logger\Logger;
|
||||
|
||||
/**
|
||||
* Minify CSS files
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class Minify extends AbstractCSSOptimization implements ProcessorInterface {
|
||||
use CSSTrait;
|
||||
|
||||
/**
|
||||
* Minifies CSS files
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
public function optimize( $html ) {
|
||||
Logger::info( 'CSS MINIFICATION PROCESS STARTED.', [ 'css minification process' ] );
|
||||
|
||||
$styles = $this->get_styles( $html );
|
||||
|
||||
if ( empty( $styles ) ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
foreach ( $styles as $style ) {
|
||||
if ( $this->is_minify_excluded_file( $style ) ) {
|
||||
Logger::debug(
|
||||
'Style is excluded.',
|
||||
[
|
||||
'css minification process',
|
||||
'tag' => $style[0],
|
||||
]
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$integrity_validated = $this->local_cache->validate_integrity( $style );
|
||||
|
||||
if ( false === $integrity_validated ) {
|
||||
Logger::debug(
|
||||
'Style integrity attribute not valid.',
|
||||
[
|
||||
'css minification process',
|
||||
'tag' => $style[0],
|
||||
]
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$style['final'] = $integrity_validated;
|
||||
|
||||
$minify_url = $this->replace_url( strtok( $style['url'], '?' ) );
|
||||
|
||||
if ( ! $minify_url ) {
|
||||
Logger::error(
|
||||
'Style minification failed.',
|
||||
[
|
||||
'css minification process',
|
||||
'tag' => $style[0],
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$html = $this->replace_style( $style, $minify_url, $html );
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all style tags from HTML.
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return array Array with style tags, empty array if no style tags found.
|
||||
*/
|
||||
protected function get_styles( $html ) {
|
||||
$html_nocomments = $this->hide_comments( $html );
|
||||
$styles = $this->find( '<link\s+([^>]+[\s"\'])?href\s*=\s*[\'"]\s*?(?<url>[^\'"]+\.css(?:\?[^\'"]*)?)\s*?[\'"]([^>]+)?\/?>', $html_nocomments );
|
||||
|
||||
if ( ! $styles ) {
|
||||
Logger::debug( 'No `<link>` tags found.', [ 'css minification process' ] );
|
||||
return [];
|
||||
}
|
||||
|
||||
Logger::debug(
|
||||
'Found ' . count( $styles ) . ' `<link>` tags.',
|
||||
[
|
||||
'css minification process',
|
||||
'tags' => $styles,
|
||||
]
|
||||
);
|
||||
|
||||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the minify URL if the minification is successful
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param string $url Original file URL.
|
||||
|
||||
* @return string|bool The minify URL if successful, false otherwise
|
||||
*/
|
||||
private function replace_url( $url ) {
|
||||
if ( empty( $url ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This filter is documented in /inc/classes/optimization/class-abstract-optimization.php.
|
||||
$url = apply_filters( 'rocket_asset_url', $url, $this->get_zones() );
|
||||
|
||||
$parsed_url = wp_parse_url( $url );
|
||||
|
||||
if ( empty( $parsed_url['path'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! empty( $parsed_url['host'] ) ) {
|
||||
$url = rocket_add_url_protocol( $url );
|
||||
}
|
||||
|
||||
$unique_id = md5( $url . $this->minify_key );
|
||||
$filename = preg_replace( '/\.(css)$/', '-' . $unique_id . '.css', ltrim( rocket_realpath( $parsed_url['path'] ), '/' ) );
|
||||
$minified_file = rawurldecode( $this->minify_base_path . $filename );
|
||||
$minify_url = $this->get_minify_url( $filename, $url );
|
||||
|
||||
if ( rocket_direct_filesystem()->exists( $minified_file ) ) {
|
||||
Logger::debug(
|
||||
'Minified CSS file already exists.',
|
||||
[
|
||||
'css minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
return $minify_url;
|
||||
}
|
||||
|
||||
$external_url = $this->is_external_file( $url );
|
||||
$file_path = $external_url ? $this->local_cache->get_filepath( $url ) : $this->get_file_path( $url );
|
||||
|
||||
if ( empty( $file_path ) ) {
|
||||
Logger::error(
|
||||
'Couldn’t get the file path from the URL.',
|
||||
[
|
||||
'css minification process',
|
||||
'url' => $url,
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_content = $external_url ? $this->local_cache->get_content( $url ) : $this->get_file_content( $file_path );
|
||||
|
||||
if ( ! $file_content ) {
|
||||
Logger::error(
|
||||
'No file content.',
|
||||
[
|
||||
'css minification process',
|
||||
'path' => $file_path,
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$minified_content = $external_url ? $this->minify( $url, $minified_file, $file_content ) : $this->minify( $file_path, $minified_file, $file_content );
|
||||
|
||||
if ( empty( $minified_content ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$minified_content = $this->font_display_swap( $url, $minified_file, $minified_content );
|
||||
|
||||
if ( empty( $minified_content ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$save_minify_file = $this->save_minify_file( $minified_file, $minified_content );
|
||||
|
||||
if ( ! $save_minify_file ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $minify_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace old style tag with the minified tag.
|
||||
*
|
||||
* @param array $style Style matched data.
|
||||
* @param string $minify_url Minified URL.
|
||||
* @param string $html HTML content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function replace_style( $style, $minify_url, $html ) {
|
||||
$replace_style = str_replace( $style['url'], $minify_url, $style['final'] );
|
||||
$replace_style = str_replace( '<link', '<link data-minify="1"', $replace_style );
|
||||
$html = str_replace( $style[0], $replace_style, $html );
|
||||
|
||||
Logger::info(
|
||||
'Style minification succeeded.',
|
||||
[
|
||||
'css minification process',
|
||||
'url' => $minify_url,
|
||||
]
|
||||
);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save minified CSS file.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $minified_file Minified file path.
|
||||
* @param string $minified_content Minified HTML content.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function save_minify_file( $minified_file, $minified_content ) {
|
||||
$save_minify_file = $this->write_file( $minified_content, $minified_file );
|
||||
|
||||
if ( ! $save_minify_file ) {
|
||||
Logger::error(
|
||||
'Minified CSS file could not be created.',
|
||||
[
|
||||
'css minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
Logger::debug(
|
||||
'Minified CSS file successfully created.',
|
||||
[
|
||||
'css minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies font display swap if the file contains @font-face.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $url File Url.
|
||||
* @param string $minified_file Minified file path.
|
||||
* @param string $content CSS file content.
|
||||
* @return string
|
||||
*/
|
||||
protected function font_display_swap( $url, $minified_file, $content ) {
|
||||
if (
|
||||
preg_match( '/(?:-|\.)min.css/iU', $url )
|
||||
&&
|
||||
false === stripos( $content, '@font-face' )
|
||||
) {
|
||||
Logger::error(
|
||||
'Do not apply font display swap on min.css files without font-face.',
|
||||
[
|
||||
'css minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! $this->is_external_file( $url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
return $this->apply_font_display_swap( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Minifies the content
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param string $file_path Source filepath.
|
||||
* @param string $minified_file Target filepath.
|
||||
* @param string $file_content Content to minify.
|
||||
* @return string
|
||||
*/
|
||||
protected function minify( $file_path, $minified_file, $file_content ) {
|
||||
$file_content = $this->rewrite_paths( $file_path, $minified_file, $file_content );
|
||||
$minifier = $this->get_minifier( $file_content );
|
||||
$minified_content = $minifier->minify();
|
||||
|
||||
if ( empty( $minified_content ) ) {
|
||||
Logger::error(
|
||||
'No minified content.',
|
||||
[
|
||||
'css minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
return '';
|
||||
}
|
||||
|
||||
return $minified_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new minifier instance
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $file_content Content to minify.
|
||||
* @return Minifier\CSS
|
||||
*/
|
||||
protected function get_minifier( $file_content ) {
|
||||
return new Minifier\CSS( $file_content );
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\CSS;
|
||||
|
||||
use WP_Rocket\Engine\Optimization\AssetsLocalCache;
|
||||
use WP_Rocket\Engine\Optimization\Minify\AbstractMinifySubscriber;
|
||||
|
||||
/**
|
||||
* Minify/Combine CSS subscriber
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class Subscriber extends AbstractMinifySubscriber {
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
$events = [
|
||||
'rocket_css_url' => [
|
||||
[ 'fix_ssl_minify' ],
|
||||
[ 'i18n_multidomain_url' ],
|
||||
],
|
||||
'rocket_buffer' => [ 'process', 16 ],
|
||||
];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the HTML to Minify/Combine CSS.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
public function process( $html ) {
|
||||
if ( ! $this->is_allowed() ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$assets_local_cache = new AssetsLocalCache( rocket_get_constant( 'WP_ROCKET_MINIFY_CACHE_PATH' ), $this->filesystem );
|
||||
|
||||
if ( $this->options->get( 'minify_css' ) && $this->options->get( 'minify_concatenate_css' ) ) {
|
||||
$this->set_processor_type( new Combine( $this->options, $assets_local_cache ) );
|
||||
} elseif ( $this->options->get( 'minify_css' ) && ! $this->options->get( 'minify_concatenate_css' ) ) {
|
||||
$this->set_processor_type( new Minify( $this->options, $assets_local_cache ) );
|
||||
}
|
||||
|
||||
return $this->processor->optimize( $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is allowed to Minify/Combine CSS.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_allowed() {
|
||||
if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! (bool) $this->options->get( 'minify_css', 0 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! is_rocket_post_excluded_option( 'minify_css' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of CDN zones for CSS files.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_zones() {
|
||||
return [ 'all', 'css_and_js', 'css' ];
|
||||
}
|
||||
}
|
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\JS;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Engine\Optimization\AbstractOptimization;
|
||||
use WP_Rocket\Engine\Optimization\AssetsLocalCache;
|
||||
|
||||
/**
|
||||
* Abstract class for JS optimization
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
abstract class AbstractJSOptimization extends AbstractOptimization {
|
||||
const FILE_TYPE = 'js';
|
||||
|
||||
/**
|
||||
* Assets local cache instance
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var AssetsLocalCache
|
||||
*/
|
||||
protected $local_cache;
|
||||
|
||||
/**
|
||||
* Creates an instance of inheriting class.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param Options_Data $options Options instance.
|
||||
* @param AssetsLocalCache $local_cache Assets local cache instance.
|
||||
*/
|
||||
public function __construct( Options_Data $options, AssetsLocalCache $local_cache ) {
|
||||
$this->options = $options;
|
||||
$this->local_cache = $local_cache;
|
||||
$this->minify_key = $this->options->get( 'minify_js_key', create_rocket_uniqid() );
|
||||
$this->excluded_files = $this->get_excluded_files();
|
||||
$this->init_base_path_and_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all files to exclude from minification/concatenation.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @return string A list of files to exclude, ready to be used in a regex pattern.
|
||||
*/
|
||||
protected function get_excluded_files() {
|
||||
$excluded_files = $this->options->get( 'exclude_js', [] );
|
||||
$excluded_files[] = '/wp-includes/js/dist/i18n.min.js';
|
||||
$jquery_urls = $this->get_jquery_urls();
|
||||
|
||||
if ( ! empty( $jquery_urls ) ) {
|
||||
$excluded_files = array_merge( $excluded_files, $jquery_urls );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter JS files to exclude from minification/concatenation.
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param array $js_files List of excluded JS files.
|
||||
*/
|
||||
$excluded_files = (array) apply_filters( 'rocket_exclude_js', $excluded_files );
|
||||
|
||||
if ( empty( $excluded_files ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach ( $excluded_files as $i => $excluded_file ) {
|
||||
// Escape characters for future use in regex pattern.
|
||||
$excluded_files[ $i ] = str_replace( '#', '\#', $excluded_file );
|
||||
}
|
||||
|
||||
return implode( '|', $excluded_files );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CDN zones.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_zones() {
|
||||
return [ 'all', 'css_and_js', self::FILE_TYPE ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if it is a file excluded from minification.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param array $tag Tag corresponding to a JS file.
|
||||
*
|
||||
* @return bool True if it is a file excluded, false otherwise
|
||||
*/
|
||||
protected function is_minify_excluded_file( array $tag ) {
|
||||
if ( ! isset( $tag[0], $tag['url'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// File should not be minified.
|
||||
if (
|
||||
false !== strpos( $tag[0], 'data-minify=' )
|
||||
||
|
||||
false !== strpos( $tag[0], 'data-no-minify=' )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$file_path = wp_parse_url( $tag['url'], PHP_URL_PATH );
|
||||
|
||||
// File extension is not js.
|
||||
if ( pathinfo( $file_path, PATHINFO_EXTENSION ) !== self::FILE_TYPE ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! empty( $this->excluded_files ) ) {
|
||||
// File is excluded from minification/concatenation.
|
||||
if ( preg_match( '#^(' . $this->excluded_files . ')$#', $file_path ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minify URL.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $filename Minified filename.
|
||||
* @param string $original_url Original URL for this file. Optional.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_minify_url( $filename, $original_url = '' ) {
|
||||
$minify_url = $this->minify_base_url . $filename;
|
||||
|
||||
/**
|
||||
* Filters JS file URL with CDN hostname
|
||||
*
|
||||
* @since 2.1
|
||||
*
|
||||
* @param string $minify_url Minified file URL.
|
||||
* @param string $original_url Original URL for this file.
|
||||
*/
|
||||
return apply_filters( 'rocket_js_url', $minify_url, $original_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets jQuery URL if defer JS safe mode is active.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_jquery_urls() {
|
||||
if ( ! $this->options->get( 'defer_all_js', 0 ) || ! $this->options->get( 'defer_all_js_safe', 0 ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$exclude_jquery = [];
|
||||
$jquery = wp_scripts()->registered['jquery-core']->src;
|
||||
|
||||
if ( isset( $jquery ) ) {
|
||||
if ( empty( wp_parse_url( $jquery, PHP_URL_HOST ) ) ) {
|
||||
$exclude_jquery[] = wp_parse_url( site_url( $jquery ), PHP_URL_PATH );
|
||||
} else {
|
||||
$exclude_jquery[] = $jquery;
|
||||
}
|
||||
}
|
||||
|
||||
$exclude_jquery[] = 'c0.wp.com/c/(?:.+)/wp-includes/js/jquery/jquery.js';
|
||||
$exclude_jquery[] = 'ajax.googleapis.com/ajax/libs/jquery/(?:.+)/jquery(?:\.min)?.js';
|
||||
$exclude_jquery[] = 'cdnjs.cloudflare.com/ajax/libs/jquery/(?:.+)/jquery(?:\.min)?.js';
|
||||
$exclude_jquery[] = 'code.jquery.com/jquery-.*(?:\.min|slim)?.js';
|
||||
|
||||
return $exclude_jquery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patterns in URL excluded from being combined
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_excluded_external_file_path() {
|
||||
$defaults = [
|
||||
'html5.js',
|
||||
'show_ads.js',
|
||||
'histats.com/js',
|
||||
'ws.amazon.com/widgets',
|
||||
'/ads/',
|
||||
'intensedebate.com',
|
||||
'scripts.chitika.net/',
|
||||
'jotform.com/',
|
||||
'gist.github.com',
|
||||
'forms.aweber.com',
|
||||
'video.unrulymedia.com',
|
||||
'stats.wp.com',
|
||||
'stats.wordpress.com',
|
||||
'widget.rafflecopter.com',
|
||||
'widget-prime.rafflecopter.com',
|
||||
'releases.flowplayer.org',
|
||||
'c.ad6media.fr',
|
||||
'cdn.stickyadstv.com',
|
||||
'www.smava.de',
|
||||
'contextual.media.net',
|
||||
'app.getresponse.com',
|
||||
'adserver.reklamstore.com',
|
||||
's0.wp.com',
|
||||
'wprp.zemanta.com',
|
||||
'files.bannersnack.com',
|
||||
'smarticon.geotrust.com',
|
||||
'js.gleam.io',
|
||||
'ir-na.amazon-adsystem.com',
|
||||
'web.ventunotech.com',
|
||||
'verify.authorize.net',
|
||||
'ads.themoneytizer.com',
|
||||
'embed.finanzcheck.de',
|
||||
'imagesrv.adition.com',
|
||||
'js.juicyads.com',
|
||||
'form.jotformeu.com',
|
||||
'speakerdeck.com',
|
||||
'content.jwplatform.com',
|
||||
'ads.investingchannel.com',
|
||||
'app.ecwid.com',
|
||||
'www.industriejobs.de',
|
||||
's.gravatar.com',
|
||||
'googlesyndication.com',
|
||||
'a.optmstr.com',
|
||||
'a.optmnstr.com',
|
||||
'a.opmnstr.com',
|
||||
'adthrive.com',
|
||||
'mediavine.com',
|
||||
'js.hsforms.net',
|
||||
'googleadservices.com',
|
||||
'f.convertkit.com',
|
||||
'recaptcha/api.js',
|
||||
'mailmunch.co',
|
||||
'apps.shareaholic.com',
|
||||
'dsms0mj1bbhn4.cloudfront.net',
|
||||
'nutrifox.com',
|
||||
'code.tidio.co',
|
||||
'www.uplaunch.com',
|
||||
'widget.reviewability.com',
|
||||
'embed-cdn.gettyimages.com/widgets.js',
|
||||
'app.mailerlite.com',
|
||||
'ck.page',
|
||||
'cdn.jsdelivr.net/gh/AmauriC/',
|
||||
'static.klaviyo.com/onsite/js/klaviyo.js',
|
||||
'a.omappapi.com/app/js/api.min.js',
|
||||
'static.zdassets.com',
|
||||
'feedbackcompany.com/widgets/feedback-company-widget.min.js',
|
||||
'widget.gleamjs.io',
|
||||
'phonewagon.com',
|
||||
'simplybook.asia/v2/widget/widget.js',
|
||||
'simplybook.it/v2/widget/widget.js',
|
||||
'simplybook.me/v2/widget/widget.js',
|
||||
'static.botsrv.com/website/js/widget2.36cf1446.js',
|
||||
'static.mailerlite.com/data/',
|
||||
'cdn.voxpow.com',
|
||||
'loader.knack.com',
|
||||
'embed.lpcontent.net/leadboxes/current/embed.js',
|
||||
];
|
||||
|
||||
$excluded_external = array_merge( $defaults, $this->options->get( 'exclude_js', [] ) );
|
||||
|
||||
/**
|
||||
* Filters JS externals files to exclude from the combine process
|
||||
*
|
||||
* @since 2.2
|
||||
*
|
||||
* @param array $pattern Patterns to match.
|
||||
*/
|
||||
return apply_filters( 'rocket_minify_excluded_external_js', $excluded_external );
|
||||
}
|
||||
}
|
@@ -0,0 +1,846 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\JS;
|
||||
|
||||
use WP_Rocket\Dependencies\Minify\JS as MinifyJS;
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Engine\Optimization\AssetsLocalCache;
|
||||
use WP_Rocket\Engine\Optimization\Minify\ProcessorInterface;
|
||||
use WP_Rocket\Logger\Logger;
|
||||
|
||||
/**
|
||||
* Combines JS files
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class Combine extends AbstractJSOptimization implements ProcessorInterface {
|
||||
/**
|
||||
* Minifier instance
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var MinifyJS
|
||||
*/
|
||||
private $minifier;
|
||||
|
||||
/**
|
||||
* JQuery URL
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $jquery_urls;
|
||||
|
||||
/**
|
||||
* Scripts to combine
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $scripts = [];
|
||||
|
||||
/**
|
||||
* Inline scripts excluded from combined and moved after the combined file
|
||||
*
|
||||
* @since 3.1.4
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $move_after = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param Options_Data $options Plugin options instance.
|
||||
* @param MinifyJS $minifier Minifier instance.
|
||||
* @param AssetsLocalCache $local_cache Assets local cache instance.
|
||||
*/
|
||||
public function __construct( Options_Data $options, MinifyJS $minifier, AssetsLocalCache $local_cache ) {
|
||||
parent::__construct( $options, $local_cache );
|
||||
|
||||
$this->minifier = $minifier;
|
||||
$this->jquery_urls = $this->get_jquery_urls();
|
||||
}
|
||||
|
||||
/**
|
||||
* Minifies and combines JavaScripts into one
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
public function optimize( $html ) {
|
||||
Logger::info( 'JS COMBINE PROCESS STARTED.', [ 'js combine process' ] );
|
||||
|
||||
$html_nocomments = $this->hide_comments( $html );
|
||||
$scripts = $this->find( '<script.*<\/script>', $html_nocomments );
|
||||
|
||||
if ( ! $scripts ) {
|
||||
Logger::debug( 'No `<script>` tags found.', [ 'js combine process' ] );
|
||||
return $html;
|
||||
}
|
||||
|
||||
Logger::debug(
|
||||
'Found ' . count( $scripts ) . ' `<script>` tag(s).',
|
||||
[
|
||||
'js combine process',
|
||||
'tags' => $scripts,
|
||||
]
|
||||
);
|
||||
|
||||
$combine_scripts = $this->parse( $scripts );
|
||||
|
||||
if ( empty( $combine_scripts ) ) {
|
||||
Logger::debug( 'No `<script>` tags to optimize.', [ 'js combine process' ] );
|
||||
return $html;
|
||||
}
|
||||
|
||||
Logger::debug(
|
||||
count( $combine_scripts ) . ' `<script>` tag(s) remaining.',
|
||||
[
|
||||
'js combine process',
|
||||
'tags' => $combine_scripts,
|
||||
]
|
||||
);
|
||||
|
||||
$content = $this->get_content();
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
Logger::debug( 'No JS content.', [ 'js combine process' ] );
|
||||
return $html;
|
||||
}
|
||||
|
||||
$minify_url = $this->combine( $content );
|
||||
|
||||
if ( ! $minify_url ) {
|
||||
Logger::error( 'JS combine process failed.', [ 'js combine process' ] );
|
||||
return $html;
|
||||
}
|
||||
|
||||
$move_after = '';
|
||||
|
||||
if ( ! empty( $this->move_after ) ) {
|
||||
foreach ( $this->move_after as $script ) {
|
||||
$move_after .= $script;
|
||||
$html = str_replace( $script, '', $html );
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
|
||||
$html = str_replace( '</body>', '<script src="' . esc_url( $minify_url ) . '" data-minify="1"></script>' . $move_after . '</body>', $html );
|
||||
|
||||
foreach ( $combine_scripts as $script ) {
|
||||
$html = str_replace( $script[0], '', $html );
|
||||
}
|
||||
|
||||
Logger::info(
|
||||
'Combined JS file successfully added.',
|
||||
[
|
||||
'js combine process',
|
||||
'url' => $minify_url,
|
||||
]
|
||||
);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses found nodes to keep only the ones to combine
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param Array $scripts scripts corresponding to JS file or content.
|
||||
* @return array
|
||||
*/
|
||||
protected function parse( $scripts ) {
|
||||
$scripts = array_map(
|
||||
function( $script ) {
|
||||
preg_match( '/<script\s+([^>]+[\s\'"])?src\s*=\s*[\'"]\s*?(?<url>[^\'"]+\.js(?:\?[^\'"]*)?)\s*?[\'"]([^>]+)?\/?>/Umsi', $script[0], $matches );
|
||||
|
||||
if ( isset( $matches['url'] ) ) {
|
||||
if ( $this->is_external_file( $matches['url'] ) ) {
|
||||
foreach ( $this->get_excluded_external_file_path() as $excluded_file ) {
|
||||
if ( false !== strpos( $matches['url'], $excluded_file ) ) {
|
||||
Logger::debug(
|
||||
'Script is external.',
|
||||
[
|
||||
'js combine process',
|
||||
'tag' => $matches[0],
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $this->jquery_urls ) ) {
|
||||
$jquery_urls = implode( '|', $this->jquery_urls );
|
||||
if ( preg_match( '#^(' . $jquery_urls . ')$#', rocket_remove_url_protocol( strtok( $matches['url'], '?' ) ) ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->scripts[] = [
|
||||
'type' => 'url',
|
||||
'content' => $matches['url'],
|
||||
];
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
if ( $this->is_minify_excluded_file( $matches ) ) {
|
||||
Logger::debug(
|
||||
'Script is excluded.',
|
||||
[
|
||||
'js combine process',
|
||||
'tag' => $matches[0],
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$file_path = $this->get_file_path( strtok( $matches['url'], '?' ) );
|
||||
|
||||
if ( ! $file_path ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->scripts[] = [
|
||||
'type' => 'file',
|
||||
'content' => $file_path,
|
||||
];
|
||||
} else {
|
||||
preg_match( '/<script\b(?<attrs>[^>]*)>(?:\/\*\s*<!\[CDATA\[\s*\*\/)?\s*(?<content>[\s\S]*?)\s*(?:\/\*\s*\]\]>\s*\*\/)?<\/script>/msi', $script[0], $matches_inline );
|
||||
|
||||
$matches_inline = array_merge(
|
||||
[
|
||||
'attrs' => '',
|
||||
'content' => '',
|
||||
],
|
||||
$matches_inline
|
||||
);
|
||||
|
||||
if ( preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR ) {
|
||||
Logger::debug(
|
||||
'PCRE regex execution Catastrophic Backtracking',
|
||||
[
|
||||
'inline JS backtracking error',
|
||||
'content' => $matches_inline['content'],
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( strpos( $matches_inline['attrs'], 'type' ) !== false && ! preg_match( '/type\s*=\s*["\']?(?:text|application)\/(?:(?:x\-)?javascript|ecmascript)["\']?/i', $matches_inline['attrs'] ) ) {
|
||||
Logger::debug(
|
||||
'Inline script is not JS.',
|
||||
[
|
||||
'js combine process',
|
||||
'attributes' => $matches_inline['attrs'],
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( false !== strpos( $matches_inline['attrs'], 'src=' ) ) {
|
||||
Logger::debug(
|
||||
'Inline script has a `src` attribute.',
|
||||
[
|
||||
'js combine process',
|
||||
'attributes' => $matches_inline['attrs'],
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( in_array( $matches_inline['content'], $this->get_localized_scripts(), true ) ) {
|
||||
Logger::debug(
|
||||
'Inline script is a localize script',
|
||||
[
|
||||
'js combine process',
|
||||
'excluded_content' => $matches_inline['content'],
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_delayed_script( $matches_inline['attrs'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $this->get_excluded_inline_content() as $excluded_content ) {
|
||||
if ( false !== strpos( $matches_inline['content'], $excluded_content ) ) {
|
||||
Logger::debug(
|
||||
'Inline script has excluded content.',
|
||||
[
|
||||
'js combine process',
|
||||
'excluded_content' => $excluded_content,
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $this->get_move_after_inline_scripts() as $move_after_script ) {
|
||||
if ( false !== strpos( $matches_inline['content'], $move_after_script ) ) {
|
||||
$this->move_after[] = $script[0];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->scripts[] = [
|
||||
'type' => 'inline',
|
||||
'content' => $matches_inline['content'],
|
||||
];
|
||||
}
|
||||
|
||||
return $script;
|
||||
},
|
||||
$scripts
|
||||
);
|
||||
|
||||
return array_filter( $scripts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets content for each script either from inline or from src
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_content() {
|
||||
$content = '';
|
||||
|
||||
foreach ( $this->scripts as $script ) {
|
||||
if ( 'file' === $script['type'] ) {
|
||||
$file_content = $this->get_file_content( $script['content'] );
|
||||
$content .= $file_content;
|
||||
|
||||
$this->add_to_minify( $file_content );
|
||||
} elseif ( 'url' === $script['type'] ) {
|
||||
$file_content = $this->local_cache->get_content( rocket_add_url_protocol( $script['content'] ) );
|
||||
$content .= $file_content;
|
||||
|
||||
$this->add_to_minify( $file_content );
|
||||
} elseif ( 'inline' === $script['type'] ) {
|
||||
$inline_js = rtrim( $script['content'], ";\n\t\r" ) . ';';
|
||||
$content .= $inline_js;
|
||||
|
||||
$this->add_to_minify( $inline_js );
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the minify URL if the minification is successful
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param string $content Content to minify & combine.
|
||||
|
||||
* @return string|bool The minify URL if successful, false otherwise
|
||||
*/
|
||||
protected function combine( $content ) {
|
||||
if ( empty( $content ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filename = md5( $content . $this->minify_key ) . '.js';
|
||||
$minified_file = $this->minify_base_path . $filename;
|
||||
if ( ! rocket_direct_filesystem()->is_readable( $minified_file ) ) {
|
||||
$minified_content = $this->minify();
|
||||
|
||||
if ( ! $minified_content ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$minify_filepath = $this->write_file( $minified_content, $minified_file );
|
||||
|
||||
if ( ! $minify_filepath ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->get_minify_url( $filename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Minifies the content
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @return string|bool Minified content, false if empty
|
||||
*/
|
||||
protected function minify() {
|
||||
$minified_content = $this->minifier->minify();
|
||||
|
||||
if ( empty( $minified_content ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $minified_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds content to the minifier
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $content Content to minify/combine.
|
||||
* @return void
|
||||
*/
|
||||
protected function add_to_minify( $content ) {
|
||||
$this->minifier->add( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Patterns in content excluded from being combined
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_excluded_inline_content() {
|
||||
$defaults = [
|
||||
'document.write',
|
||||
'google_ad',
|
||||
'edToolbar',
|
||||
'gtag',
|
||||
'_gaq.push',
|
||||
'_gaLt',
|
||||
'GoogleAnalyticsObject',
|
||||
'syntaxhighlighter',
|
||||
'adsbygoogle',
|
||||
'ci_cap_',
|
||||
'_stq',
|
||||
'nonce',
|
||||
'post_id',
|
||||
'LogHuman',
|
||||
'idcomments_acct',
|
||||
'ch_client',
|
||||
'sc_online_t',
|
||||
'_stq',
|
||||
'bannersnack_embed',
|
||||
'vtn_player_type',
|
||||
'ven_video_key',
|
||||
'ANS_customer_id',
|
||||
'tdBlock',
|
||||
'tdLocalCache',
|
||||
'wpRestNonce',
|
||||
'"url":',
|
||||
'lazyLoadOptions',
|
||||
'adthrive',
|
||||
'loadCSS',
|
||||
'google_tag_params',
|
||||
'clicky_custom',
|
||||
'clicky_site_ids',
|
||||
'NSLPopupCenter',
|
||||
'_paq',
|
||||
'gtm',
|
||||
'dataLayer',
|
||||
'RecaptchaLoad',
|
||||
'WPCOM_sharing_counts',
|
||||
'jetpack_remote_comment',
|
||||
'subscribe-field',
|
||||
'contextly',
|
||||
'_mmunch',
|
||||
'gt_request_uri',
|
||||
'doGTranslate',
|
||||
'docTitle',
|
||||
'bs_ajax_paginate_',
|
||||
'bs_deferred_loading_',
|
||||
'theChampRedirectionUrl',
|
||||
'theChampFBCommentUrl',
|
||||
'theChampTwitterRedirect',
|
||||
'theChampRegRedirectionUrl',
|
||||
'ESSB_CACHE_URL',
|
||||
'oneall_social_login_providers_',
|
||||
'betterads_screen_width',
|
||||
'woocommerce_wishlist_add_to_wishlist_url',
|
||||
'arf_conditional_logic',
|
||||
'heateorSsHorSharingShortUrl',
|
||||
'TL_Const',
|
||||
'bimber_front_microshare',
|
||||
'setAttribute("id"',
|
||||
'setAttribute( "id"',
|
||||
'TribeEventsPro',
|
||||
'peepsotimedata',
|
||||
'wphc_data',
|
||||
'hc_rand_id',
|
||||
'RBL_ADD',
|
||||
'AfsAnalyticsObject',
|
||||
'_thriveCurrentPost',
|
||||
'esc_login_url',
|
||||
'fwduvpMainPlaylist',
|
||||
'Bibblio.initRelatedContent',
|
||||
'showUFC()',
|
||||
'#iphorm-',
|
||||
'#fancy-',
|
||||
'ult-carousel-',
|
||||
'theChampLJAuthUrl',
|
||||
'f._fbq',
|
||||
'Insticator',
|
||||
'w2dc_js_objects',
|
||||
'cherry_ajax',
|
||||
'ad_block_',
|
||||
'elementorFrontendConfig',
|
||||
'zeen_',
|
||||
'disqusIdentifier',
|
||||
'currentAjaxUrl',
|
||||
'geodir_event_call_calendar_',
|
||||
'atatags-',
|
||||
'hbspt.forms.create',
|
||||
'function(c,h,i,m,p)',
|
||||
'dataTable({',
|
||||
'rankMath = {',
|
||||
'_atrk_opts',
|
||||
'quicklinkOptions',
|
||||
'ct_checkjs_',
|
||||
'WP_Statistics_http',
|
||||
'penci_block_',
|
||||
'omapi_localized',
|
||||
'omapi_data',
|
||||
'OptinMonsterApp',
|
||||
'tminusnow',
|
||||
'nfForms',
|
||||
'galleries.gallery_',
|
||||
'wcj_evt.prodID',
|
||||
'advads_tracking_ads',
|
||||
'advadsGATracking.postContext',
|
||||
'woopack_config',
|
||||
'ulp_content_id',
|
||||
'wp-cumulus/tagcloud.swf?r=',
|
||||
'ctSetCookie(\'ct_checkjs\'',
|
||||
'woof_really_curr_tax',
|
||||
'uLogin.customInit',
|
||||
'i18n_no_matching_variations_text',
|
||||
'alsp_map_markers_attrs',
|
||||
'var inc_opt =',
|
||||
'iworks_upprev',
|
||||
'yith_wcevti_tickets',
|
||||
'window.metrilo.ensure_cbuid',
|
||||
'metrilo.event',
|
||||
'wordpress_page_root',
|
||||
'wcct_info',
|
||||
'Springbot.product_id',
|
||||
'pysWooProductData',
|
||||
'dfd-heading',
|
||||
'owl=$("#',
|
||||
'penci_megamenu',
|
||||
'fts_security',
|
||||
'algoliaAutocomplete',
|
||||
'avia_framework_globals',
|
||||
'tabs.easyResponsiveTabs',
|
||||
'searchlocationHeader',
|
||||
'yithautocomplete',
|
||||
'data-parallax-speed',
|
||||
'currency_data=',
|
||||
'cedexisData',
|
||||
'function reenableButton',
|
||||
'#wpnbio-show',
|
||||
'e.Newsletter2GoTrackingObject',
|
||||
'var categories_',
|
||||
'"+nRemaining+"',
|
||||
'cartsguru_cart_token',
|
||||
'after_share_easyoptin',
|
||||
'location_data.push',
|
||||
'thirstyFunctions.isThirstyLink',
|
||||
'styles: \' #custom-menu-',
|
||||
'function svc_center_',
|
||||
'#svc_carousel2_container_',
|
||||
'advads.move',
|
||||
'elementid',
|
||||
'advads_has_ads',
|
||||
'wpseo_map_init',
|
||||
'mdf_current_page_url',
|
||||
'tptn_tracker',
|
||||
'dpsp_pin_button_data',
|
||||
'searchwp_live_search_params',
|
||||
'wpp_params',
|
||||
'top.location,thispage',
|
||||
'selection+pagelink',
|
||||
'ic_window_resolution',
|
||||
'PHP.wp_p_id',
|
||||
'ShopifyBuy.UI.onReady(client)',
|
||||
'orig_request_uri',
|
||||
'gie.widgets.load',
|
||||
'Adman.Flash',
|
||||
'PHP.wp_p_id',
|
||||
'window.broadstreetKeywords',
|
||||
'var productId =',
|
||||
'var flatsomeVars',
|
||||
'wc_product_block_data',
|
||||
'static.mailerlite.com',
|
||||
'amzn_assoc',
|
||||
'_bs_getParameterByName',
|
||||
'_stq.push',
|
||||
'h._remove',
|
||||
'var FlowFlowOpts',
|
||||
'var WCPFData =',
|
||||
'var _beeketing',
|
||||
'var _statcounter',
|
||||
'var actions =',
|
||||
'var current_url',
|
||||
'var object_name',
|
||||
'var the_ajax_script',
|
||||
'var wc_cart_fragments_params',
|
||||
'var woocommerce_params',
|
||||
'var wpml_cookies',
|
||||
'wc_add_to_cart_params',
|
||||
'window.broadstreetKeywords',
|
||||
'window.wc_ga_pro.available_gateways',
|
||||
'xa.prototype',
|
||||
'HOUZEZ_ajaxcalls_vars',
|
||||
'w2dc_maps_objects',
|
||||
'w2dc_controller_args_array',
|
||||
'w2dc_map_markers_attrs',
|
||||
'YT.Player',
|
||||
'WPFC.data',
|
||||
'function current_video_',
|
||||
'var videodiv',
|
||||
'var slider_wppasrotate',
|
||||
'wppas_ga',
|
||||
'var blockClass',
|
||||
'tarteaucitron',
|
||||
'pw_brand_product_list',
|
||||
'tminusCountDown',
|
||||
'pysWooSelectContentData',
|
||||
'wpvq_ans89733',
|
||||
'_isp_version',
|
||||
'price_range_data',
|
||||
'window.FeedbackCompanyWidgets',
|
||||
'woocs_current_currency',
|
||||
'woo_variation_swatches_options',
|
||||
'woocommerce_price_slider_params',
|
||||
'scriptParams',
|
||||
'form-adv-pagination',
|
||||
'borlabsCookiePrioritize',
|
||||
'urls_wpwidgetpolylang',
|
||||
'quickViewNonce',
|
||||
'frontendscripts_params',
|
||||
'nj-facebook-messenger',
|
||||
'var fb_mess_position',
|
||||
'init_particles_row_background_script',
|
||||
'setREVStartSize',
|
||||
'fl-node',
|
||||
'PPAccordion',
|
||||
'soliloquy_',
|
||||
'wprevpublicjs_script_vars',
|
||||
'DTGS_NONCE_FRONTEND',
|
||||
'et_animation_data',
|
||||
'archives-dropdown',
|
||||
'loftloaderCache',
|
||||
'SmartSliderSimple',
|
||||
'var nectarLove',
|
||||
'var incOpt',
|
||||
'RocketBrowserCompatibilityChecker',
|
||||
'RocketPreloadLinksConfig',
|
||||
'placementVersionId',
|
||||
'var useEdit',
|
||||
'var DTGS_NONCE_FRONTEND',
|
||||
'n2jQuery',
|
||||
'et_core_api_spam_recaptcha',
|
||||
'cnArgs',
|
||||
'__CF$cv$params',
|
||||
'trustbox_settings',
|
||||
'aepro',
|
||||
'cdn.jst.ai',
|
||||
'w2dc_fields_in_categories',
|
||||
'aepc_pixel',
|
||||
'avadaWooCommerceVars',
|
||||
'var isb',
|
||||
'fcaPcPost',
|
||||
'csrf_token',
|
||||
'icwp_wpsf_vars_lpantibot',
|
||||
'wpvViewHead',
|
||||
'ed_school_plugin',
|
||||
'aps_comp_',
|
||||
];
|
||||
|
||||
$excluded_inline = array_merge( $defaults, $this->options->get( 'exclude_inline_js', [] ) );
|
||||
|
||||
/**
|
||||
* Filters inline JS excluded from being combined
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param array $pattern Patterns to match.
|
||||
*/
|
||||
return apply_filters( 'rocket_excluded_inline_js_content', $excluded_inline );
|
||||
}
|
||||
|
||||
/**
|
||||
* Patterns of inline JS to move after the combined JS file
|
||||
*
|
||||
* @since 3.1.4
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_move_after_inline_scripts() {
|
||||
$move_after_scripts = [
|
||||
'map_fusion_map_',
|
||||
'ec:addProduct',
|
||||
'ec:addImpression',
|
||||
'clear_better_facebook_comments',
|
||||
'vc-row-destroy-equal-heights-',
|
||||
'dfd-icon-list-',
|
||||
'SFM_template',
|
||||
'WLTChangeState',
|
||||
'wlt_star_',
|
||||
'wlt_pop_distance_',
|
||||
'smart_list_tip',
|
||||
'gd-wgt-pagi-',
|
||||
'data-rf-id=',
|
||||
'tvc_po=',
|
||||
'scrapeazon',
|
||||
'startclock',
|
||||
'it_logo_field_owl-box_',
|
||||
'td_live_css_uid',
|
||||
'wpvl_paramReplace',
|
||||
'tdAjaxCount',
|
||||
'mec_skin_',
|
||||
'_wca',
|
||||
'_taboola',
|
||||
'fbq(\'trackCustom\'',
|
||||
'fbq(\'track\'',
|
||||
'data.token',
|
||||
'sharrre',
|
||||
'dfads_ajax_load_ads',
|
||||
'tie_postviews',
|
||||
'wmp_update',
|
||||
'h5ab-print-article',
|
||||
'gform_ajax_frame_',
|
||||
'gform_post_render',
|
||||
'mts_view_count',
|
||||
'act_css_tooltip',
|
||||
'window.SLB',
|
||||
'wpt_view_count',
|
||||
'var dateNow',
|
||||
'gallery_product_',
|
||||
'.flo-block-slideshow-',
|
||||
'data=\'api-key=ct-',
|
||||
'ip_common_function()',
|
||||
'("style#gsf-custom-css").append',
|
||||
'a3revWCDynamicGallery_',
|
||||
'#owl-carousel-instagram-',
|
||||
'window.FlowFlowOpts',
|
||||
'jQuery(\'.td_uid_',
|
||||
'jQuery(".slider-',
|
||||
'#dfd-vcard-widget-',
|
||||
'#sf-instagram-widget-',
|
||||
'.woocommerce-tabs-',
|
||||
'penci_megamenu__',
|
||||
'vc_prepareHoverBox',
|
||||
'wp-temp-form-div',
|
||||
'_wswebinarsystem_already_',
|
||||
'#views-extra-css").text',
|
||||
'fusetag.setTargeting',
|
||||
'hit.uptrendsdata.com',
|
||||
'callback:window.renderBadge',
|
||||
'test_run_nf_conditional_logic',
|
||||
'cb_nombre',
|
||||
'$(\'.fl-node-',
|
||||
'function($){google_maps_',
|
||||
'$("#myCarousel',
|
||||
'et_animation_data=',
|
||||
'current_url="',
|
||||
'CustomEvent.prototype=window.Event.prototype',
|
||||
'electro-wc-product-gallery',
|
||||
'woof_is_mobile',
|
||||
'jQuery(\'.videonextup',
|
||||
'wpp_params',
|
||||
'us.templateDirectoryUri=',
|
||||
'.fat-gallery-item',
|
||||
'.ratingbox',
|
||||
'user_rating.prototype.eraseCookie',
|
||||
'test_run_nf_conditional',
|
||||
'dpsp-networks-btns-wrapper',
|
||||
'pa_woo_product_info',
|
||||
'sharing_enabled_on_post_via_metabox',
|
||||
'#product-search-field-',
|
||||
'GOTMLS_login_offset',
|
||||
'berocket_aapf_time_to_fix_products_style',
|
||||
'window.vc_googleMapsPointer',
|
||||
'sinceID_',
|
||||
'#ut-background-video-ut-section',
|
||||
'+window.comment_tab_width+',
|
||||
'dfd-button-hover-in',
|
||||
'wpseo-address-wrapper',
|
||||
'platform.stumbleupon.com',
|
||||
'#woo_pp_ec_button_mini_cart',
|
||||
'#supercarousel',
|
||||
'blockClass',
|
||||
'tdbMenuItem',
|
||||
'tdbSearchItem',
|
||||
'best_seller_badge',
|
||||
'jQuery(\'#product-top-bar',
|
||||
'fb_desc-',
|
||||
'FC_regenerate_captcha',
|
||||
'wp_post_blocks_vars.listed_posts=[',
|
||||
'captcha-hash',
|
||||
'mapdata={',
|
||||
'.ywpc-char-',
|
||||
').countdowntimer(',
|
||||
'jQuery("#td_uid_',
|
||||
'find(\'#td_uid_',
|
||||
];
|
||||
|
||||
/**
|
||||
* Filters inline JS to move after the combined JS file
|
||||
*
|
||||
* @since 3.1.4
|
||||
*
|
||||
* @param array $move_after_scripts Patterns to match.
|
||||
*/
|
||||
return apply_filters( 'rocket_move_after_combine_js', $move_after_scripts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all localized scripts data to exclude them from combine.
|
||||
*
|
||||
* @since 3.1.3
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_localized_scripts() {
|
||||
static $localized_scripts;
|
||||
|
||||
if ( isset( $localized_scripts ) ) {
|
||||
return $localized_scripts;
|
||||
}
|
||||
|
||||
$localized_scripts = [];
|
||||
|
||||
foreach ( array_unique( wp_scripts()->queue ) as $item ) {
|
||||
$data = wp_scripts()->print_extra_script( $item, false );
|
||||
|
||||
if ( empty( $data ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$localized_scripts[] = $data;
|
||||
}
|
||||
|
||||
return $localized_scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this script a delayed script or not.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $script_attributes Attributes beside the opening of script tag.
|
||||
*
|
||||
* @return bool True if it's a delayed script and false if not.
|
||||
*/
|
||||
private function is_delayed_script( $script_attributes ) {
|
||||
return false !== strpos( $script_attributes, 'data-rocketlazyloadscript=' );
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\JS;
|
||||
|
||||
use WP_Rocket\Dependencies\Minify as Minifier;
|
||||
use WP_Rocket\Engine\Optimization\Minify\ProcessorInterface;
|
||||
use WP_Rocket\Logger\Logger;
|
||||
|
||||
/**
|
||||
* Minify JS files
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class Minify extends AbstractJSOptimization implements ProcessorInterface {
|
||||
/**
|
||||
* Minifies JS files
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
public function optimize( $html ) {
|
||||
Logger::info( 'JS MINIFICATION PROCESS STARTED.', [ 'js minification process' ] );
|
||||
|
||||
$scripts = $this->get_scripts( $html );
|
||||
|
||||
if ( empty( $scripts ) ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
foreach ( $scripts as $script ) {
|
||||
global $wp_scripts;
|
||||
|
||||
$is_external_url = $this->is_external_file( $script['url'] );
|
||||
|
||||
if (
|
||||
! $is_external_url
|
||||
&&
|
||||
preg_match( '/[-.]min\.js/iU', $script['url'] )
|
||||
) {
|
||||
Logger::debug(
|
||||
'Script is already minified.',
|
||||
[
|
||||
'js minification process',
|
||||
'tag' => $script[0],
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$is_external_url
|
||||
&&
|
||||
$this->is_excluded_external( $script['url'] )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $this->is_minify_excluded_file( $script ) ) {
|
||||
Logger::debug(
|
||||
'Script is excluded.',
|
||||
[
|
||||
'js minification process',
|
||||
'tag' => $script[0],
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't minify jQuery included in WP core since it's already minified but without .min in the filename.
|
||||
if ( ! empty( $wp_scripts->registered['jquery-core']->src ) && false !== strpos( $script['url'], $wp_scripts->registered['jquery-core']->src ) ) {
|
||||
Logger::debug(
|
||||
'jQuery script is already minified.',
|
||||
[
|
||||
'js minification process',
|
||||
'tag' => $script[0],
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$integrity_validated = $this->local_cache->validate_integrity( $script );
|
||||
|
||||
if ( false === $integrity_validated ) {
|
||||
Logger::debug(
|
||||
'Script integrity attribute not valid.',
|
||||
[
|
||||
'js minification process',
|
||||
'tag' => $script[0],
|
||||
]
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$script['final'] = $integrity_validated;
|
||||
|
||||
$minify_url = $this->replace_url( strtok( $script['url'], '?' ) );
|
||||
|
||||
if ( ! $minify_url ) {
|
||||
Logger::error(
|
||||
'Script minification failed.',
|
||||
[
|
||||
'js minification process',
|
||||
'tag' => $script[0],
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$html = $this->replace_script( $script, $minify_url, $html );
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all script tags from HTML.
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return array Array with script tags, empty array if no script tags found.
|
||||
*/
|
||||
private function get_scripts( $html ) {
|
||||
$html_nocomments = $this->hide_comments( $html );
|
||||
$scripts = $this->find( '<script\s+([^>]+[\s\'"])?src\s*=\s*[\'"]\s*?(?<url>[^\'"]+\.js(?:\?[^\'"]*)?)\s*?[\'"]([^>]+)?\/?>', $html_nocomments );
|
||||
|
||||
if ( ! $scripts ) {
|
||||
Logger::debug( 'No `<script>` tags found.', [ 'js minification process' ] );
|
||||
return [];
|
||||
}
|
||||
|
||||
Logger::debug(
|
||||
'Found ' . count( $scripts ) . ' <link> tags.',
|
||||
[
|
||||
'js minification process',
|
||||
'tags' => $scripts,
|
||||
]
|
||||
);
|
||||
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided external URL is excluded from minify
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $url External URL to check.
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_excluded_external( $url ) {
|
||||
$excluded_externals = $this->get_excluded_external_file_path();
|
||||
$excluded_externals[] = 'google-analytics.com/analytics.js';
|
||||
|
||||
foreach ( $excluded_externals as $excluded ) {
|
||||
if ( false !== strpos( $url, $excluded ) ) {
|
||||
Logger::debug(
|
||||
'Script is external.',
|
||||
[
|
||||
'js combine process',
|
||||
'url' => $url,
|
||||
]
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the minify URL if the minification is successful
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param string $url Original file URL.
|
||||
|
||||
* @return string|bool The minify URL if successful, false otherwise
|
||||
*/
|
||||
protected function replace_url( $url ) {
|
||||
if ( empty( $url ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This filter is documented in /inc/classes/optimization/class-abstract-optimization.php.
|
||||
$url = apply_filters( 'rocket_asset_url', $url, $this->get_zones() );
|
||||
$unique_id = md5( $url . $this->minify_key );
|
||||
$filename = preg_replace( '/\.js$/', '-' . $unique_id . '.js', ltrim( rocket_realpath( wp_parse_url( $url, PHP_URL_PATH ) ), '/' ) );
|
||||
|
||||
$minified_file = rawurldecode( $this->minify_base_path . $filename );
|
||||
$minified_url = $this->get_minify_url( $filename, $url );
|
||||
|
||||
if ( rocket_direct_filesystem()->exists( $minified_file ) ) {
|
||||
Logger::debug(
|
||||
'Minified JS file already exists.',
|
||||
[
|
||||
'js minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
return $minified_url;
|
||||
}
|
||||
|
||||
$is_external_url = $this->is_external_file( $url );
|
||||
$file_path = $is_external_url ? $this->local_cache->get_filepath( $url ) : $this->get_file_path( $url );
|
||||
|
||||
if ( ! $file_path ) {
|
||||
Logger::error(
|
||||
'Couldn’t get the file path from the URL.',
|
||||
[
|
||||
'js minification process',
|
||||
'url' => $url,
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_content = $is_external_url ? $this->local_cache->get_content( rocket_add_url_protocol( $url ) ) : $this->get_file_content( $file_path );
|
||||
|
||||
if ( empty( $file_content ) ) {
|
||||
Logger::error(
|
||||
'No file content.',
|
||||
[
|
||||
'js minification process',
|
||||
'path' => $file_path,
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$minified_content = $this->minify( $file_content );
|
||||
|
||||
if ( empty( $minified_content ) ) {
|
||||
Logger::error(
|
||||
'No minified content.',
|
||||
[
|
||||
'js minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$save_minify_file = $this->save_minify_file( $minified_file, $minified_content );
|
||||
|
||||
if ( ! $save_minify_file ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $minified_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace old script tag with the minified tag.
|
||||
*
|
||||
* @param array $script Script matched data.
|
||||
* @param string $minify_url Minified URL.
|
||||
* @param string $html HTML content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function replace_script( $script, $minify_url, $html ) {
|
||||
$replace_script = str_replace( $script['url'], $minify_url, $script['final'] );
|
||||
$replace_script = str_replace( '<script', '<script data-minify="1"', $replace_script );
|
||||
$html = str_replace( $script[0], $replace_script, $html );
|
||||
|
||||
Logger::info(
|
||||
'Script minification succeeded.',
|
||||
[
|
||||
'js minification process',
|
||||
'url' => $minify_url,
|
||||
]
|
||||
);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save minified JS file.
|
||||
*
|
||||
* @since 3.7
|
||||
*
|
||||
* @param string $minified_file Minified file path.
|
||||
* @param string $minified_content Minified HTML content.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function save_minify_file( $minified_file, $minified_content ) {
|
||||
$save_minify_file = $this->write_file( $minified_content, $minified_file );
|
||||
|
||||
if ( ! $save_minify_file ) {
|
||||
Logger::error(
|
||||
'Minified JS file could not be created.',
|
||||
[
|
||||
'js minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger::debug(
|
||||
'Minified JS file successfully created.',
|
||||
[
|
||||
'js minification process',
|
||||
'path' => $minified_file,
|
||||
]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minifies the content
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param string $file_content Content to minify.
|
||||
* @return string
|
||||
*/
|
||||
protected function minify( $file_content ) {
|
||||
$minifier = $this->get_minifier( $file_content );
|
||||
$minified_content = $minifier->minify();
|
||||
|
||||
if ( empty( $minified_content ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $minified_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new minifier instance
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $file_content Content to minify.
|
||||
* @return Minifier\JS
|
||||
*/
|
||||
protected function get_minifier( $file_content ) {
|
||||
return new Minifier\JS( $file_content );
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization\Minify\JS;
|
||||
|
||||
use WP_Rocket\Dependencies\Minify\JS as MinifyJS;
|
||||
use WP_Rocket\Engine\Optimization\AssetsLocalCache;
|
||||
use WP_Rocket\Engine\Optimization\Minify\AbstractMinifySubscriber;
|
||||
|
||||
/**
|
||||
* Minify/Combine JS subscriber
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class Subscriber extends AbstractMinifySubscriber {
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
$events = [
|
||||
'rocket_js_url' => [
|
||||
[ 'fix_ssl_minify' ],
|
||||
[ 'i18n_multidomain_url' ],
|
||||
],
|
||||
'rocket_buffer' => [ 'process', 22 ],
|
||||
];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the HTML to Minify/Combine JS.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
public function process( $html ) {
|
||||
if ( ! $this->is_allowed() ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$assets_local_cache = new AssetsLocalCache( rocket_get_constant( 'WP_ROCKET_MINIFY_CACHE_PATH' ), $this->filesystem );
|
||||
|
||||
if ( $this->options->get( 'minify_js' ) && $this->options->get( 'minify_concatenate_js' ) ) {
|
||||
$this->set_processor_type( new Combine( $this->options, new MinifyJS(), $assets_local_cache ) );
|
||||
} elseif ( $this->options->get( 'minify_js' ) && ! $this->options->get( 'minify_concatenate_js' ) ) {
|
||||
$this->set_processor_type( new Minify( $this->options, $assets_local_cache ) );
|
||||
}
|
||||
|
||||
return $this->processor->optimize( $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is allowed to Minify/Combine JS.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_allowed() {
|
||||
if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! (bool) $this->options->get( 'minify_js', 0 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! is_rocket_post_excluded_option( 'minify_js' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of CDN zones for JS files.
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_zones() {
|
||||
return [ 'all', 'css_and_js', 'js' ];
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\Optimization\Minify;
|
||||
|
||||
interface ProcessorInterface {
|
||||
/**
|
||||
* Performs the optimization process on the given HTML
|
||||
*
|
||||
* @param string $html HTML content.
|
||||
* @return string
|
||||
*/
|
||||
public function optimize( $html );
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
namespace WP_Rocket\Engine\Optimization;
|
||||
|
||||
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
|
||||
|
||||
/**
|
||||
* Service provider for the WP Rocket optimizations
|
||||
*
|
||||
* @since 3.3
|
||||
* @since 3.6 Renamed and moved into this module.
|
||||
*/
|
||||
class ServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The provides array is a way to let the container
|
||||
* know that a service is provided by this service
|
||||
* provider. Every service that is registered via
|
||||
* this service provider must have an alias added
|
||||
* to this array or it will be ignored.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = [
|
||||
'config',
|
||||
'tests',
|
||||
'buffer_optimization',
|
||||
'buffer_subscriber',
|
||||
'cache_dynamic_resource',
|
||||
'ie_conditionals_subscriber',
|
||||
'optimize_google_fonts',
|
||||
'combine_google_fonts_subscriber',
|
||||
'minify_css_subscriber',
|
||||
'minify_js_subscriber',
|
||||
'dequeue_jquery_migrate_subscriber',
|
||||
'delay_js_html',
|
||||
'delay_js_subscriber',
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the option array in the container
|
||||
*
|
||||
* @since 3.3
|
||||
* @author Remy Perona
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$options = $this->getContainer()->get( 'options' );
|
||||
$filesystem = rocket_direct_filesystem();
|
||||
|
||||
$this->getContainer()->add( 'config', 'WP_Rocket\Buffer\Config' )
|
||||
->withArgument( [ 'config_dir_path' => rocket_get_constant( 'WP_ROCKET_CONFIG_PATH' ) ] );
|
||||
$this->getContainer()->add( 'tests', 'WP_Rocket\Buffer\Tests' )
|
||||
->withArgument( $this->getContainer()->get( 'config' ) );
|
||||
$this->getContainer()->add( 'buffer_optimization', 'WP_Rocket\Buffer\Optimization' )
|
||||
->withArgument( $this->getContainer()->get( 'tests' ) );
|
||||
$this->getContainer()->share( 'buffer_subscriber', 'WP_Rocket\Subscriber\Optimization\Buffer_Subscriber' )
|
||||
->withArgument( $this->getContainer()->get( 'buffer_optimization' ) );
|
||||
$this->getContainer()->share( 'cache_dynamic_resource', 'WP_Rocket\Engine\Optimization\CacheDynamicResource' )
|
||||
->withArgument( $options )
|
||||
->withArgument( WP_ROCKET_CACHE_BUSTING_PATH )
|
||||
->withArgument( WP_ROCKET_CACHE_BUSTING_URL );
|
||||
$this->getContainer()->add( 'optimize_google_fonts', 'WP_Rocket\Engine\Optimization\GoogleFonts\Combine' );
|
||||
$this->getContainer()->share( 'combine_google_fonts_subscriber', 'WP_Rocket\Engine\Optimization\GoogleFonts\Subscriber' )
|
||||
->withArgument( $this->getContainer()->get( 'optimize_google_fonts' ) )
|
||||
->withArgument( $options );
|
||||
$this->getContainer()->share( 'minify_css_subscriber', 'WP_Rocket\Engine\Optimization\Minify\CSS\Subscriber' )
|
||||
->withArgument( $options )
|
||||
->withArgument( $filesystem );
|
||||
$this->getContainer()->share( 'minify_js_subscriber', 'WP_Rocket\Engine\Optimization\Minify\JS\Subscriber' )
|
||||
->withArgument( $options )
|
||||
->withArgument( $filesystem );
|
||||
$this->getContainer()->share( 'dequeue_jquery_migrate_subscriber', 'WP_Rocket\Subscriber\Optimization\Dequeue_JQuery_Migrate_Subscriber' )
|
||||
->withArgument( $options );
|
||||
$this->getContainer()->share( 'ie_conditionals_subscriber', 'WP_Rocket\Engine\Optimization\IEConditionalSubscriber' );
|
||||
|
||||
$this->getContainer()->add( 'delay_js_html', 'WP_Rocket\Engine\Optimization\DelayJS\HTML' )
|
||||
->withArgument( $options );
|
||||
$this->getContainer()->share( 'delay_js_subscriber', 'WP_Rocket\Engine\Optimization\DelayJS\Subscriber' )
|
||||
->withArgument( $this->getContainer()->get( 'delay_js_html' ) )
|
||||
->withArgument( $filesystem );
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user