File: /home/vslw/public_html/wp-content/plugins/penci-shortcodes/pagespeed/inc/remove-css/remove-css.php
<?php
namespace Soledad\PageSpeed;
use Soledad\PageSpeed\OptimizeCss\Stylesheet;
use Soledad\PageSpeed\RemoveCss\Sanitizer;
/**
* Process stylesheets to soledad_pagespeed and optimize CSS.
*
* @author asadkn
* @modified pencidesign
* @since 1.0.0
*/
class RemoveCss {
/**
* @var \DOMDocument
*/
public $dom;
public $html;
/**
* @var array
*/
public $used_markup = [];
/**
* @var array
*/
protected $stylesheets;
protected $include_sheets = [];
protected $exclude_sheets = [];
/**
* Undocumented function
*
* @param Stylesheet[] $stylesheets
* @param \DOMDocument $dom
* @param string $raw_html
*/
public function __construct( $stylesheets, \DOMDocument $dom, string $raw_html ) {
$this->stylesheets = $stylesheets;
$this->dom = $dom;
$this->html = $raw_html;
}
public function process() {
// Collect all the classes, ids, tags used in DOM.
$this->find_used_selectors();
// Figure out valid sheets.
$this->setup_valid_sheets();
/**
* Process and replace stylesheets with CSS cleaned.
*/
do_action( 'soledad_pagespeed/remove_css_begin', $this );
$allow_selectors = $this->get_allowed_selectors();
foreach ( $this->stylesheets as $sheet ) {
if ( ! $this->should_process_stylesheet( $sheet ) ) {
// Util\debug_log('Skipping: ' . print_r($sheet, true));
continue;
}
$org_url = $sheet->url;
if ( substr( $org_url, 0, 12 ) === "/wp-content/" ) {
$sheet->url = home_url( $org_url );
}
if ( substr( $sheet->url, 0, 2 ) === "//" ) {
$sheet->url = 'https:' . $sheet->url;
}
// Check URL content.
if ( $this->is_external_url( $sheet->url ) ) {
$file_data = $this->fetch_remote_content( $sheet->url );
} else {
$file_data = $this->process_file_by_url( $sheet->url );
}
if ( ! $file_data ) {
continue;
}
$sheet->content = $file_data['content'];
$sheet->file = $file_data['file'];
// Parsed sheet will be cached instead of being parsed by Sabberworm again.
$this->setup_sheet_cache( $sheet );
/**
* Fire up sanitizer to processa and remove unused CSS.
*/
$cache_name = 'soledad_pagespeed_sheet_cache_penci_crcss_' . $sheet->id;
if ( is_multisite() ) {
$cache_name = 'soledad_pagespeed_sheet_cache_site_' . get_current_blog_id() . '_penci_crcss ' . $sheet->id;
}
if ( is_home() ) {
$cache_name = $cache_name . '_home';
}
if ( is_front_page() ) {
$cache_name = $cache_name . '_front';
}
if ( is_page() ) {
$page_specify = [
'page-fullwidth.php',
'elementor_header_footer',
'page-vc.php',
'page-vc-sidebar.php',
'template-custom-all-blog-posts.php',
];
$page_id = get_the_ID();
$custom_cr = get_post_meta( $page_id, 'penci_post_critical_css', true );
$custom_temp = get_post_meta( $page_id, '_wp_page_template', true );
$cache_name = $custom_cr || in_array( $custom_temp, $page_specify ) ? $cache_name . '_page_' . $page_id : $cache_name . '_page_' . get_post_type();
}
if ( is_singular() ) {
$singular_id = get_the_ID();
$custom_cr = get_post_meta( $singular_id, 'penci_post_critical_css', true );
$cache_name = $custom_cr ? $cache_name . '_post_' . get_post_type() . '_' . $singular_id : $cache_name . '_post_' . get_post_type();
}
if ( is_category() ) {
$cat_data = get_queried_object();
$cat_id = $cat_data->term_id;
$cat_custom_data = get_option( "category_$cat_id" );
$cache_name = isset( $cat_custom_data['penci_critical_css'] ) && $cat_custom_data['penci_critical_css'] ? $cache_name . '_cat_' . $cat_data->taxonomy . '_' . $cat_id : $cache_name . '_cat_' . $cat_data->taxonomy;
}
if ( is_tag() ) {
$tag_data = get_queried_object();
$cache_name = $cache_name . '_tag_' . $tag_data->taxonomy;
}
if ( is_tax() ) {
$tax_data = get_queried_object();
$cache_name = $cache_name . '_tax_' . $tax_data->taxonomy;
}
if ( is_search() || is_author() || is_date() || is_day() || is_month() || is_year() ) {
$cache_name = $cache_name . '_archive';
}
$sanitized_css = Plugin::file_cache()->get( $cache_name );
if ( $sanitized_css ) {
$use_cache = true;
$sanitized_css = Plugin::file_system()->get_contents( $sanitized_css );
} else {
$use_cache = false;
$sanitizer = new Sanitizer( $sheet, $this->used_markup, $allow_selectors );
$sanitized_css = $sanitizer->sanitize();
Plugin::file_cache()->set( $cache_name, $sanitized_css );
}
if ( get_theme_mod( 'penci_speed_optimize_gfonts_delay' ) ) {
$sanitized_css = preg_replace( '/@font-face[^{]*{([^{}]|{[^{}]*})*}/', '', $sanitized_css );
}
// Store sizes for debug info.
$sheet->original_size = strlen( $sheet->content );
$sheet->new_size = $sanitized_css ? strlen( $sanitized_css ) : $sheet->original_size;
if ( $sanitized_css ) {
$extra_class = $use_cache ? 'cached' : 'no-cached';
// 'id' is pre-sanitized as it was extracted from HTML.
$replacement = "<style data-penci-cache='{$extra_class}' id='soledad_pagespeed-{$sheet->id}'>" . $sanitized_css . '</style>';
// Add tags for delayed CSS files.
if ( Plugin::delay_load()->should_delay_css() ) {
$sheet->delay_type = get_theme_mod( 'penci_speed_delay_css_type' );
$sheet->set_render( 'delay' );
$sheet->has_delay = true;
// Add the delay load CSS tag in addition to inlined sanitized CSS above.
$replacement .= $sheet->render();
}
$sheet->set_render( 'remove_css', $replacement );
$this->save_sheet_cache( $sheet );
}
$sheet->content = '';
$sheet->parsed_data = '';
}
// $this->stylesheets = array_map(function($sheet) {
// if (isset($sheet->original_size)) {
// $sheet->saved = $sheet->original_size - $sheet->new_size;
// }
// return $sheet;
// }, $this->stylesheets);
return $this->html;
}
/**
* Find all the classes, ids, and tags used in the document.
*
* @return void
*/
protected function find_used_selectors() {
$this->used_markup = [
'tags' => [],
'classes' => [],
'ids' => [],
];
/**
* @var DOMElement $node
*/
$classes = [];
foreach ( $this->dom->getElementsByTagName( '*' ) as $node ) {
$this->used_markup['tags'][ $node->tagName ] = 1;
// Collect tag classes.
if ( $node->hasAttribute( 'class' ) ) {
$class = $node->getAttribute( 'class' );
$ele_classes = preg_split( '/\s+/', $class );
array_push( $classes, ...$ele_classes );
}
if ( $node->hasAttribute( 'id' ) ) {
$this->used_markup['ids'][ $node->getAttribute( 'id' ) ] = 1;
}
}
// Add the classes.
$classes = array_filter( array_unique( $classes ) );
if ( $classes ) {
$this->used_markup['classes'] = array_fill_keys( $classes, 1 );
}
}
/**
* Setup includes and excludes based on options.
*
* @return void
*/
public function setup_valid_sheets() {
$default_excludes = [
'wp-includes/css/dashicons.css',
'admin-bar.css',
'wp-mediaelement',
'id:penci-font-awesome',
'id:penci-font-awesomeold',
'id:penci-font-iweather',
'id:penci_icon',
'id:penci-dark-style',
'id:woocommerce-smallscreen',
];
$excludes = array_merge( $default_excludes, Util\option_to_array( get_theme_mod( 'penci_speed_remove_css_excludes' ) ) );
$this->exclude_sheets = apply_filters( 'soledad_pagespeed/remove_css_excludes', $excludes, $this );
if ( get_theme_mod( 'penci_speed_remove_css' ) ) {
return;
}
$this->include_sheets[] = content_url( 'themes' ) . '/*';
$this->include_sheets[] = content_url( 'plugins' ) . '/*';
$this->include_sheets = apply_filters( 'soledad_pagespeed/remove_css_includes', $this->include_sheets, $this );
}
/**
* Get all the allowed selectors in correct data format to be used by sanitizer.
*
* @return array
*/
public function get_allowed_selectors() {
// Allowed selectors of type 'any': simple match in selector string.
$allowed_any = array_map( function ( $value ) {
if ( ! $value ) {
return '';
}
return [
'type' => 'any',
'search' => [ $value ]
];
}, Util\option_to_array( (string) get_theme_mod( 'penci_speed_allow_css_selectors' ) ) );
return apply_filters( 'soledad_pagespeed/allow_css_selectors', array_filter( $allowed_any ), $this );
}
/**
* Determine if stylesheet should be processed, based on exclusion and inclusion
* rules and settings.
*
* @param Stylesheet $sheet
*
* @return boolean
*/
public function should_process_stylesheet( Stylesheet $sheet ) {
// Handle manual excludes first.
if ( $this->exclude_sheets ) {
foreach ( $this->exclude_sheets as $exclude ) {
if ( Util\asset_match( $exclude, $sheet ) ) {
return false;
}
}
}
foreach ( $this->include_sheets as $include ) {
if ( Util\asset_match( $include, $sheet ) ) {
return true;
}
}
// All stylesheets are valid.
if ( get_theme_mod( 'penci_speed_remove_css' ) ) {
return true;
}
return false;
}
public function is_external_url( $url ): bool {
$external = false;
$assets_domain = $this->get_url_domain( $url );
$site_domain = $this->get_url_domain( home_url() );
if ( $assets_domain !== $site_domain ) {
$external = true;
}
return $external;
}
public function get_url_domain( $url ) {
$pieces = parse_url( $url );
$domain = isset( $pieces['host'] ) && $pieces['host'] ? $pieces['host'] : $pieces['path'];
if ( preg_match( '/(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', $domain, $regs ) ) {
return $regs['domain'];
}
return false;
}
/**
* Get remote asset content and add to asset content.
*
* @return array
*/
public function fetch_remote_content( $url ) {
$file = Plugin::file_cache()->get( $url );
if ( ! $file ) {
$request = wp_remote_get( $url, [
'timeout' => 10,
// For google fonts mainly.
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
] );
if ( is_wp_error( $request ) || empty( $request['body'] ) ) {
return;
}
$content = $request['body'];
} else {
$content = Plugin::file_system()->get_contents( $file );
}
return [
'content' => $content,
'file' => $url
];
}
/**
* Process a stylesheet via URL
*
* @return boolean|array With 'content' and 'file'.
* @uses Plugin::file_system()->url_to_local()
* @uses Plugin::file_system()
*/
public function process_file_by_url( $url ) {
// Try to get local path for this stylesheet
$file = Plugin::file_system()->url_to_local( $url );
if ( ! $file ) {
return false;
}
// We can only support .css files yet
if ( substr( $file, - 4 ) !== '.css' ) {
return false;
}
$content = Plugin::file_system()->get_contents( $file );
if ( ! $content ) {
return false;
}
return [
'content' => $content,
'file' => $file
];
}
/**
* Add parsed data cache to stylesheet object. Will be used by save_sheet_cache later.
*
* @param Stylesheet $sheet
*
* @return void
*/
public function setup_sheet_cache( Stylesheet $sheet ) {
if ( ! isset( $sheet->file ) ) {
return;
}
$cache = get_transient( $this->get_transient_id( $sheet ) );
if ( $cache && $cache['mtime'] < Plugin::file_system()->mtime( $sheet->file ) ) {
return;
}
if ( $cache && ! empty( $cache['data'] ) ) {
$sheet->parsed_data = $cache['data'];
$sheet->has_cache = true;
return;
}
}
protected function get_transient_id( $sheet ) {
return substr( 'soledad_pagespeed_sheet_cache_' . $sheet->id, 0, 190 );
}
/**
* Cache the parsed data.
*
* Note: This doesn't cache whole CSS as that would vary based on found selectors.
*
* @param Stylesheet $sheet
*
* @return void
*/
public function save_sheet_cache( Stylesheet $sheet ) {
if ( $sheet->has_cache ) {
return;
}
$cache_data = [
'data' => $sheet->parsed_data,
'mtime' => Plugin::file_system()->mtime( $sheet->file )
];
// With expiry; won't be auto-loaded.
set_transient( $this->get_transient_id( $sheet ), $cache_data, MONTH_IN_SECONDS );
}
}