rewrite_manager = $rewrite_manager;
$this->integration_manager = $integration_manager;
$this->lazyload_manager = $lazyload_manager;
$this->request = $request;
$this->cache_manager = $cache_manager;
$this->variable_document = $variable_document;
$this->script_document = $script_document;
$this->node_map = new \SplObjectStorage();
$this->document = $document;
$this->util = $util;
parent::__construct();
}
/**
* @return \Rocket\Footer\JS\Rewrite\Manager
*/
public function get_rewrite_manager() {
return $this->rewrite_manager;
}
/**
* @return \Rocket\Footer\JS\Integration\Manager
*/
public function get_integration_manager() {
return $this->integration_manager;
}
/**
* @return \Rocket\Footer\JS\Lazyload\Manager
*/
public function get_lazyload_manager() {
return $this->lazyload_manager;
}
/**
* @return \Rocket\Footer\JS\Request
*/
public function get_request() {
return $this->request;
}
/**
*
*/
public function init() {
if ( ! $this->get_dependancies_exist() ) {
return;
}
//Get home URL
$this->home = set_url_scheme( home_url() );
// Get our domain
$this->domain = parse_url( $this->home, PHP_URL_HOST );
parent::init();
}
/**
* @return bool
*/
protected function get_dependancies_exist() {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
$error = false;
if ( validate_plugin( 'wp-rocket/wp-rocket.php' ) ) {
$error = true;
add_action( 'admin_notices', [ $this, 'activate_error_no_wprocket' ] );
} else if ( ! is_plugin_active( 'wp-rocket/wp-rocket.php' ) ) {
$error = true;
add_action( 'admin_notices', [ $this, 'activate_error_wprocket_inactive' ] );
}
if ( ! class_exists( 'DOMDocument' ) ) {
$error = true;
add_action( 'admin_notices', [ $this, 'activate_error_no_domdocument' ] );
}
if ( ! did_action( 'wp_rocket_loaded' ) ) {
$error = true;
}
return ! $error;
}
/**
* @return \Rocket\Footer\JS\Cache\Manager
*/
public function get_cache_manager() {
return $this->cache_manager;
}
/**
* @param $buffer
*
* @return mixed
*/
public function process_buffer( $buffer ) {
$this->disable_option_overrides();
/** @noinspection NotOptimalIfConditionsInspection */
if ( get_rocket_option( 'minify_js' ) && ! ( defined( 'DONOTMINIFYJS' ) && DONOTMINIFYJS ) && ! is_rocket_post_excluded_option( 'minify_js' ) ) {
/** @noinspection UsageOfSilenceOperatorInspection */
if ( ! @$this->document->loadHTML( $buffer ) ) {
return $buffer;
}
$this->body = $this->document->getElementsByTagName( 'body' )->item( 0 );
$this->normalize_cdn_domains();
do_action( 'rocket_footer_js_do_rewrites' );
do_action( 'rocket_footer_js_do_lazyload' );
$this->find_localized_scripts();
$this->build_script_list();
$this->fetch_cache();
$filename = '';
if ( empty( $this->cache ) ) {
$filename = $this->get_cache_filename();
}
$this->cleanup_nodes();
$this->process_scripts();
$this->build_inline_scripts();
extract( $this->write_cache( $filename ), EXTR_OVERWRITE );
/** @var string $src */
$this->add_main_script( $src );
$this->fix_old_libxml();
//Get HTML
$buffer = $this->document->saveHTML();
$buffer = $this->do_minify_html( $buffer );
}
return $buffer;
}
/**
*
*/
protected function disable_option_overrides() {
remove_filter( 'pre_get_rocket_option_minify_js', '__return_zero' );
remove_filter( 'pre_get_rocket_option_minify_html', '__return_zero' );
remove_filter( 'pre_get_rocket_option_lazyload_iframes', '__return_zero' );
remove_filter( 'pre_get_rocket_option_lazyload', '__return_zero' );
}
/**
*
*/
protected function normalize_cdn_domains() {
// Remote fetch external scripts
$this->cdn_domains = get_rocket_cdn_cnames( [
'all',
'css',
'js',
'css_and_js',
'images',
] );
// Get the hostname for each CDN CNAME
foreach ( array_keys( (array) $this->cdn_domains ) as $index ) {
$cdn_domain = &$this->cdn_domains[ $index ];
$cdn_domain_parts = parse_url( $cdn_domain );
if ( empty( $cdn_domain_parts['host'] ) ) {
$cdn_domain = "//{$cdn_domain}";
$cdn_domain = set_url_scheme( $cdn_domain );
$cdn_domain_parts = parse_url( $cdn_domain );
}
$cdn_domain = $cdn_domain_parts['host'];
}
// Cleanup
unset( $cdn_domain_parts, $cdn_domain );
}
/**
* Get all localized scripts
*
* @return void
*/
protected function find_localized_scripts() {
foreach ( array_unique( wp_scripts()->queue ) as $item ) {
$data = wp_scripts()->print_extra_script( $item, false );
if ( ! empty( $data ) ) {
$this->enqueued_variable_tags[] = '/* */';
}
}
}
/**
*
*/
protected function build_script_list() {
foreach ( $this->document->getElementsByTagName( 'script' ) as $tag ) {
/** @var DOMElement $tag */
if ( '1' == $tag->getAttribute( 'data-no-minify' ) || in_array( $tag->getAttribute( 'type' ), apply_filters( 'rocket_footer_js_exclude_tag_types', [
'x-tmpl-mustache',
'text/x-handlebars-template',
'text/template',
'text/html',
'text/css',
] ), true )
) {
continue;
}
if ( in_array( str_replace( "\n", '', $tag->textContent ), $this->enqueued_variable_tags ) ) {
$this->variable_document->appendChild( $tag );
} else {
// Skip ld+json and leave it in the header
if ( 'application/ld+json' !== $tag->getAttribute( 'type' ) ) {
$this->script_document->appendChild( $tag );
$src = $tag->getAttribute( 'src' );
if ( ! empty( $src ) ) {
if ( $this->is_file_excluded( $src ) ) {
continue;
}
$this->cache_list['external'][] = $src;
} else if ( ! empty( $tag->textContent ) ) {
$this->cache_list['inline'][] = $tag->textContent;
}
}
}
}
}
private function is_file_excluded( $file ) {
$found = preg_match( '#^(' . $this->get_excluded_files() . ')$#', $file );
return ! empty( $found );
}
private function get_excluded_files() {
if ( ! $this->excluded_file_regex ) {
$excluded_files = get_rocket_option( 'exclude_js' );
/**
* Filter JS files to exclude from minification/concatenation.
*
* @param array $js_files List of excluded JS files.
*
* @since 2.6
*
*/
$excluded_files = 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 );
}
$this->excluded_file_regex = implode( '|', $excluded_files );
}
return $this->excluded_file_regex;
}
/**
*
*/
protected function fetch_cache() {
if ( ! apply_filters( 'rocket_footer_js_save_cache', true ) ) {
$this->cache = false;
return;
}
$this->cache = $this->get_cache_fragment( $this->get_cache_id() );
// Cached file is gone, we dont have cache
if ( ! empty( $this->cache ) && ! file_exists( $this->cache ['filename'] ) ) {
$this->cache = false;
}
}
private function get_cache_fragment( $cache_id ) {
if ( apply_filters( 'rocket_footer_js_save_cache', true ) ) {
return $this->cache_manager->get_store()->get_cache_fragment( $cache_id );
}
return false;
}
/**
* @return array
*/
protected function get_cache_id() {
$post_cache_id_hash = $this->get_cache_hash();
$post_cache_id = array();
if ( is_singular() ) {
$post_cache_id [] = 'post_' . get_the_ID();
} else if ( is_tag() || is_category() || is_tax() ) {
$post_cache_id [] = 'tax_' . get_queried_object()->term_id;
} else if ( is_author() ) {
$post_cache_id [] = 'author_' . get_the_author_meta( 'ID' );
} else {
$post_cache_id [] = 'generic';
}
$post_cache_id [] = $post_cache_id_hash;
if ( is_user_logged_in() ) {
$post_cache_id [] = wp_get_current_user()->roles[0];
}
return apply_filters( 'rocket_footer_js_get_cache_id', $post_cache_id );
}
/**
* @return string
*/
protected function get_cache_hash() {
if ( null === $this->cache_hash ) {
$this->cache_hash = md5( serialize( $this->cache_list ) );
}
return $this->cache_hash;
}
/**
* @return string
*/
protected function get_cache_filename() {
$js_key = get_rocket_option( 'minify_js_key' );
$cache_path = $this->get_cache_path();
// If we have a user logged in, include user role in filename to be unique as we may have user only JS content. Otherwise file will be a hash of (minify-global-[js_key]-[content_hash]).js
if ( is_user_logged_in() ) {
$filename = $cache_path . md5( 'minify-' . wp_get_current_user()->roles[0] . '-' . $js_key . '-' . $this->get_cache_hash() ) . '.js';
} else {
$filename = $cache_path . md5( 'minify-global' . '-' . $js_key . '-' . $this->get_cache_hash() ) . '.js';
}
// Create post_cache dir if needed
if ( ! is_dir( $cache_path ) ) {
rocket_mkdir_p( $cache_path );
}
return $filename;
}
/**
* @return string
*/
public function get_cache_path() {
return WP_ROCKET_MINIFY_CACHE_PATH . get_current_blog_id() . '/';
}
/**
*
*/
protected function cleanup_nodes() {
/** @var DOMElement $tag */
// Remove all elements from DOM
foreach (
array_merge(
iterator_to_array( $this->script_document->get_script_tags() ),
iterator_to_array( $this->variable_document->get_script_tags() )
) as $tag
) {
if ( ! empty( $this->node_map[ $tag ] ) ) {
$this->node_map[ $tag ]->remove();
}
}
}
/**
*
*/
protected function process_scripts() {
$this->dom_collection = new DOMCollection( $this->script_document, 'script' );
while ( $this->dom_collection->valid() ) {
$this->process_script();
$this->dom_collection->next();
}
}
/**
*
*/
protected function process_script() {
// If the last character is not a semicolon, and we have content,add one to prevent syntax errors
if ( 0 < strlen( $this->js ) && ! in_array( $this->js[ strlen( $this->js ) - 1 ], [ ';', "\n" ] ) ) {
$this->js .= ";\n";
}
//Decode html entities
$src = $this->dom_collection->current()->getAttribute( 'src' );
if ( ! empty( $src ) ) {
$this->process_external_script();
return;
}
$this->process_inline_script();
}
/**
*
*/
protected function process_external_script() {
$src = $this->dom_collection->current()->getAttribute( 'src' );
$src = html_entity_decode( preg_replace( '/((?cache ) ) {
if ( 0 === strpos( $src, '//' ) ) {
//Handle no protocol urls
$src = rocket_add_url_protocol( $src );
}
//Has it been processed before?
if ( ! in_array( $src, $this->urls ) ) {
// Get host of tag source
$src_host = parse_url( $src, PHP_URL_HOST );
// Being remote is defined as not having our home url and not being in the CDN list. However if the file does not have a JS extension, assume its a dynamic script generating JS, so we need to web fetch it.
if ( 0 != strpos( $src, '/' ) && ( ( $src_host != $this->domain && ! in_array( $src_host, $this->cdn_domains ) ) || 'js' !== pathinfo( parse_url( $src, PHP_URL_PATH ), PATHINFO_EXTENSION ) ) ) {
$this->process_remote_script( $src );
$this->urls[] = $src;
return;
}
$this->process_local_script( $src );
$this->urls[] = $src;
}
}
}
/**
* @param string $src
*
* @internal param DOMElement $tag
*/
protected function process_remote_script( $src ) {
// Check item cache
$item_cache_id = [ md5( $src ) ];
$item_cache = $this->get_cache_fragment( $item_cache_id );
// Only run if there is no item cache
if ( empty( $item_cache ) ) {
$file = $this->remote_fetch( $src );
// Catch Error
if ( ! empty( $file ) ) {
$js_part_cache = apply_filters( 'rocket_footer_js_process_remote_script', $file, $src );
$js_part = $this->minify( $js_part_cache );
if ( $js_part_cache != $file && apply_filters( 'rocket_footer_js_reprocess_remote_script', true, $file, $src ) ) {
$js_part_cache = $file;
$js_part_cache = $this->minify( $js_part_cache );
} else {
$js_part_cache = $js_part;
}
$this->update_cache_fragment( $item_cache_id, $js_part_cache );
$this->js .= $js_part;
}
} else {
$js_part = $item_cache;
if ( apply_filters( 'rocket_footer_js_reprocess_remote_script', true, $js_part, $src ) ) {
$js_part = apply_filters( 'rocket_footer_js_process_remote_script', $js_part, $src );
}
$this->js .= $js_part;
}
}
/**
* @param $url
*
* @return bool|string
*/
public function remote_fetch( $url, $method = 'get', $args = [] ) {
$func = "wp_remote_{$method}";
if ( ! function_exists( $func ) ) {
return false;
}
$file = $func( $url, array_merge_recursive( [
'user-agent' => 'WP-Rocket',
'sslverify' => false,
], $args ) );
if ( ! ( $file instanceof \WP_Error || ( is_array( $file ) && ( empty( $file['response']['code'] ) || ! in_array( $file['response']['code'], array(
200,
304,
) ) ) )
)
) {
return $file['body'];
}
return false;
}
/**
* @param string $script
*
* @return string
*/
protected function minify( $script ) {
$closure_url = 'https://closure-compiler.appspot.com/compile';
$run_closure = ( defined( "ROCKET_FOOTER_JS_ENABLE_CLOSURE_COMPILER" ) && ROCKET_FOOTER_JS_ENABLE_CLOSURE_COMPILER );
$closure_error = ! $run_closure;
$result = null;
if ( $run_closure ) {
$args = [
'body' => [
'js_code' => $script,
'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
'output_info' => 'errors',
'output_format' => 'json',
],
];
$result = $this->remote_fetch( $closure_url, 'post', $args );
if ( empty( $result ) ) {
$closure_error = true;
}
if ( ! empty( $result ) ) {
$json = json_decode( $result );
if ( ! empty( $json->errors ) ) {
$closure_error = true;
}
if ( empty( $json->errors ) ) {
$script = $json->compiledCode;
}
}
}
if ( ! $closure_error ) {
return $script;
}
if ( $run_closure ) {
$closure_error = false;
$args['body']['compilation_level'] = 'WHITESPACE_ONLY';
$result = $this->remote_fetch( $closure_url, 'post', $args );
if ( empty( $result ) ) {
$closure_error = true;
}
if ( ! empty( $result ) ) {
$json = json_decode( $result );
if ( ! empty( $json->errors ) ) {
$closure_error = true;
}
if ( empty( $json->errors ) ) {
$script = $json->compiledCode;
}
}
if ( ! $closure_error ) {
return $script;
}
}
$script = preg_replace( '~(?(?![\'"/])~Us', '', $script );
if ( class_exists( '\MatthiasMullie\Minify\JS' ) ) {
$minify = new \MatthiasMullie\Minify\JS( $script );
$script = $minify->minify();
} else {
$script = rocket_minify_inline_js( $script );
}
$script = preg_replace( '~/\*!?\s+.*\*/~sU', '', $script );
return $script;
}
private function update_cache_fragment( $cache_id, $data ) {
if ( apply_filters( 'rocket_footer_js_save_cache', true ) ) {
$this->cache_manager->get_store()->update_cache_fragment( $cache_id, $data );
}
}
/**
* @param string $src
*
*/
protected function process_local_script( $src ) {
if ( 0 == strpos( $src, '/' ) ) {
$src = $this->home . $src;
}
// Remove query strings
$src_file = $src;
if ( false !== strpos( $src, '?' ) ) {
$src_file = substr( $src, 0, strpos( $src, strrchr( $src, '?' ) ) );
}
// Break up url
$url_parts = parse_url( $src_file );
$url_parts['host'] = $this->domain;
$url_parts['scheme'] = is_ssl() ? 'https' : 'http';
/*
* Check and see what version of php-http we have.
* 1.x uses procedural functions.
* 2.x uses OOP classes with a http namespace.
* Convert the address to a path, minify, and add to buffer.
*/
if ( class_exists( 'http\Url' ) ) {
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
$url = new \http\Url( $url_parts );
$url = $url->toString();
} else {
$url = http_build_url( $url_parts );
}
// Check item cache
$item_cache_id = [ md5( $url ) ];
$item_cache = $this->get_cache_fragment( $item_cache_id );
// Only run if there is no item cache
if ( empty( $item_cache ) ) {
$file = $this->get_content( str_replace( $this->home, ABSPATH, $url ) );
$js_part_cache = apply_filters( 'rocket_footer_js_process_local_script', $file, $url );
$js_part = $js_part_cache;
$js_part = $this->minify( $js_part );
if ( $js_part_cache != $file && apply_filters( 'rocket_footer_js_reprocess_local_script', true, $file, $src ) ) {
$js_part_cache = $file;
$js_part_cache = $this->minify( $js_part_cache );
} else {
$js_part_cache = $js_part;
}
if ( strpos( $js_part, 'sourceMappingURL' ) !== false ) {
$js_part .= "\n";
} else {
$js_part = trim( $js_part );
}
$this->js .= $js_part;
$this->update_cache_fragment( $item_cache_id, $js_part_cache );
} else {
$js_part = $item_cache;
if ( apply_filters( 'rocket_footer_js_reprocess_local_script', true, $js_part, $src ) ) {
$js_part = apply_filters( 'rocket_footer_js_process_local_script', $js_part, $url );
}
$this->js .= $js_part;
}
}
/**
* @param $file
*
* @return bool|string
*/
public function get_content( $file ) {
return $this->get_wp_filesystem()->get_contents( $file );
}
/**
*
*/
protected function process_inline_script() {
// Check item cache
$content = $this->util->maybe_decode_script( $this->dom_collection->current()->textContent );
$item_cache_id = [ md5( $content ) ];
$item_cache = $this->get_cache_fragment( $item_cache_id );
// Only run if there is no item cache
if ( empty( $item_cache ) ) {
// Remove any conditional comments for IE that somehow was put in the script tag
$js_part = preg_replace( '/(?:/is', '', $content );
$js_part = $this->minify( $js_part );
$this->update_cache_fragment( $item_cache_id, $js_part );
} else {
$js_part = $item_cache;
}
//Add inline JS to buffer
$this->js .= $js_part;
$this->dom_collection->remove();
}
/**
*
*/
protected function build_inline_scripts() {
$inline_js = '';
//Combine all inline tags to one
foreach (
array_merge(
iterator_to_array( $this->variable_document->get_script_tags() ),
iterator_to_array( $this->script_document->get_script_tags() )
)
as $tag
) {
// If the last character is not a semicolon, and we have content,add one to prevent syntax errors
if ( 0 < strlen( $inline_js ) && ';' !== $inline_js[ strlen( $inline_js ) - 1 ] ) {
$inline_js .= ';';
}
// Remove any conditional comments for IE that somehow was put in the script tag
$inline_js .= preg_replace( '/(?:/is', '', $tag->textContent );
}
if ( ! empty( $inline_js ) ) {
$inline_js = $this->minify( $inline_js );
$this->insert_inline_script( $inline_js );
}
}
/**
* @param $script
*/
protected function insert_inline_script( $script ) {
//Create script tag
$inline_tag = $this->document->createElement( 'script', $script );
$inline_tag->setAttribute( 'type', 'text/javascript' );
// Add element to footer
$this->body->appendChild( $inline_tag );
}
/**
* @param $filename
*
* @return array|string
*/
protected function write_cache( $filename ) {
if ( empty( $this->cache ) ) {
$data = [ 'filename' => $filename ];
$this->put_content( $filename, $this->js );
$data['src'] = get_rocket_cdn_url( set_url_scheme( str_replace( WP_CONTENT_DIR, WP_CONTENT_URL, $filename ) ), [
'all',
'css',
'js',
'css_and_js',
] );
$this->update_cache_fragment( $this->get_cache_id(), $data );
return $data;
}
return $this->cache;
}
/**
* @param $file
* @param $data
*
* @return bool
*/
public function put_content( $file, $data ) {
return $this->get_wp_filesystem()->put_contents( $file, $data );
}
/**
* @param $src
*/
protected function add_main_script( $src ) {
// Create script element
$external_tag = $this->document->createElement( 'script' );
$external_tag->setAttribute( 'type', 'text/javascript' );
$external_tag->setAttribute( 'src', $src );
$external_tag->setAttribute( 'data-minify', '1' );
$external_tag->setAttribute( 'async', 'async' );
$external_tag->setAttribute( 'crossorigin', 'anonymous' );
// Add element to footer
$this->body->appendChild( $external_tag );
}
/**
*
*/
protected function fix_old_libxml() {
// Hack to fix a bug with some libxml versions
$body = $this->document->getElementsByTagName( 'body' )->item( 0 );
$body_class = $body->getAttribute( 'class' );
if ( empty( $body_class ) ) {
$body->setAttribute( 'class', implode( ' ', get_body_class() ) );
}
}
/**
* @param $buffer
*
* @return mixed|string
*/
protected function do_minify_html( $buffer ) {
remove_filter( 'pre_get_rocket_option_minify_html', '__return_zero' );
// If HTML minify is on, process it
if ( get_rocket_option( 'minify_html' ) && ! is_rocket_post_excluded_option( 'minify_html' ) ) {
$buffer = rocket_minify_html( $buffer );
$buffer = preg_replace_callback( '~
Opps! %s requires WP-Rocket! Please Download at www.wp-rocket.me
Opps! %s requires WP-Rocket! Please Enable the plugin here
Opps! %s requires PHP XML extension! Please contact your web host or system administrator to get this installed.