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

288 lines
6.9 KiB
PHP

<?php
declare( strict_types=1 );
namespace WP_Rocket\Engine\Optimization\DelayJS;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Engine\Optimization\DynamicLists\DataManager;
use WP_Rocket\Engine\Optimization\RegexTrait;
class HTML {
use RegexTrait;
/**
* Plugin options instance.
*
* @var Options_Data
*/
protected $options;
/**
* DataManager instance
*
* @var DataManager
*/
private $data_manager;
/**
* Array of excluded patterns from delay JS
*
* @since 3.9
*
* @var array
*/
protected $excluded = [];
/**
* Allowed type attributes.
*
* @var array Array of allowed type attributes.
*/
private $allowed_types = [
'text/javascript',
'module',
'application/javascript',
'application/ecmascript',
'application/x-ecmascript',
'application/x-javascript',
'text/ecmascript',
'text/javascript1.0',
'text/javascript1.1',
'text/javascript1.2',
'text/javascript1.3',
'text/javascript1.4',
'text/javascript1.5',
'text/jscript',
'text/livescript',
'text/x-ecmascript',
'text/x-javascript',
];
/**
* Creates an instance of HTML.
*
* @param Options_Data $options Plugin options instance.
* @param DataManager $data_manager DataManager instance.
*/
public function __construct( Options_Data $options, DataManager $data_manager ) {
$this->options = $options;
$this->data_manager = $data_manager;
}
/**
* Adjust HTML to have delay js structure.
*
* @since 3.9 Updated to use exclusions list instead of inclusions list.
* @since 3.7
*
* @param string $html Buffer html for the page.
*
* @return string
*/
public function delay_js( $html ): string {
if ( ! $this->is_allowed() ) {
return $html;
}
$this->set_exclusions();
$this->excluded = array_merge( $this->excluded, $this->options->get( 'delay_js_exclusions', [] ) );
/**
* Filters the delay JS exclusions array
*
* @since 3.9
*
* @param array $excluded Array of excluded patterns.
*/
$this->excluded = (array) apply_filters( 'rocket_delay_js_exclusions', $this->excluded );
$this->excluded = array_map(
function ( $value ) {
if ( ! is_string( $value ) ) {
$value = (string) $value;
}
return str_replace(
[ '+', '?ver', '#' ],
[ '\+', '\?ver', '\#' ],
$value
);
},
$this->excluded
);
return $this->parse( $html );
}
/**
* Checks if is allowed to Delay JS.
*
* @since 3.7
*
* @return bool
*/
public function is_allowed(): bool {
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 );
}
/**
* Gets Javascript to redirect IE visitors to the uncached page
*
* @since 3.9
*
* @return string
*/
public function get_ie_fallback(): string {
return 'if(navigator.userAgent.match(/MSIE|Internet Explorer/i)||navigator.userAgent.match(/Trident\/7\..*?rv:11/i)){var href=document.location.href;if(!href.match(/[?&]nowprocket/)){if(href.indexOf("?")==-1){if(href.indexOf("#")==-1){document.location.href=href+"?nowprocket=1"}else{document.location.href=href.replace("#","?nowprocket=1#")}}else{if(href.indexOf("#")==-1){document.location.href=href+"&nowprocket=1"}else{document.location.href=href.replace("#","&nowprocket=1#")}}}}';
}
/**
* Parse the html and add/remove attributes from specific scripts.
*
* @since 3.7
*
* @param string $html Buffer html for the page.
*
* @return string
*/
private function parse( $html ): string {
if ( empty( $html ) ) {
return $html;
}
$result = $this->replace_xmp_tags( $html );
$replaced_html = preg_replace_callback(
'/<\s*script(?<attr>\s*[^>]*?)?>(?<content>.*?)?<\s*\/\s*script\s*>/ims',
[
$this,
'replace_scripts',
],
$result
);
if ( empty( $replaced_html ) ) {
return $html;
}
return $this->restore_xmp_tags( $replaced_html );
}
/**
* Callback method for preg_replace_callback that is used to adjust attributes for scripts.
*
* @since 3.9 Use exclusions list & fake type attribute.
* @since 3.7
*
* @param array $matches Matches array for scripts regex.
*
* @return string
*/
public function replace_scripts( $matches ): string {
foreach ( $this->excluded as $pattern ) {
if ( preg_match( "#{$pattern}#i", $matches[0] ) ) {
return $matches[0];
}
}
if ( empty( $matches['attr'] ) ) {
return '<script type="rocketlazyloadscript">' . $matches['content'] . '</script>';
}
$type_regex = '/type\s*=\s*(["\'])(?<type>.*)\1/i';
preg_match( $type_regex . 'U', $matches['attr'], $type_matches );
if (
! empty( $type_matches )
&&
! empty( trim( $type_matches['type'] ) )
&&
! in_array( trim( $type_matches['type'] ), $this->allowed_types, true )
) {
return $matches[0];
}
$matches['attr'] = preg_replace( $type_regex, 'data-rocket-type=$1$2$1', $matches['attr'] );
// To remove type attribute without any value.
$matches['attr'] = preg_replace( '/(\s+type\s+)|(^type\s+)|(\s+type$)/i', '', $matches['attr'] );
// Checks if script has src attribute so then treat as external script and replace src with data-rocket-src.
$src_regex = '/src\s*=\s*(["\'])(.*)\1/i';
$matches['attr'] = preg_replace( $src_regex, 'data-rocket-src=$1$2$1', $matches['attr'] );
return '<script type="rocketlazyloadscript" ' . trim( $matches['attr'] ) . '>' . $matches['content'] . '</script>';
}
/**
* Move meta charset to head if not found to the top of page content.
*
* @since 3.9.4
*
* @param string $html Html content.
*
* @return string
*/
public function move_meta_charset_to_head( $html ): string {
$meta_pattern = "#<meta[^h]*(http-equiv[^=]*=[^\'\"]*[\'\" ]Content-Type[\'\"][ ]*[^>]*|)(charset[^=]*=[ ]*[\'\" ]*[^\'\"> ][^\'\">]+[^\'\"> ][\'\" ]*|charset[^=]*=*[^\'\"> ][^\'\">]+[^\'\"> ])([^>]*|)>(?=.*</head>)#Usmi";
if ( ! preg_match( $meta_pattern, $html, $matches ) ) {
return $html;
}
$replaced_html = preg_replace( "$meta_pattern", '', $html );
if ( empty( $replaced_html ) ) {
return $html;
}
if ( preg_match( '/<head\b/i', $replaced_html ) ) {
$replaced_html = preg_replace( '/(<head\b[^>]*?>)/i', "\${1}${matches[0]}", $replaced_html, 1 );
if ( empty( $replaced_html ) ) {
return $html;
}
return $replaced_html;
}
if ( preg_match( '/<html\b/i', $replaced_html ) ) {
$replaced_html = preg_replace( '/(<html\b[^>]*?>)/i', "\${1}${matches[0]}", $replaced_html, 1 );
if ( empty( $replaced_html ) ) {
return $html;
}
return $replaced_html;
}
$replaced_html = preg_replace( '/(<\w+)/', "${matches[0]}\${1}", $replaced_html, 1 );
if ( empty( $replaced_html ) ) {
return $html;
}
return $replaced_html;
}
/**
* Get exclusions
*
* @return void
*/
private function set_exclusions() {
$lists = $this->data_manager->get_lists();
$this->excluded = isset( $lists->delay_js_exclusions ) ? $lists->delay_js_exclusions : [];
}
}