} //get excluded selectors private static function get_excluded_selectors() { self::$excluded_selectors = array( '.wp-embed-responsive', //core '.wp-block-embed', '.wp-block-embed__wrapper', '.wp-caption', '#elementor-device-mode', //elementor '.elementor-nav-menu', '.elementor-has-item-ratio', '.elementor-popup-modal', '.elementor-sticky--active', '.dialog-type-lightbox', '.dialog-widget-content', '.lazyloaded', '.elementor-motion-effects-container', '.elementor-motion-effects-layer', '.ast-header-break-point', //astra '.dropdown-nav-special-toggle', //kadence 'rs-fw-forcer', //rev slider 'div.product' //woocommerce ); if(!empty(Config::$options['assets']['rucss_excluded_selectors'])) { self::$excluded_selectors = array_merge(self::$excluded_selectors, Config::$options['assets']['rucss_excluded_selectors']); } self::$excluded_selectors = apply_filters('perfmatters_rucss_excluded_selectors', self::$excluded_selectors); } //remove unusde css from stylesheet private static function clean_stylesheet($url, $css) { //https://github.com/sabberworm/PHP-CSS-Parser/issues/150 $css = preg_replace('/^\xEF\xBB\xBF/', '', $css); //setup css parser $settings = Settings::create()->withMultibyteSupport(false); $parser = new CSSParser($css, $settings); $parsed_css = $parser->parse(); //convert relative urls to full urls self::fix_relative_urls($url, $parsed_css); $css_data = self::prep_css_data($parsed_css); return self::remove_unused_selectors($css_data); } //convert relative urls to full urls private static function fix_relative_urls($stylesheet_url, Document $data) { //get base url from stylesheet $base_url = preg_replace('#[^/]+(\?.*)?$#', '', $stylesheet_url); //search css for urls $values = $data->getAllValues(); foreach($values as $value) { if(!($value instanceof URL)) { continue; } $url = $value->getURL()->getString(); //not relative if(preg_match('/^(https?|data):/', $url)) { continue; } $parsed_url = parse_url($url); //final checks if(!empty($parsed_url['host']) || empty($parsed_url['path']) || $parsed_url['path'][0] === '/') { continue; } //create full url and replace $new_url = $base_url . $url; $value->getUrl()->setString($new_url); } } //prep parsed css for cleaning private static function prep_css_data(CSSBlockList $data) { $items = array(); foreach($data->getContents() as $content) { //remove charset objects since were printing inline if($content instanceof Charset) { continue; } if($content instanceof AtRuleBlockList) { $items[] = array( 'rulesets' => self::prep_css_data($content), 'at_rule' => "@{$content->atRuleName()} {$content->atRuleArgs()}", ); } else { $item = array('css' => $content->render(OutputFormat::createCompact())); if($content instanceof DeclarationBlock) { $item['selectors'] = self::sort_selectors($content->getSelectors()); } $items[] = $item; } } return $items; } //sort selectors into different categories we need private static function sort_selectors($selectors) { $selectors = array_map( function($sel) { return $sel->__toString(); }, $selectors ); $selectors_data = array(); foreach($selectors as $selector) { //setup selector data array $data = array( 'selector' => trim($selector), 'classes' => array(), 'ids' => array(), 'tags' => array(), 'atts' => array() ); //eliminate false negatives (:not(), pseudo, etc...) $selector = preg_replace('/(? $value) { if(preg_match('#(' . preg_quote($value) . ')(?=\s|\.|\:|,|\[|$)#', $selector['selector'])) { return true; } } } //is selector used in the dom foreach(array('classes', 'ids', 'tags') as $type) { if(!empty($selector[$type])) { //cast array if needed $targets = (array)$selector[$type]; foreach($targets as $target) { //bail if a target doesn't exist if(!isset(self::$used_selectors[$type][$target])) { return false; } } } } return true; } //delete all files in the css cache directory public static function clear_used_css($site = null) { $path = ''; //add site path if specified if(is_object($site) && !empty($site->path)) { $path = ltrim($site->path, '/'); } $files = glob(PERFMATTERS_CACHE_DIR . $path . 'css/*'); foreach($files as $file) { if(is_file($file)) { unlink($file); } } delete_option('perfmatters_used_css_time'); } //clear used css file for specific post or post type public static function clear_post_used_css() { if(empty($_POST['action']) || empty($_POST['nonce']) || empty($_POST['post_id'])) { return; } if($_POST['action'] != 'perfmatters_clear_post_used_css') { return; } if(!wp_verify_nonce($_POST['nonce'], 'perfmatters_clear_post_used_css')) { return; } $post_id = (int)$_POST['post_id']; $post_type = get_post_type($post_id); $path = $post_type == 'page' ? 'page-' . $post_id : $post_type; $file = PERFMATTERS_CACHE_DIR . 'css/' . $path . '.used.css'; if(is_file($file)) { unlink($file); } wp_send_json_success(); exit; } //add admin bar menu item public static function admin_bar_menu(WP_Admin_Bar $wp_admin_bar) { if(!current_user_can('manage_options') || !perfmatters_network_access()) { return; } $type = !is_admin() ? self::get_url_type() : ''; $menu_item = array( 'parent' => 'perfmatters', 'id' => 'perfmatters-clear-used-css', 'title' => __('Clear Used CSS', 'perfmatters') . ' (' . (!empty($type) ? __('Current', 'perfmatters') : __('All', 'perfmatters')) . ')', 'href' => add_query_arg(array( 'action' => 'perfmatters_clear_used_css', '_wp_http_referer' => rawurlencode($_SERVER['REQUEST_URI']), '_wpnonce' => wp_create_nonce('perfmatters_clear_used_css'), 'type' => $type ), admin_url('admin-post.php')) ); $wp_admin_bar->add_menu($menu_item); } //display admin notices public static function admin_notices() { if(get_transient('perfmatters_used_css_cleared') === false) { return; } delete_transient('perfmatters_used_css_cleared'); echo '

' . __('Used CSS cleared.', 'perfmatters' ) . '

'; } //clear used css from admin bar public static function admin_bar_clear_used_css() { if(!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'perfmatters_clear_used_css')) { wp_nonce_ays(''); } if(!empty($_GET['type'])) { //clear specific type $file = PERFMATTERS_CACHE_DIR . 'css/' . $_GET['type'] . '.used.css'; if(is_file($file)) { unlink($file); } } else { //clear all self::clear_used_css(); if(is_admin()) { set_transient('perfmatters_used_css_cleared', 1); } } //go back to url where button was pressed wp_safe_redirect(esc_url_raw(wp_get_referer())); exit; } //clear used css ajax action public static function clear_used_css_ajax() { Ajax::security_check(); self::clear_used_css(); wp_send_json_success(array( 'message' => __('Used CSS cleared.', 'perfmatters'), )); } }