add wp-rocket

This commit is contained in:
nguyen dung
2022-02-18 19:09:35 +07:00
parent 39b8cb3612
commit 3110d00ee7
927 changed files with 271703 additions and 2 deletions

View File

@@ -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
}
}

View File

@@ -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;
}
}

View File

@@ -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 ]
);
}
}

View File

@@ -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();
}
}

View File

@@ -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(
'Couldnt 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 );
}
}

View File

@@ -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' ];
}
}

View File

@@ -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 );
}
}

View File

@@ -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=' );
}
}

View File

@@ -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(
'Couldnt 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 );
}
}

View File

@@ -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' ];
}
}

View File

@@ -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 );
}