<?php namespace WpAssetCleanUp; use WpAssetCleanUp\OptimiseAssets\CombineJs; use WpAssetCleanUp\OptimiseAssets\DynamicLoadedAssets; use WpAssetCleanUp\OptimiseAssets\OptimizeCommon; /** * Class Main * @package WpAssetCleanUp */ class Main { /** * */ const START_DEL_ENQUEUED = 'BEGIN WPACU PLUGIN JSON ENQUEUED'; /** * */ const END_DEL_ENQUEUED = 'END WPACU PLUGIN JSON ENQUEUED'; /** * */ const START_DEL_HARDCODED = 'BEGIN WPACU PLUGIN JSON HARDCODED'; /** * */ const END_DEL_HARDCODED = 'END WPACU PLUGIN JSON HARDCODED'; /** * Option "Plugin Usage Preferences" -> "Manage in the Dashboard" -> "Select a retrieval way:" ('Direct' or 'WP Remote Post') * * @var string */ public static $domGetType = 'direct'; /** * Record them for debugging purposes when using /?wpacu_debug * * @var array */ public $allUnloadedAssets = array( 'styles' => array(), 'scripts' => array() ); /** * @var array */ public $globalUnloaded = array(); /** * @var array */ public $loadExceptionsPageLevel = array( 'styles' => array(), 'scripts' => array(), '_set' => false ); /** * @var array[] */ public $loadExceptionsPostType = array( 'styles' => array(), 'scripts' => array(), '_set' => false ); /** * Rule that applies site-wide: if the user is logged-in * * @var array */ public $loadExceptionsLoggedInGlobal = array( 'styles' => array(), 'scripts' => array(), '_set' => false ); /** * @var */ public $fetchUrl; // [wpacu_lite] /** * @var */ public $isUpdateable = true; // [/wpacu_lite] /** * @var int */ public $currentPostId = 0; /** * @var array */ public $currentPost = array(); /** * @var array */ public static $vars = array( 'woo_url_not_match' => false, 'is_woo_shop_page' => false, 'for_type' => '', 'current_post_id' => 0, 'current_post_type' => '' ); /** * This is set to `true` only if "Manage in the Front-end?" is enabled in plugin's settings * and the logged-in administrator with plugin activation privileges * is outside the Dashboard viewing the pages like a visitor * * @var bool */ public $isFrontendEditView = false; /** * @var array */ public $stylesInHead = array(); /** * @var array */ public $scriptsInHead = array(); /** * @var array */ public $assetsInFooter = array( 'styles' => array(), 'scripts' => array() ); /** * @var array */ public $wpAllScripts = array(); /** * @var array */ public $wpAllStyles = array(); /** * @var array */ public $ignoreChildren = array(); /** * @var array */ public $ignoreChildrenHandlesOnTheFly = array(); /** * @var int */ public static $wpStylesSpecialDelimiters = array( 'start' => '<!--START-WPACU-SPECIAL-STYLES', 'end' => 'END-WPACU-SPECIAL-STYLES-->' ); /** * @var array */ public $postTypesUnloaded = array(); /** * @var array */ public $settings = array(); /** * @var bool */ public $isAjaxCall = false; /** * Fetch CSS/JS list from the Dashboard * * @var bool */ public $isGetAssetsCall = false; /** * Populated in the Parser constructor * * @var array */ public $skipAssets = array( 'styles' => array(), 'scripts' => array() ); /** * For these handles, it's strongly recommended to use 'Ignore dependency rule and keep the "children" loaded' * if any of them are unloaded in any page * * @var \string[][] */ public $keepChildrenLoadedForHandles = array( 'css' => array( 'elementor-icons' ), 'js' => array( 'swiper', 'elementor-waypoints', 'share-link' ) ); /** * @var Main|null */ private static $singleton; /** * @return null|Main */ public static function instance() { if ( self::$singleton === null ) { self::$singleton = new self(); } return self::$singleton; } /** * Parser constructor. */ public function __construct() { $this->skipAssets['styles'] = array_merge( array( 'admin-bar', // The top admin bar 'yoast-seo-adminbar', // Yoast "WordPress SEO" plugin 'autoptimize-toolbar', 'query-monitor', 'wp-fastest-cache-toolbar', // WP Fastest Cache plugin toolbar CSS 'litespeed-cache', // LiteSpeed toolbar 'siteground-optimizer-combined-styles-header' // Combine CSS in SG Optimiser (irrelevant as it made from the combined handles) ), // Own Scripts (for admin use only) OwnAssets::getOwnAssetsHandles('styles') ); $this->skipAssets['scripts'] = array_merge( array( 'admin-bar', // The top admin bar 'autoptimize-toolbar', 'query-monitor', 'wpfc-toolbar' // WP Fastest Cache plugin toolbar JS ), // Own Scripts (for admin use only) OwnAssets::getOwnAssetsHandles('scripts') ); // Filter before triggering the actual unloading through "wp_deregister_script", "wp_dequeue_script", "wp_deregister_style", "wp_dequeue_style" $this->fallbacks(); $this->isAjaxCall = ( ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) === 'xmlhttprequest' ); $this->isGetAssetsCall = isset( $_REQUEST[ WPACU_LOAD_ASSETS_REQ_KEY ] ) && $_REQUEST[ WPACU_LOAD_ASSETS_REQ_KEY ]; if ( $this->isGetAssetsCall ) { $currentTheme = strtolower(wp_get_theme()); $noRocketInit = true; if (strpos($currentTheme, 'uncode') !== false) { $noRocketInit = false; // make exception for the "Uncode" Theme as it doesn't check if the get_rocket_option() function exists } if ($noRocketInit) { add_filter('rocket_cache_reject_uri', function($urls) { $urls[] = '/?wpassetcleanup_load=1'; return $urls; }); } // Do not output Query Monitor's information as it's irrelevant in this context if ( class_exists( '\QueryMonitor' ) && class_exists( '\QM_Plugin' ) ) { add_filter( 'user_has_cap', static function( $user_caps ) { $user_caps['view_query_monitor'] = false; return $user_caps; }, 10, 1 ); } add_filter( 'style_loader_tag', static function( $styleTag, $tagHandle ) { // This is used to determine if the LINK is enqueued later on // If the handle name is not showing up, then the LINK stylesheet has been hardcoded (not enqueued the WordPress way) return str_replace( '<link ', '<link data-wpacu-style-handle=\'' . $tagHandle . '\' ', $styleTag ); }, PHP_INT_MAX, 2 ); // Trigger it later in case plugins such as "Ronneby Core" plugin alters it add_filter( 'script_loader_tag', static function( $scriptTag, $tagHandle ) { // This is used to determine if the SCRIPT is enqueued later on // If the handle name is not showing up, then the SCRIPT has been hardcoded (not enqueued the WordPress way) $reps = array( '<script ' => '<script data-wpacu-script-handle=\'' . $tagHandle . '\' ' ); return str_replace( array_keys( $reps ), array_values( $reps ), $scriptTag ); }, PHP_INT_MAX, 2 ); } // "File Size:" value from the asset row add_filter('wpacu_get_asset_file_size', array($this, 'getAssetFileSize'), 10, 2); // Triggers only if the administrator is logged in ('wp_ajax_nopriv' is not required) // Used to determine the total size of an external loaded assets (e.g. a CSS file from Google APIs) add_action('wp_ajax_'.WPACU_PLUGIN_ID.'_get_external_file_size', array($this, 'ajaxGetExternalFileSize')); // Early Triggers $wpacuAction = Misc::isElementorMaintenanceModeOn() ? 'template_redirect' : 'wp'; if ($wpacuAction === 'wp') { add_action( 'wp', array( $this, 'setVarsBeforeUpdate' ), 8 ); add_action( 'wp', array( $this, 'setVarsAfterAnyUpdate' ), 10 ); } else { add_action( 'template_redirect', array( $this, 'setVarsBeforeUpdate' ), 12 ); // over 11 which is set in Elementor's maintenance-mode.php add_action( 'template_redirect', array( $this, 'setVarsAfterAnyUpdate' ), 13 ); } // Fetch Assets AJAX Call? Make sure the output is as clean as possible (no plugins interfering with it) // It can also be used for debugging purposes (via /?wpacu_clean_load) when you want to view all the CSS/JS // that are loaded in the HTML source code before they are unloaded or altered in any way if ( $this->isGetAssetsCall || isset($_GET['wpacu_clean_load']) ) { $wpacuCleanUp = new CleanUp(); $wpacuCleanUp->cleanUpHtmlOutputForAssetsCall(); } // "Direct" AJAX call or "WP Remote Post" method used? // Do not trigger the admin bar as it's not relevant if ( $this->isAjaxCall || $this->isGetAssetsCall ) { add_filter( 'show_admin_bar', '__return_false' ); } // This is triggered AFTER "saveSettings" from 'Settings' class // In case the settings were just updated, the script will get the latest values add_action( 'init', array( $this, 'triggersAfterInit' ), 10 ); // Front-end View - Unload the assets // If there are reasons to prevent the unloading in case 'test mode' is enabled, // then the prevention will trigger within filterStyles() and filterScripts() if ( ! is_admin() ) { $this->alterWpStylesScriptsObj(); } /* * [START] /?wpassetcleanup_load=1 is called */ if ( $this->isGetAssetsCall && ! is_admin() ) { // These actions are also called when the page is loaded without query string (regular load) // This time, the CSS/JS will not be unloaded, but the CSS/JS marked for unload will be collected // and passed to the AJAX call for the option "Group by loaded or unloaded status" if ( get_option( 'siteground_optimizer_combine_css' ) ) { add_action( 'wp_print_styles', array( $this, 'filterStyles' ), 9 ); // priority should be below 10 } add_action( 'wp_print_styles', array( $this, 'filterStyles' ), 100000 ); add_action( 'wp_print_scripts', array( $this, 'filterScripts' ), 100000 ); add_action( 'wp_print_footer_scripts', array( $this, 'onPrintFooterScriptsStyles' ), 1 ); } /* * [END] /?wpassetcleanup_load=1 is called */ /* * [START] Front-end page visited (e.g. by the admin or a guest visitor) */ if ( ! $this->isGetAssetsCall && ! is_admin() ) { // [START] Unload CSS/JS on page request (for debugging) add_filter( 'wpacu_ignore_child_parent_list', array( $this, 'filterIgnoreChildParentList' ) ); // [END] Unload CSS/JS on page request (for debugging) // SG Optimizer Compatibility: Unload Styles - HEAD (Before pre_combine_header_styles() from Combinator) if ( get_option( 'siteground_optimizer_combine_css' ) ) { add_action( 'wp_print_styles', array( $this, 'filterStyles' ), 9 ); // priority should be below 10 } $this->filterStylesSpecialCases(); // e.g. CSS enqueued in a different way via Oxygen Builder add_action( 'wp_print_styles', array( $this, 'filterStyles' ), 100000 ); // Unload Styles - HEAD add_action( 'wp_print_scripts', array( $this, 'filterScripts' ), 100000 ); // Unload Scripts - HEAD add_action( 'wp_print_styles', array($this, 'printAnySpecialCss'), PHP_INT_MAX ); // Unload Styles & Scripts - FOOTER // Needs to be triggered very soon as some old plugins/themes use wp_footer() to enqueue scripts // Sometimes styles are loaded in the BODY section of the page add_action( 'wp_print_footer_scripts', array( $this, 'onPrintFooterScriptsStyles' ), 1 ); // Preloads add_action( 'wp_head', static function() { if ( Plugin::preventAnyFrontendOptimization() || self::isTestModeActive() ) { return; } // Only place the market IF there's at least one preload OR combine JS is activated $preloadsClass = new Preloads(); if ( isset( $preloadsClass->preloads[ 'styles' ] ) && ! empty( $preloadsClass->preloads[ 'styles' ] ) ) { echo Preloads::DEL_STYLES_PRELOADS; } if ( (isset( $preloadsClass->preloads[ 'scripts' ] ) && ! empty( $preloadsClass->preloads[ 'scripts' ] )) || CombineJs::proceedWithJsCombine() ) { echo Preloads::DEL_SCRIPTS_PRELOADS; } }, 1 ); add_filter( 'style_loader_tag', static function( $styleTag, $tagHandle ) { if ( Plugin::preventAnyFrontendOptimization() ) { return $styleTag; } // Preload the plugin's CSS for assets management layout (for faster content paint if the user is logged-in and manages the assets in the front-end) // For a better admin experience if ( $tagHandle === WPACU_PLUGIN_ID . '-style' ) { $styleTag = str_ireplace( array( '<link ', 'rel=\'stylesheet\'', 'rel="stylesheet"', 'id=\'', 'id="' ), array( '<link rel=\'preload\' as=\'style\' data-wpacu-preload-it-async=\'1\' ', 'onload="this.onload=null;this.rel=\'stylesheet\'"', 'onload="this.rel=\'stylesheet\'"', 'id=\'wpacu-preload-', 'id="wpacu-preload-' ), $styleTag ); } // Irrelevant for Critical CSS as the top admin bar is for logged-in users // and if it's not included in the critical CSS it would cause a flash of unstyled content which is not pleasant for the admin if ( $tagHandle === 'admin-bar' ) { $styleTag = str_replace( '<link ', '<link data-wpacu-skip-preload=\'1\' ', $styleTag ); } if ( Plugin::preventAnyFrontendOptimization() || self::isTestModeActive() ) { return $styleTag; } // Alter for debugging purposes; triggers before anything else // e.g. you're working on a website and there is no Dashboard access and you want to determine the handle name // if the handle name is not showing up, then the LINK stylesheet has been hardcoded (not enqueued the WordPress way) if ( isset($_GET['wpacu_show_handle_names']) ) { $styleTag = str_replace( '<link ', '<link data-wpacu-debug-style-handle=\'' . $tagHandle . '\' ', $styleTag ); } if ( strpos( $styleTag, 'data-wpacu-style-handle' ) === false ) { $styleTag = str_replace( '<link ', '<link data-wpacu-style-handle=\'' . $tagHandle . '\' ', $styleTag ); } return $styleTag; }, PHP_INT_MAX, 2 ); // Trigger it later in case plugins such as "Ronneby Core" plugin alters it add_filter( 'script_loader_tag', static function( $scriptTag, $tagHandle ) { if ( Plugin::preventAnyFrontendOptimization() ) { return $scriptTag; } // Alter for debugging purposes; triggers before anything else // e.g. you're working on a website and there is no Dashboard access and you want to determine the handle name // if the handle name is not showing up, then the SCRIPT has been hardcoded (not enqueued the WordPress way) if ( isset($_GET['wpacu_show_handle_names']) ) { $scriptTag = str_replace( '<script ', '<script data-wpacu-debug-script-handle=\'' . $tagHandle . '\' ', $scriptTag ); } if ( strpos( $scriptTag, 'data-wpacu-script-handle' ) === false && Menu::userCanManageAssets() && self::instance()->isFrontendEditView ) { $scriptTag = str_replace( '<script ', '<script data-wpacu-script-handle=\'' . $tagHandle . '\' ', $scriptTag ); } if ( Plugin::preventAnyFrontendOptimization() || self::isTestModeActive() ) { return $scriptTag; } if ( strpos( $scriptTag, 'data-wpacu-script-handle' ) === false ) { $scriptTag = str_replace( '<script ', '<script data-wpacu-script-handle=\'' . $tagHandle . '\' ', $scriptTag ); } if ( $tagHandle === 'jquery-core' ) { $scriptTag = str_replace( '<script ', '<script data-wpacu-jquery-core-handle=1 ', $scriptTag ); } if ( $tagHandle === 'jquery-migrate' ) { $scriptTag = str_replace( '<script ', '<script data-wpacu-jquery-migrate-handle=1 ', $scriptTag ); } return $scriptTag; }, PHP_INT_MAX, 2 ); Preloads::instance()->init(); } /* * [END] Front-end page visited (e.g. by the admin or a guest visitor) */ // Only trigger it within the Dashboard when an Asset CleanUp (Pro) page is accessed and the transient is non-existent or expired if ( is_admin() ) { add_action( 'admin_footer', array( $this, 'ajaxFetchActivePluginsJsFooterCode' ) ); add_action( 'wp_ajax_' . WPACU_PLUGIN_ID . '_fetch_active_plugins_icons', array( $this, 'ajaxFetchActivePluginsIconsFromWordPressOrg' ) ); } add_filter( 'duplicate_post_meta_keys_filter', static function( $meta_keys ) { // Get the original post ID $postId = isset( $_GET['post'] ) ? (int)$_GET['post'] : false; if ( ! $postId ) { $postId = isset( $_POST['post'] ) ? (int)$_POST['post'] : false; } if ( $postId ) { global $wpdb; $metaKeyLike = '_' . WPACU_PLUGIN_ID . '_%'; $assetCleanUpMetaKeysQuery = <<<SQL SELECT `meta_key` FROM {$wpdb->postmeta} WHERE meta_key LIKE '{$metaKeyLike}' AND post_id='{$postId}' SQL; $assetCleanUpMetaKeys = $wpdb->get_col( $assetCleanUpMetaKeysQuery ); if ( ! empty( $assetCleanUpMetaKeys ) ) { $meta_keys = array_merge( $meta_keys, $assetCleanUpMetaKeys ); } } return $meta_keys; } ); $this->wpacuHtmlNoticeForAdmin(); add_action( 'wp_ajax_' . WPACU_PLUGIN_ID . '_check_external_urls_for_status_code', array( $this, 'ajaxCheckExternalUrlsForStatusCode' ) ); } /** * */ public function triggersAfterInit() { $wpacuSettingsClass = new Settings(); $this->settings = $wpacuSettingsClass->getAll(); if ( $this->settings['dashboard_show'] && $this->settings['dom_get_type'] ) { self::$domGetType = $this->settings['dom_get_type']; } // Fetch the page in the background to see what scripts/styles are already loading // This applies only for front-end loading if ( ! is_admin() && ($this->isGetAssetsCall || $this->frontendShow()) ) { if ( $this->isGetAssetsCall ) { add_filter( 'show_admin_bar', '__return_false' ); } // Save CSS handles list that is printed in the <HEAD> // No room for errors, some developers might enqueue (although not ideal) assets via "wp_head" or "wp_print_styles"/"wp_print_scripts" add_action( 'wp_enqueue_scripts', array( $this, 'saveHeadAssets' ), PHP_INT_MAX - 1 ); // Save CSS/JS list that is printed in the <BODY> add_action( 'wp_print_footer_scripts', array( $this, 'saveFooterAssets' ), 100000000 ); add_action( 'wp_footer', array( $this, 'printScriptsStyles' ), ( PHP_INT_MAX - 1 ) ); } // Send an AJAX request to get the list of the loaded hardcoded scripts and styles and print it add_action( 'wp_ajax_' . WPACU_PLUGIN_ID . '_print_loaded_hardcoded_assets', array( $this, 'ajaxPrintLoadedHardcodedAssets' ) ); if ( is_admin() ) { $metaboxes = new MetaBoxes; // Do not load the meta box nor do any AJAX calls // if the asset management is not enabled for the Dashboard if ( $this->settings['dashboard_show'] == 1 ) { // Send an AJAX request to get the list of loaded scripts and styles and print it nicely add_action( 'wp_ajax_' . WPACU_PLUGIN_ID . '_get_loaded_assets', array( $this, 'ajaxGetJsonListCallback' ) ); // This is valid when the Gutenberg editor (not via "Classic Editor" plugin) is used and the user used the following option: // "Do not load Asset CleanUp Pro on this page (this will disable any functionality of the plugin)" add_action( 'wp_ajax_' . WPACU_PLUGIN_ID . '_load_page_restricted_area', array( $this, 'ajaxLoadRestrictedPageAreaCallback' ) ); } // If assets management within the Dashboard is not enabled, an explanation message will be shown within the box unless the meta box is hidden completely if ( $this->settings['show_assets_meta_box'] ) { $metaboxes->initMetaBox( 'manage_page_assets' ); } } /* DO NOT disable the features below if the following apply: - The option is not enabled - Test Mode Enabled & Admin Logged in - The user is in the Dashboard (any changes are applied in the front-end view) */ if ( ! ( $this->preventAssetsSettings() || is_admin() ) ) { if ( $this->settings['disable_emojis'] == 1 ) { $wpacuCleanUp = new CleanUp(); $wpacuCleanUp->doDisableEmojis(); } if ( $this->settings['disable_oembed'] == 1 ) { $wpacuCleanUp = new CleanUp(); $wpacuCleanUp->doDisableOembed(); } } } /** * Priority: 8 (earliest) */ public function setVarsBeforeUpdate() { // Conditions // 1) User has rights to manage the assets and the option is enabled in plugin's Settings // 2) Not an AJAX call from the Dashboard // 3) Not inside the Dashboard $this->isFrontendEditView = ( $this->frontendShow() && Menu::userCanManageAssets() // 1 && ! $this->isGetAssetsCall // 2 && ! is_admin() ); // 3 if ( $this->isFrontendEditView ) { $wpacuCleanUp = new CleanUp(); $wpacuCleanUp->cleanUpHtmlOutputForAssetsCall(); } $this->getCurrentPostId(); define( 'WPACU_CURRENT_PAGE_ID', $this->getCurrentPostId() ); } /** * Priority: 10 (latest) */ public function setVarsAfterAnyUpdate() { if ( ! is_admin() ) { $this->globalUnloaded = $this->getGlobalUnload(); // [wpacu_lite] if (! $this->isUpdateable) { return; } // [/wpacu_lite] $getCurrentPost = $this->getCurrentPost(); $post = $type = false; if (Misc::isHomePage() && empty($getCurrentPost) && ! get_option('page_on_front')) { $type = 'front_page'; } elseif ( ! empty($getCurrentPost) ) { $type = 'post'; $post = $getCurrentPost; $this->postTypesUnloaded = (isset($post->post_type) && $post->post_type) ? $this->getBulkUnload('post_type', $post->post_type) : array(); } // [wpacu_lite] if (! $type) { // The request is done for a page such as is_archive(), is_author(), 404, search // and the premium extension is not available, thus no load exceptions are available return; } // [/wpacu_lite] self::$vars['for_type'] = $type; self::$vars['current_post_id'] = $this->currentPostId; if ($post && $type === 'post' && isset($post->post_type) && $post->post_type) { self::$vars['current_post_type'] = $post->post_type; } } } /** * This is useful to change via hooks the "src", "ver" or other values of the loaded handle * Example: You have your theme's main style.css that is needed on every page * On some pages, you only need 20% of it to load, and you can manually trim the other 80% (if you're sure you know which CSS is not used) * You can use a filter hook such as 'wpacu_{main_theme_handle_name_here}_css_handle_obj' to filter the "src" of the object and load an alternative purified CSS file */ public function alterWpStylesScriptsObj() { add_action('wp_print_styles', function() { global $wp_styles; if ( ! empty($wp_styles->registered) ) { foreach (array_keys($wp_styles->registered) as $styleHandle) { $wp_styles->registered[$styleHandle] = $this->maybeFilterAssetObject($wp_styles->registered[$styleHandle], 'css'); } } }); foreach (array('wp_print_scripts', 'wp_print_footer_scripts') as $actionToAdd) { add_action( $actionToAdd, function() { global $wp_scripts; if ( ! empty($wp_scripts->registered) ) { foreach (array_keys($wp_scripts->registered) as $scriptHandle) { $wp_scripts->registered[$scriptHandle] = $this->maybeFilterAssetObject($wp_scripts->registered[$scriptHandle], 'js'); } } }); } } /** * @param $object | as returned from $wp_styles or $wp_scripts * @param $assetType | "css" or "js" * * @return mixed */ public function maybeFilterAssetObject($object, $assetType) { if ( isset($_GET['wpacu_clean_load']) || isset($_GET['wpacu_load_original']) ) { return $object; // this is for debugging purposes, load the original source } if ( ! isset($object->handle, $object->src) ) { return $object; } $filterTagName = 'wpacu_'.$object->handle.'_'.$assetType.'_handle_data'; if ( has_filter($filterTagName) ) { $originData = (array)$object; $newData = apply_filters( $filterTagName, $originData ); if ( isset($originData['src'], $newData['src']) && $newData['src'] !== $originData['src'] ) { $object->src = $newData['src']; $object->src_origin = $originData['src']; $object->ver = $newData['ver'] ?: null; $object->ver_origin = isset($originData['ver']) ? $originData['ver'] : null; } } return $object; } /** * In case there were assets enqueued within "wp_footer" action hook, instead of the standard "wp_enqueue_scripts" */ public function onPrintFooterScriptsStyles() { $this->filterStyles(); $this->filterScripts(); } /* [START] Styles Dequeue */ /** * See if there is any list with styles to be removed in JSON format * Only the handles (the ID of the styles) is stored */ public function filterStyles() { /* [wpacu_timing] */ Misc::scriptExecTimer( 'filter_dequeue_styles' );/* [/wpacu_timing] */ if (is_admin()) { return; } global $wp_styles; if (current_action() === 'wp_print_styles') { ObjectCache::wpacu_cache_set('wpacu_styles_object_after_wp_print_styles', $wp_styles); } $list = array(); if (current_action() === 'wp_print_footer_scripts') { $cachedWpStyles = ObjectCache::wpacu_cache_get('wpacu_styles_object_after_wp_print_styles'); if (isset($cachedWpStyles->registered) && count($cachedWpStyles->registered) === count($wp_styles->registered)) { // The list was already generated in "wp_print_styles" and the number of registered assets are the same // Save resources and do not re-generate it $list = ObjectCache::wpacu_cache_get('wpacu_styles_handles_marked_for_unload'); } } if ( empty($list) || ! is_array($list) ) { // [wpacu_lite] $nonAssetConfigPage = ( ! $this->isUpdateable && ! Misc::getShowOnFront() ); // [/wpacu_lite] // It looks like the page loaded is neither a post, page or the front-page // We'll see if there are assets unloaded globally and unload them /* * [START] Build unload list */ $globalUnload = $this->globalUnloaded; // [wpacu_lite] if ( $nonAssetConfigPage && ! empty( $globalUnload['styles'] ) ) { $list = $globalUnload['styles']; } else { // [/wpacu_lite] // Post, Page, Front-page // and more (if the Premium Extension is activated) $toRemove = $this->getAssetsUnloadedPageLevel(); $jsonList = @json_decode( $toRemove ); $list = array(); if ( isset( $jsonList->styles ) ) { $list = (array) $jsonList->styles; } if (! is_array($list)) { $list = array(); } // Any global unloaded styles? Append them if ( ! empty( $globalUnload['styles'] ) ) { foreach ( $globalUnload['styles'] as $handleStyle ) { $list[] = $handleStyle; } } if ( self::isSingularPage() ) { // Any bulk unloaded styles (e.g. for all pages belonging to a post type)? Append them if ( empty( $this->postTypesUnloaded ) ) { $post = $this->getCurrentPost(); $this->postTypesUnloaded = ( isset( $post->post_type ) && $post->post_type ) ? $this->getBulkUnload( 'post_type', $post->post_type ) : array(); } if ( isset( $this->postTypesUnloaded['styles'] ) && ! empty( $this->postTypesUnloaded['styles'] ) ) { foreach ( $this->postTypesUnloaded['styles'] as $handleStyle ) { $list[] = $handleStyle; } } } // [wpacu_lite] } // [/wpacu_lite] // Site-Wide Unload for "Dashicons" if user is not logged-in if ( $this->settings['disable_dashicons_for_guests'] && ! is_user_logged_in() ) { $list[] = 'dashicons'; } // Any bulk unloaded styles for 'category', 'post_tag' and more? // If the Pro version is enabled, any of the unloaded CSS will be added to the list $list = apply_filters( 'wpacu_filter_styles_list_unload', array_unique( $list ) ); /* * [END] Build unload list */ // Add handles such as the Oxygen Builder CSS ones that are missing and added differently to the queue $allStyles = $this->wpStylesFilter( $wp_styles, 'registered', $list ); if ( $allStyles !== null && ! empty( $allStyles->registered ) ) { // Going through all the registered styles foreach ( $allStyles->registered as $handle => $value ) { // This could be triggered several times, check if the style already exists if ( ! isset( $this->wpAllStyles['registered'][ $handle ] ) ) { $this->wpAllStyles['registered'][ $handle ] = $value; if ( in_array( $handle, $allStyles->queue ) ) { $this->wpAllStyles['queue'][] = $handle; } } } if ( isset( $this->wpAllStyles['queue'] ) && ! empty( $this->wpAllStyles['queue'] ) ) { $this->wpAllStyles['queue'] = array_unique( $this->wpAllStyles['queue'] ); } } if ( isset( $this->wpAllStyles['registered'] ) && ! empty( $this->wpAllStyles['registered'] ) ) { ObjectCache::wpacu_cache_set( 'wpacu_all_styles_handles', array_keys( $this->wpAllStyles['registered'] ) ); } // e.g. for test/debug mode or AJAX calls (where all assets have to load) if ( isset($_REQUEST['wpacu_no_css_unload']) ) { /* [wpacu_timing] */Misc::scriptExecTimer( 'filter_dequeue_styles', 'end' ); /* [/wpacu_timing] */ return; } if ( $this->preventAssetsSettings(array('assets_call')) ) { /* [wpacu_timing] */Misc::scriptExecTimer( 'filter_dequeue_styles', 'end' ); /* [/wpacu_timing] */ return; } /* * [START] Load Exception Check * */ // Load exception rules ALWAYS have priority over the unloading ones // Thus, if an exception is found, the handle will be removed from the unloading list // Let's see if there are load exceptions for this page or site-wide (e.g. for logged-in users) // Only check for any load exceptions if the unloading list has at least one item // Otherwise the action is irrelevant since the assets are loaded anyway by default // These are LITE rules $list = ( ! empty($list) ) ? $this->filterAssetsUnloadList($list, 'styles','load_exception') : $list; // These are PRO rules or rules added via custom coding $list = ( ! empty($list) ) ? apply_filters('wpacu_filter_styles_list_load_exception', $list) : $list; /* * [END] Load Exception Check * */ // Is $list still empty? Nothing to unload? Stop here if (empty($list)) { /* [wpacu_timing] */ Misc::scriptExecTimer( 'filter_dequeue_styles', 'end' ); /* [/wpacu_timing] */ return; } } $ignoreChildParentList = apply_filters('wpacu_ignore_child_parent_list', $this->getIgnoreChildren()); foreach ($list as $handle) { if (isset($ignoreChildParentList['styles'], $this->wpAllStyles['registered'][$handle]->src) && is_array($ignoreChildParentList['styles']) && array_key_exists($handle, $ignoreChildParentList['styles'])) { // Do not dequeue it as it's "children" will also be dequeued (ignore rule is applied) // It will be stripped by cleaning its LINK tag from the HTML Source $this->ignoreChildren['styles'][$handle] = $this->wpAllStyles['registered'][$handle]->src; $this->ignoreChildren['styles'][$handle.'_has_unload_rule'] = 1; $this->allUnloadedAssets['styles'][] = $handle; continue; } $handle = trim($handle); // Ignore auto generated handles for the hardcoded CSS as they were added for reference purposes // They will get stripped later on via OptimizeCommon.php // This is in case there are references from using the Pro version if (strpos($handle, 'wpacu_hardcoded_link_') === 0) { continue; } if (strpos($handle, 'wpacu_hardcoded_style_') === 0) { continue; } // Do not unload "dashicons" if the top WordPress admin bar is showing up if ($handle === 'dashicons' && is_admin_bar_showing()) { continue; } $this->allUnloadedAssets['styles'][] = $handle; // Only trigger the unloading on regular page load, not when the assets list is collected if ( ! $this->isGetAssetsCall ) { wp_deregister_style( $handle ); wp_dequeue_style( $handle ); } } if (current_action() === 'wp_print_styles') { ObjectCache::wpacu_cache_set( 'wpacu_styles_handles_marked_for_unload', $list ); } /* [wpacu_timing] */ Misc::scriptExecTimer( 'filter_dequeue_styles', 'end' ); /* [/wpacu_timing] */ } /** * @param $wpStylesFilter * @param string $listType * @param array $unloadedList * * @return mixed */ public function wpStylesFilter($wpStylesFilter, $listType, $unloadedList = array()) { global $wp_styles, $oxygen_vsb_css_styles; if ( ( $listType === 'registered' ) && isset( $oxygen_vsb_css_styles->registered ) && is_object( $oxygen_vsb_css_styles ) && ! empty( $oxygen_vsb_css_styles->registered ) ) { $stylesSpecialCases = array(); foreach ($oxygen_vsb_css_styles->registered as $oxygenHandle => $oxygenValue) { if (! array_key_exists($oxygenHandle, $wp_styles->registered)) { $wpStylesFilter->registered[$oxygenHandle] = $oxygenValue; $stylesSpecialCases[$oxygenHandle] = $oxygenValue->src; } } $unloadedSpecialCases = array(); foreach ($unloadedList as $unloadedHandle) { if (array_key_exists($unloadedHandle, $stylesSpecialCases)) { $unloadedSpecialCases[$unloadedHandle] = $stylesSpecialCases[$unloadedHandle]; } } if (! empty($unloadedSpecialCases)) { // This will be later used in 'wp_loaded' below to extract the special styles echo self::$wpStylesSpecialDelimiters['start'] . wp_json_encode($unloadedSpecialCases) . self::$wpStylesSpecialDelimiters['end']; } } if ( ( $listType === 'done' ) && isset( $oxygen_vsb_css_styles->done ) && is_object( $oxygen_vsb_css_styles ) ) { foreach ($oxygen_vsb_css_styles->done as $oxygenHandle) { if (! in_array($oxygenHandle, $wp_styles->done)) { $wpStylesFilter[] = $oxygenHandle; } } } if ( ( $listType === 'queue' ) && isset( $oxygen_vsb_css_styles->queue ) && is_object( $oxygen_vsb_css_styles ) ) { foreach ($oxygen_vsb_css_styles->queue as $oxygenHandle) { if (! in_array($oxygenHandle, $wp_styles->queue)) { $wpStylesFilter[] = $oxygenHandle; } } } return $wpStylesFilter; } /** * */ public function filterStylesSpecialCases() { if ( isset($_REQUEST['wpacu_no_css_unload']) ) { return; } add_action('wp_loaded', static function() { ob_start(static function($htmlSource) { if (strpos($htmlSource, self::$wpStylesSpecialDelimiters['start']) === false && strpos($htmlSource, self::$wpStylesSpecialDelimiters['end']) === false) { return $htmlSource; } $jsonStylesSpecialCases = Misc::extractBetween($htmlSource, self::$wpStylesSpecialDelimiters['start'], self::$wpStylesSpecialDelimiters['end']); $stylesSpecialCases = json_decode($jsonStylesSpecialCases, ARRAY_A); if (Misc::jsonLastError() === JSON_ERROR_NONE && ! empty($stylesSpecialCases)) { foreach ($stylesSpecialCases as $styleSrc) { $styleLocalSrc = Misc::getLocalSrc($styleSrc); $styleRelSrc = isset($styleLocalSrc['rel_src']) ? $styleLocalSrc['rel_src'] : $styleSrc; $htmlSource = CleanUp::cleanLinkTagFromHtmlSource($styleRelSrc, $htmlSource); } // Strip the info HTML comment $htmlSource = str_replace( self::$wpStylesSpecialDelimiters['start'] . $jsonStylesSpecialCases . self::$wpStylesSpecialDelimiters['end'], '', $htmlSource ); } return $htmlSource; }); }, 1); } /** * */ public function printAnySpecialCss() { if (isset($this->allUnloadedAssets['styles']) && ! empty($this->allUnloadedAssets['styles']) && in_array('photoswipe', $this->allUnloadedAssets['styles'])) { ?> <?php if (current_user_can('administrator')) { ?><!-- Asset CleanUp: "photoswipe" unloaded (avoid printing useless HTML) --><?php } ?> <style <?php echo Misc::getStyleTypeAttribute(); ?>>.pswp { display: none; }</style> <?php } } /* [END] Styles Dequeue */ /* [START] Scripts Dequeue */ /** * See if there is any list with scripts to be removed in JSON format * Only the handles (the ID of the scripts) are saved */ public function filterScripts() { /* [wpacu_timing] */ Misc::scriptExecTimer( 'filter_dequeue_scripts' );/* [/wpacu_timing] */ if (is_admin()) { return; } global $wp_scripts; if (current_action() === 'wp_print_scripts') { ObjectCache::wpacu_cache_set('wpacu_scripts_object_after_wp_print_scripts', $wp_scripts); } $list = array(); if (current_action() === 'wp_print_footer_scripts') { $cachedWpScripts = ObjectCache::wpacu_cache_get('wpacu_scripts_object_after_wp_print_scripts'); if (isset($cachedWpScripts->registered) && count($cachedWpScripts->registered) === count($wp_scripts->registered)) { // The list was already generated in "wp_print_scripts" and the number of registered assets are the same // Save resources and do not re-generate it $list = ObjectCache::wpacu_cache_get('wpacu_scripts_handles_marked_for_unload'); } } if ( empty($list) ) { // [wpacu_lite] $nonAssetConfigPage = ( ! $this->isUpdateable && ! Misc::getShowOnFront() ); // [/wpacu_lite] // It looks like the page loaded is neither a post, page or the front-page // We'll see if there are assets unloaded globally and unload them /* * [START] Build unload list */ $globalUnload = $this->globalUnloaded; // [wpacu_lite] if ( $nonAssetConfigPage && ! empty( $globalUnload['scripts'] ) ) { $list = $globalUnload['scripts']; } else { // [/wpacu_lite] // Post, Page or Front-page? $toRemove = $this->getAssetsUnloadedPageLevel(); $jsonList = @json_decode( $toRemove ); $list = array(); if ( isset( $jsonList->scripts ) ) { $list = (array) $jsonList->scripts; } // Any global unloaded styles? Append them if ( ! empty( $globalUnload['scripts'] ) ) { foreach ( $globalUnload['scripts'] as $handleScript ) { $list[] = $handleScript; } } if ( self::isSingularPage() ) { // Any bulk unloaded styles (e.g. for all pages belonging to a post type)? Append them if ( empty( $this->postTypesUnloaded ) ) { $post = $this->getCurrentPost(); // Make sure the post_type is set; it's not in specific pages (e.g. BuddyPress ones) $this->postTypesUnloaded = ( isset( $post->post_type ) && $post->post_type ) ? $this->getBulkUnload( 'post_type', $post->post_type ) : array(); } if ( isset( $this->postTypesUnloaded['scripts'] ) && ! empty( $this->postTypesUnloaded['scripts'] ) ) { foreach ( $this->postTypesUnloaded['scripts'] as $handleStyle ) { $list[] = $handleStyle; } } } // [wpacu_lite] } // [/wpacu_lite] // Any bulk unloaded styles for 'category', 'post_tag' and more? // These are PRO rules or rules added via custom coding $list = apply_filters( 'wpacu_filter_scripts_list_unload', array_unique( $list ) ); global $wp_scripts; $allScripts = $wp_scripts; if ( $allScripts !== null && ! empty( $allScripts->registered ) ) { foreach ( $allScripts->registered as $handle => $value ) { // This could be triggered several times, check if the script already exists if ( ! isset( $this->wpAllScripts['registered'][ $handle ] ) ) { $this->wpAllScripts['registered'][ $handle ] = $value; if ( in_array( $handle, $allScripts->queue ) ) { $this->wpAllScripts['queue'][] = $handle; } } } if ( isset( $this->wpAllScripts['queue'] ) && ! empty( $this->wpAllScripts['queue'] ) ) { $this->wpAllScripts['queue'] = array_unique( $this->wpAllScripts['queue'] ); } } /* * [END] Build unload list */ /* * [START] Load Exception Check * */ // Load exception rules ALWAYS have priority over the unloading ones // Thus, if an exception is found, the handle will be removed from the unloading list // Let's see if there are load exceptions for this page or site-wide (e.g. for logged-in users) // These are LITE rules $list = ( ! empty($list) ) ? $this->filterAssetsUnloadList($list, 'scripts', 'load_exception') : $list; // [wpacu_pro] // These are PRO rules or rules added via custom coding // Only check for any load exceptions if the unloading list has at least one item // Otherwise the action is irrelevant since the assets are loaded anyway by default $list = ( ! empty($list) ) ? apply_filters('wpacu_filter_scripts_list_load_exception', $list) : $list; // [/wpacu_pro] /* * [END] Load Exception Check * */ // Nothing to unload if ( empty( $list ) ) { /* [wpacu_timing] */Misc::scriptExecTimer( 'filter_dequeue_scripts', 'end' ); /* [/wpacu_timing] */ return; } // e.g. for test/debug mode or AJAX calls (where all assets have to load) if ( isset($_REQUEST['wpacu_no_js_unload']) || $this->preventAssetsSettings(array('assets_call')) ) { /* [wpacu_timing] */Misc::scriptExecTimer( 'filter_dequeue_scripts', 'end' ); /* [/wpacu_timing] */ return; } } $ignoreChildParentList = apply_filters('wpacu_ignore_child_parent_list', $this->getIgnoreChildren()); foreach ($list as $handle) { $handle = trim($handle); // Ignore auto generated handles for the hardcoded CSS as they were added for reference purposes // They will get stripped later on via OptimizeCommon.php // The handle is used just for reference for later stripping via altering the DOM if (strpos($handle, 'wpacu_hardcoded_script_inline_') !== false || strpos($handle, 'wpacu_hardcoded_noscript_inline_') !== false) { // [wpacu_pro] $saveMarkedHandles = ObjectCache::wpacu_cache_get('wpacu_hardcoded_scripts_noscripts_inline') ?: array(); $saveMarkedHandles[] = $handle; $this->allUnloadedAssets['scripts'][] = $handle; // for "wpacu_no_load" on hardcoded list ObjectCache::wpacu_cache_set( 'wpacu_hardcoded_scripts_noscripts_inline', $saveMarkedHandles ); // [/wpacu_pro] continue; } if (strpos($handle, 'wpacu_hardcoded_script_src_') !== false) { // [wpacu_pro] $saveMarkedHandles = ObjectCache::wpacu_cache_get('wpacu_hardcoded_scripts_src') ?: array(); $saveMarkedHandles[] = $handle; $this->allUnloadedAssets['scripts'][] = $handle; // for "wpacu_no_load" on hardcoded list ObjectCache::wpacu_cache_set( 'wpacu_hardcoded_scripts_src', $saveMarkedHandles ); // [/wpacu_pro] continue; } // Special Action for 'jquery-migrate' handler as it's tied to 'jquery' if ($handle === 'jquery-migrate' && isset($this->wpAllScripts['registered']['jquery'])) { $jQueryRegScript = $this->wpAllScripts['registered']['jquery']; if (isset($jQueryRegScript->deps)) { $jQueryRegScript->deps = array_diff($jQueryRegScript->deps, array('jquery-migrate')); } if (Misc::isPluginActive('jquery-updater/jquery-updater.php')) { wp_dequeue_script($handle); } continue; } if (isset($ignoreChildParentList['scripts'], $this->wpAllScripts['registered'][$handle]->src) && is_array($ignoreChildParentList['scripts']) && array_key_exists($handle, $ignoreChildParentList['scripts'])) { // Do not dequeue it as it's "children" will also be dequeued (ignore rule is applied) // It will be stripped by cleaning its SCRIPT tag from the HTML Source $this->ignoreChildren['scripts'][$handle] = $this->wpAllScripts['registered'][$handle]->src; $this->ignoreChildren['scripts'][$handle.'_has_unload_rule'] = 1; $this->allUnloadedAssets['scripts'][] = $handle; continue; } $this->allUnloadedAssets['scripts'][] = $handle; // Only trigger the unloading on regular page load, not when the assets list is collected if ( ! $this->isGetAssetsCall ) { wp_deregister_script( $handle ); wp_dequeue_script( $handle ); } } if (current_action() === 'wp_print_scripts') { ObjectCache::wpacu_cache_set( 'wpacu_scripts_handles_marked_for_unload', $list ); } /* [wpacu_timing] */ Misc::scriptExecTimer( 'filter_dequeue_scripts', 'end' ); /* [/wpacu_timing] */ } /* [END] Scripts Dequeue */ /** * @param $list (should never be empty when called) * @param $assetType * @param $ruleType * * @return mixed */ public function filterAssetsUnloadList($list, $assetType, $ruleType) { // Remove any assets from the list in case there are any load exceptions detected if ($ruleType === 'load_exception') { $this->loadExceptionsPageLevel = $this->getLoadExceptionsPageLevel(self::$vars['for_type'], self::$vars['current_post_id']); $this->loadExceptionsLoggedInGlobal = $this->getHandleLoadLoggedIn(); $this->loadExceptionsPostType = $this->getLoadExceptionsPostType(self::$vars['current_post_type']); $anyAssetsLoadExceptions = ( ! empty( $this->loadExceptionsPageLevel[ $assetType ] ) || ( ! empty( $this->loadExceptionsLoggedInGlobal[ $assetType ] ) ) || ( ! empty( $this->loadExceptionsPostType[$assetType] ) ) ); if ( $anyAssetsLoadExceptions ) { foreach ( $list as $handleKey => $handle ) { $loadAssetAsException = in_array( $handle, $this->loadExceptionsPageLevel[ $assetType ] ) // 1) per page, per group pages OR || in_array( $handle, $this->loadExceptionsPostType[ $assetType ] ) // 2) On all pages belonging to a specific post type || ( in_array( $handle, $this->loadExceptionsLoggedInGlobal[ $assetType ] ) && is_user_logged_in() ); // 3) site-wide if the user is logged-in if ( $loadAssetAsException ) { unset( $list[ $handleKey ] ); } } } } return $list; } /** * Alter CSS/JS list marked for dequeue * @param $for * @return array */ public function unloadAssetOnTheFly($for) { $assetType = ($for === 'css') ? 'styles' : 'scripts'; $assetIndex = 'wpacu_unload_'.$for; if (! ($unloadAsset = Misc::getVar('get', $assetIndex))) { return array(); } $assetHandles = array(); if (strpos($unloadAsset, ',') === false) { // No comma, just one asset targeted if (strpos($unloadAsset, '[ignore-deps]') !== false) { $unloadAsset = str_replace('[ignore-deps]', '', $unloadAsset); $this->ignoreChildrenHandlesOnTheFly[$assetType][] = $unloadAsset; } $assetHandles[] = $unloadAsset; } else { // There are commas, multiple assets targeted foreach (explode(',', $unloadAsset) as $unloadAsset) { $unloadAsset = trim($unloadAsset); if ($unloadAsset) { if (strpos($unloadAsset, '[ignore-deps]') !== false) { $unloadAsset = str_replace('[ignore-deps]', '', $unloadAsset); $this->ignoreChildrenHandlesOnTheFly[$assetType][] = $unloadAsset; } $assetHandles[] = $unloadAsset; } } } return $assetHandles; } /** * @param $exceptionsList * * @return array */ public function makeLoadExceptionOnTheFly($exceptionsList) { $exceptionsListDebug = array('styles' => array(), 'scripts' => array()); foreach (array('css', 'js') as $assetExt) { $assetKey = ($assetExt === 'css') ? 'styles' : 'scripts'; $indexToCheck = 'wpacu_load_'.$assetExt; if ($loadAsset = Misc::getVar('get', $indexToCheck)) { if (strpos($loadAsset, ',') === false && (! in_array($loadAsset, $exceptionsList[$assetKey]))) { $exceptionsList[$assetKey][] = $loadAsset; $exceptionsListDebug[$assetKey][] = $loadAsset; } else { foreach (explode(',', $loadAsset) as $loadAsset) { if (($loadAsset = trim($loadAsset)) && (! in_array($loadAsset, $exceptionsList[$assetKey]))) { $exceptionsList[$assetKey][] = $loadAsset; $exceptionsListDebug[$assetKey][] = $loadAsset; } } } } } ObjectCache::wpacu_cache_add('wpacu_exceptions_list_page_request', $exceptionsListDebug); return $exceptionsList; } /** * This fetches the "Load it on this page" exceptions * * @param string $type ("post", "front_page") * @param string $postId * * @return array|array[] */ public function getLoadExceptionsPageLevel($type = 'post', $postId = '') { if ( $this->loadExceptionsPageLevel['_set'] ) { return $this->loadExceptionsPageLevel; // it was set } $exceptionsListDefault = $exceptionsList = array( 'styles' => array(), 'scripts' => array() ); if ( $type === 'post' && ! $postId ) { // $postId needs to have a value if $type is a 'post' type $this->loadExceptionsPageLevel = $exceptionsListDefault; $this->loadExceptionsPageLevel['_set'] = true; return $this->loadExceptionsPageLevel; } if ( ! $type ) { // Invalid request $this->loadExceptionsPageLevel = $exceptionsListDefault; $this->loadExceptionsPageLevel['_set'] = true; return $this->loadExceptionsPageLevel; } // Default $exceptionsListJson = ''; // Post or Post of the Homepage (if chosen in the Dashboard) if ( $type === 'post' || ( Misc::getShowOnFront() === 'page' && $postId ) ) { $exceptionsListJson = get_post_meta( $postId, '_' . WPACU_PLUGIN_ID . '_load_exceptions', true ); } // The home page could also be the list of the latest blog posts if ( $type === 'front_page' ) { $exceptionsListJson = get_option( WPACU_PLUGIN_ID . '_front_page_load_exceptions' ); } if ( $exceptionsListJson ) { $exceptionsList = json_decode( $exceptionsListJson, true ); if ( Misc::jsonLastError() !== JSON_ERROR_NONE ) { $exceptionsList = $exceptionsListDefault; } } // Any exceptions on the fly added for debugging purposes? Make sure to grab them $exceptionsList = $this->makeLoadExceptionOnTheFly( $exceptionsList ); // Avoid any notice errors foreach ( array( 'styles', 'scripts' ) as $assetType ) { if ( ! isset( $exceptionsList[ $assetType ] ) ) { $exceptionsList[ $assetType ] = array(); } } $this->loadExceptionsPageLevel = $exceptionsList; $this->loadExceptionsPageLevel['_set'] = true; return $this->loadExceptionsPageLevel; } /** * Option: 'Make an exception from any unload rule & always load it' -> 'On all pages of "post" post type' * * @param $postType * * @return \array[][] */ public function getLoadExceptionsPostType($postType) { if ($this->loadExceptionsPostType['_set']) { return $this->loadExceptionsPostType; } // Not on a singular page if ($postType === '') { $this->loadExceptionsPostType['_set'] = true; return $this->loadExceptionsPostType; } $exceptionsListDefault = array('styles' => array(), 'scripts' => array()); $exceptionsListJson = get_option(WPACU_PLUGIN_ID . '_post_type_load_exceptions'); $exceptionsList = @json_decode($exceptionsListJson, true); // Issues with decoding the JSON file? Return an empty list if (Misc::jsonLastError() !== JSON_ERROR_NONE) { $this->loadExceptionsPostType = $exceptionsListDefault; $this->loadExceptionsPostType['_set'] = true; return $this->loadExceptionsPostType; } // Return any handles added as load exceptions for the requested $postType if (isset($exceptionsList[$postType])) { foreach ( $exceptionsList[$postType] as $assetType => $assetList ) { foreach ( $assetList as $assetHandle => $assetValue ) { if ( $assetValue ) { $this->loadExceptionsPostType[ $assetType ][] = $assetHandle; } } } } $this->loadExceptionsPostType['_set'] = true; return $this->loadExceptionsPostType; } /** * Option: Unload site-wide * everywhere * * @return array */ public function getGlobalUnload() { $existingListEmpty = array('styles' => array(), 'scripts' => array()); $existingListJson = get_option( WPACU_PLUGIN_ID . '_global_unload'); $existingListData = $this->existingList($existingListJson, $existingListEmpty); // No 'styles' or 'scripts' - Set them as empty to avoid any PHP warning errors foreach ( array('styles', 'scripts') as $assetType ) { if ( ! isset( $existingListData['list'][$assetType] ) || ! is_array( $existingListData['list'][$assetType] ) ) { $existingListData['list'][$assetType] = array(); } } return $existingListData['list']; } /** * @param string $for (could be 'post_type', 'taxonomy' for the Pro version etc.) * @param string $type * * @return array */ public function getBulkUnload($for, $type = 'all') { $existingListEmpty = array('styles' => array(), 'scripts' => array()); $existingListAllJson = get_option( WPACU_PLUGIN_ID . '_bulk_unload'); if (! $existingListAllJson) { return $existingListEmpty; } $existingListAll = json_decode($existingListAllJson, true); if (Misc::jsonLastError() !== JSON_ERROR_NONE) { return $existingListEmpty; } $existingList = $existingListEmpty; foreach (array('styles', 'scripts') as $assetType) { if ( isset( $existingListAll[$assetType][ $for ][ $type ] ) && is_array( $existingListAll[$assetType][ $for ][ $type ] ) ) { $existingList[$assetType] = $existingListAll[$assetType][ $for ][ $type ]; } } return $existingList; } /** * Option: Add Note * * @return array */ public function getHandleNotes() { $handleNotes = array('styles' => array(), 'scripts' => array()); $handleNotesListJson = get_option(WPACU_PLUGIN_ID . '_global_data'); if ($handleNotesListJson) { $handleNotesList = @json_decode($handleNotesListJson, true); // Issues with decoding the JSON file? Return an empty list if (Misc::jsonLastError() !== JSON_ERROR_NONE) { return $handleNotes; } // Are new positions set for styles and scripts? foreach (array('styles', 'scripts') as $assetKey) { if ( isset( $handleNotesList[$assetKey]['notes'] ) && ! empty( $handleNotesList[$assetKey]['notes'] ) ) { $handleNotes[$assetKey] = $handleNotesList[$assetKey]['notes']; } } } return $handleNotes; } /** * Option: 'Make an exception from any unload rule & always load it' -> 'If the user is logged-in' * * @return array */ public function getHandleLoadLoggedIn() { if ($this->loadExceptionsLoggedInGlobal['_set']) { return $this->loadExceptionsLoggedInGlobal; } $targetGlobalKey = 'load_it_logged_in'; $handleData = array( 'styles' => array(), 'scripts' => array() ); $handleDataListJson = get_option( WPACU_PLUGIN_ID . '_global_data' ); if ( $handleDataListJson ) { $handleDataList = @json_decode( $handleDataListJson, true ); // Issues with decoding the JSON file? Return an empty list if ( Misc::jsonLastError() !== JSON_ERROR_NONE ) { $this->loadExceptionsLoggedInGlobal = $handleData; $this->loadExceptionsLoggedInGlobal['_set'] = true; return $handleData; } // Are load exceptions set for styles and scripts? foreach ( array( 'styles', 'scripts' ) as $assetKey ) { if ( isset( $handleDataList[ $assetKey ][ $targetGlobalKey ] ) && ! empty( $handleDataList[ $assetKey ][ $targetGlobalKey ] ) ) { $handleData[ $assetKey ] = array_keys($handleDataList[ $assetKey ][ $targetGlobalKey ]); } } } $this->loadExceptionsLoggedInGlobal = $handleData; // Avoid any PHP notice errors foreach (array('styles', 'scripts') as $assetType) { if ( ! isset($this->loadExceptionsLoggedInGlobal[$assetType]) ) { $this->loadExceptionsLoggedInGlobal[$assetType] = array(); } } $this->loadExceptionsLoggedInGlobal['_set'] = true; return $this->loadExceptionsLoggedInGlobal; } /** * Option: 'Ignore dependency rule and keep the "children" loaded' * * @return array */ public function getIgnoreChildren() { if (empty($this->ignoreChildren)) { $ignoreChildListJson = get_option(WPACU_PLUGIN_ID . '_global_data'); if ($ignoreChildListJson) { $ignoreChildList = @json_decode($ignoreChildListJson, true); // Issues with decoding the JSON file? Return an empty list if (Misc::jsonLastError() !== JSON_ERROR_NONE) { return $this->ignoreChildren; } // Are ignore "children" rules set for styles and scripts? foreach (array('styles', 'scripts') as $assetKey) { if (isset($ignoreChildList[$assetKey]['ignore_child']) && $ignoreChildList[$assetKey]['ignore_child']) { $this->ignoreChildren[$assetKey] = $ignoreChildList[$assetKey]['ignore_child']; } } } } return $this->ignoreChildren; } /** * @return array */ public static function getHandlesInfo() { $assetsInfo = array('styles' => array(), 'scripts' => array()); $wpacuGlobalDataJson = get_option(WPACU_PLUGIN_ID . '_global_data'); $wpacuGlobalData = json_decode($wpacuGlobalDataJson, ARRAY_A); if (Misc::jsonLastError() === JSON_ERROR_NONE) { foreach (array('styles', 'scripts') as $assetKey) { if ( isset( $wpacuGlobalData[$assetKey]['assets_info'] ) && ! empty( $wpacuGlobalData[$assetKey]['assets_info'] ) ) { $assetsInfo[$assetKey] = Misc::filterList( $wpacuGlobalData[$assetKey]['assets_info'] ); } } } // Fallback for those who still use the old transient way of fetching the assets info if ($assetsInfoTransient = get_transient(WPACU_PLUGIN_ID . '_assets_info')) { $assetsInfoTransientArray = @json_decode($assetsInfoTransient, ARRAY_A); if (is_array($assetsInfoTransientArray) && ! empty($assetsInfoTransientArray)) { foreach ($assetsInfoTransientArray as $assetKeyTransient => $handlesList) { if (! in_array($assetKeyTransient, array('styles', 'scripts'))) { continue; } foreach ($handlesList as $handleName => $handleData) { if (! isset($assetsInfo[$assetKeyTransient][$handleName])) { $assetsInfo[$assetKeyTransient][$handleName] = $handleData; } } } } } return $assetsInfo; } /** * @param $ignoreChildParentList * * @return array */ public function filterIgnoreChildParentList($ignoreChildParentList) { if (isset($this->ignoreChildrenHandlesOnTheFly['styles']) && ! empty($this->ignoreChildrenHandlesOnTheFly['styles'])) { foreach ($this->ignoreChildrenHandlesOnTheFly['styles'] as $cssHandle) { $ignoreChildParentList['styles'][$cssHandle] = 1; } } if (isset($this->ignoreChildrenHandlesOnTheFly['scripts']) && ! empty($this->ignoreChildrenHandlesOnTheFly['scripts'])) { foreach ($this->ignoreChildrenHandlesOnTheFly['scripts'] as $jsHandle) { $ignoreChildParentList['scripts'][$jsHandle] = 1; } } return $ignoreChildParentList; } /** * */ public function saveHeadAssets() { global $wp_styles, $wp_scripts; if (isset($this->wpAllStyles['queue']) && ! empty($this->wpAllStyles['queue'])) { $this->stylesInHead = $this->wpAllStyles['queue']; } if (isset($wp_styles->queue) && ! empty($wp_styles->queue)) { foreach ($wp_styles->queue as $styleHandle) { $this->stylesInHead[] = $styleHandle; } } $this->stylesInHead = array_unique($this->stylesInHead); if (isset($this->wpAllScripts['queue']) && ! empty($this->wpAllScripts['queue'])) { $this->scriptsInHead = $this->wpAllScripts['queue']; } if (isset($wp_scripts->queue) && ! empty($wp_scripts->queue)) { foreach ($wp_scripts->queue as $scriptHandle) { $this->scriptsInHead[] = $scriptHandle; } } $this->scriptsInHead = array_unique($this->scriptsInHead); } /** * */ public function saveFooterAssets() { global $wp_scripts, $wp_styles; // [Styles Collection] $footerStyles = array(); if (isset($this->wpAllStyles['queue']) && ! empty($this->wpAllStyles['queue'])) { foreach ( $this->wpAllStyles['queue'] as $handle ) { if ( ! in_array( $handle, $this->stylesInHead ) ) { $footerStyles[] = $handle; } } } if (isset($wp_styles->queue) && ! empty($wp_styles->queue)) { foreach ( $wp_styles->queue as $handle ) { if ( ! in_array( $handle, $this->stylesInHead ) ) { $footerStyles[] = $handle; } } } $this->assetsInFooter['styles'] = array_unique($footerStyles); // [/Styles Collection] // [Scripts Collection] $this->assetsInFooter['scripts'] = (isset($wp_scripts->in_footer) && ! empty($wp_scripts->in_footer)) ? $wp_scripts->in_footer : array(); if (isset($this->wpAllScripts['queue']) && ! empty($this->wpAllScripts['queue'])) { foreach ( $this->wpAllScripts['queue'] as $handle ) { if ( ! in_array( $handle, $this->scriptsInHead ) ) { $this->assetsInFooter['scripts'][] = $handle; } } } if (isset($wp_scripts->queue) && ! empty($wp_scripts->queue)) { foreach ( $wp_scripts->queue as $handle ) { if ( ! in_array( $handle, $this->scriptsInHead ) ) { $this->assetsInFooter['scripts'][] = $handle; } } } $this->assetsInFooter['scripts'] = array_unique($this->assetsInFooter['scripts']); // [/Scripts Collection] } /** * This output will be extracted and the JSON will be processed * in the WP Dashboard when editing a post * * It will also print the asset list in the front-end * if the option was enabled in the Settings */ public function printScriptsStyles() { // Not for WordPress AJAX calls if (self::$domGetType === 'direct' && defined('DOING_AJAX') && DOING_AJAX) { return; } $isFrontEndEditView = $this->isFrontendEditView; $isDashboardEditView = (! $isFrontEndEditView && $this->isGetAssetsCall); if (! $isFrontEndEditView && ! $isDashboardEditView) { return; } if ($isFrontEndEditView && isset($_GET['elementor-preview']) && $_GET['elementor-preview']) { return; } /* [wpacu_timing] */ $wpacuTimingName = 'output_css_js_manager'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */ // Prevent plugins from altering the DOM add_filter('w3tc_minify_enable', '__return_false'); Misc::w3TotalCacheFlushObjectCache(); // This is the list of the scripts and styles that were eventually loaded // We have also the list of the ones that were unloaded // located in $this->wpScripts and $this->wpStyles // We will add it to the list as they will be marked $stylesBeforeUnload = $this->wpAllStyles; $scriptsBeforeUnload = $this->wpAllScripts; global $wp_scripts, $wp_styles; $list = array(); // e.g. for "Loaded" and "Unloaded" statuses $currentUnloadedAll = isset($this->allUnloadedAssets) ? $this->allUnloadedAssets : array('styles' => array(), 'scripts' => array()); foreach (array('styles', 'scripts') as $assetType) { if ( isset( $currentUnloadedAll[$assetType] ) ) { $currentUnloadedAll[$assetType] = array_unique( $currentUnloadedAll[$assetType] ); } } $manageStylesCore = isset($wp_styles->done) && is_array($wp_styles->done) ? $wp_styles->done : array(); $manageStyles = $this->wpStylesFilter($manageStylesCore, 'done'); $manageScripts = isset($wp_scripts->done) && is_array($wp_scripts->done) ? $wp_scripts->done : array(); if ($isFrontEndEditView) { if (! empty($this->wpAllStyles) && isset($this->wpAllStyles['queue'])) { $manageStyles = $this->wpStylesFilter($this->wpAllStyles['queue'], 'queue'); } if (! empty($this->wpAllScripts) && isset($this->wpAllScripts['queue'])) { $manageScripts = $this->wpAllScripts['queue']; } if (! empty($currentUnloadedAll['styles'])) { foreach ( $currentUnloadedAll['styles'] as $currentUnloadedStyleHandle ) { if ( ! in_array( $currentUnloadedStyleHandle, $manageStyles ) ) { $manageStyles[] = $currentUnloadedStyleHandle; } } } if (! empty($manageStylesCore)) { foreach ($manageStylesCore as $wpDoneStyle) { if ( ! in_array( $wpDoneStyle, $manageStyles ) ) { $manageStyles[] = $wpDoneStyle; } } } $manageStyles = array_unique($manageStyles); if (! empty($currentUnloadedAll['scripts'])) { foreach ( $currentUnloadedAll['scripts'] as $currentUnloadedScriptHandle ) { if ( ! in_array( $currentUnloadedScriptHandle, $manageScripts ) ) { $manageScripts[] = $currentUnloadedScriptHandle; } } } if (isset($wp_scripts->done) && ! empty($wp_scripts->done)) { foreach ($wp_scripts->done as $wpDoneScript) { if ( ! in_array( $wpDoneScript, $manageScripts ) ) { $manageScripts[] = $wpDoneScript; } } } $manageScripts = array_unique($manageScripts); } /* * Style List */ if ($isFrontEndEditView) { // "Manage in the Front-end" $stylesList = $stylesBeforeUnload['registered']; } else { // "Manage in the Dashboard" $stylesListFilterAll = $this->wpStylesFilter($wp_styles, 'registered'); $stylesList = $stylesListFilterAll->registered; } if (! empty($stylesList)) { foreach ($manageStyles as $handle) { if (! isset($stylesList[$handle]) || in_array($handle, $this->skipAssets['styles'])) { continue; } $list['styles'][] = $stylesList[$handle]; } // Append unloaded ones (if any) if (! empty($stylesBeforeUnload) && ! empty($currentUnloadedAll['styles'])) { foreach ($currentUnloadedAll['styles'] as $sbuHandle) { if (! in_array($sbuHandle, $manageStyles)) { // Could be an old style that is not loaded anymore // We have to check that if (! isset($stylesBeforeUnload['registered'][$sbuHandle])) { continue; } $sbuValue = $stylesBeforeUnload['registered'][$sbuHandle]; $list['styles'][] = $sbuValue; } } } ksort($list['styles']); } /* * Scripts List */ $scriptsList = $wp_scripts->registered; if ($isFrontEndEditView) { $scriptsList = $scriptsBeforeUnload['registered']; } if (! empty($scriptsList)) { /* These scripts below are used by this plugin (except admin-bar) and they should not show in the list as they are loaded only when you (or other admin) manage the assets, never for your website visitors */ foreach ($manageScripts as $handle) { if (! isset($scriptsList[$handle]) || in_array($handle, $this->skipAssets['scripts'])) { continue; } $list['scripts'][] = $scriptsList[$handle]; } // Append unloaded ones (if any) if (! empty($scriptsBeforeUnload) && ! empty($currentUnloadedAll['scripts'])) { foreach ($currentUnloadedAll['scripts'] as $sbuHandle) { if (! in_array($sbuHandle, $manageScripts)) { // Could be an old script that is not loaded anymore // We have to check that if (! isset($scriptsBeforeUnload['registered'][$sbuHandle])) { continue; } $sbuValue = $scriptsBeforeUnload['registered'][$sbuHandle]; $list['scripts'][] = $sbuValue; } } } ksort($list['scripts']); } if (! empty($list)) { Update::updateHandlesInfo( $list ); } // [wpacu_lite] if ($isFrontEndEditView && ! $this->isUpdateable) { $this->parseTemplate('settings-frontend-lite-locked', array(), true); } // [/wpacu_lite] // Front-end View while admin is logged in elseif ($isFrontEndEditView) { $wpacuSettings = new Settings(); $data = array( 'is_frontend_view' => true, 'post_type' => '', 'bulk_unloaded' => array( 'post_type' => array() ), 'plugin_settings' => $wpacuSettings->getAll(), 'current_unloaded_all' => $currentUnloadedAll, 'current_unloaded_page_level' => $this->getAssetsUnloadedPageLevel( $this->getCurrentPostId(), true ) ); $data['wpacu_page_just_updated'] = false; if (isset($_GET['wpacu_time'], $_GET['nocache']) && get_transient('wpacu_page_just_updated')) { $data['wpacu_page_just_updated'] = true; delete_transient('wpacu_page_just_updated'); } if ($currentDebug = ObjectCache::wpacu_cache_get('wpacu_assets_unloaded_list_page_request')) { foreach ( array( 'styles', 'scripts' ) as $assetType ) { if ( isset( $data['current_unloaded_all'][ $assetType ] ) && ! empty( $data['current_unloaded_all'][ $assetType ] ) ) { foreach ( $data['current_unloaded_all'][ $assetType ] as $handleKey => $handle ) { if ( isset( $currentDebug[ $assetType ] ) && in_array( $handle, $currentDebug[ $assetType ] ) ) { unset( $data['current_unloaded_all'][ $assetType ][ $handleKey ] ); } } } } } // e.g. /?wpacu_unload_(css|js)= $data['current_debug'] = ObjectCache::wpacu_cache_get('wpacu_assets_unloaded_list_page_request'); $data['all']['scripts'] = $list['scripts']; $data['all']['styles'] = $list['styles']; if ($data['plugin_settings']['assets_list_layout'] === 'by-location') { $data['all'] = Sorting::appendLocation($data['all']); } else { $data['all'] = Sorting::sortListByAlpha($data['all']); } $this->fetchUrl = Misc::getPageUrl($this->getCurrentPostId()); $data['fetch_url'] = $this->fetchUrl; $data['nonce_action'] = Update::NONCE_ACTION_NAME; $data['nonce_name'] = Update::NONCE_FIELD_NAME; $data = $this->alterAssetObj($data); $data['global_unload'] = $this->globalUnloaded; $type = false; if (Misc::isHomePage() && $this->getCurrentPostId() < 1 && ! get_option('page_on_front')) { $type = 'front_page'; } elseif ($this->getCurrentPostId() > 0) { $type = 'post'; } $data['wpacu_type'] = $type; $data['load_exceptions_per_page'] = $this->getLoadExceptionsPageLevel($type, $this->getCurrentPostId()); // Avoid the /?wpacu_load_(css|js) to interfere with the form inputs if ($loadExceptionsDebug = ObjectCache::wpacu_cache_get( 'wpacu_exceptions_list_page_request' )) { foreach ( array( 'styles', 'scripts' ) as $assetType ) { if ( isset( $data['load_exceptions_per_page'][ $assetType ], $loadExceptionsDebug[ $assetType ] ) && ! empty( $data['load_exceptions_per_page'][ $assetType ] ) ) { foreach ( $data['load_exceptions_per_page'][ $assetType ] as $handleKey => $handle ) { if ( in_array( $handle, $loadExceptionsDebug[ $assetType ] ) ) { unset( $data['load_exceptions_per_page'][ $assetType ][ $handleKey ] ); } } } } // e.g. /?wpacu_load_(css|js)= $data['load_exceptions_debug'] = $loadExceptionsDebug; } // WooCommerce Shop Page? $data['is_woo_shop_page'] = self::$vars['is_woo_shop_page']; $data['is_bulk_unloadable'] = $data['bulk_unloaded_type'] = false; $data['bulk_unloaded']['post_type'] = array('styles' => array(), 'scripts' => array()); $data['load_exceptions_post_type'] = array(); if (self::isSingularPage()) { $post = $this->getCurrentPost(); $data['post_id'] = $post->ID; // Current Post Type $data['post_type'] = $post->post_type; $data['load_exceptions_post_type'] = $this->getLoadExceptionsPostType($data['post_type']); // Are there any assets unloaded for this specific post type? // (e.g. page, post, product (from WooCommerce) or other custom post type) $data['bulk_unloaded']['post_type'] = $this->getBulkUnload('post_type', $data['post_type']); $data['bulk_unloaded_type'] = 'post_type'; $data['is_bulk_unloadable'] = true; $data['post_type_has_tax_assoc'] = self::getAllSetTaxonomies($data['post_type']); $data = $this->setPageTemplate($data); } $data['total_styles'] = ! empty($data['all']['styles']) ? count($data['all']['styles']) : false; $data['total_scripts'] = ! empty($data['all']['scripts']) ? count($data['all']['scripts']) : false; $data['all_deps'] = $this->getAllDeps($data['all']); $data['preloads'] = Preloads::instance()->getPreloads(); // Load exception: If the user is logged in (applies globally) $data['handle_load_logged_in'] = $this->getHandleLoadLoggedIn(); $data['handle_notes'] = $this->getHandleNotes(); $data['handle_rows_contracted'] = self::getHandleRowStatus(); $data['ignore_child'] = $this->getIgnoreChildren(); switch (assetCleanUpHasNoLoadMatches($data['fetch_url'])) { case 'is_set_in_settings': // The rules from "Settings" -> "Plugin Usage Preferences" -> "Do not load the plugin on certain pages" will be checked $data['status'] = 5; break; case 'is_set_in_page': // The following option from "Page Options" (within the CSS/JS manager of the targeted page) is set: "Do not load Asset CleanUp Pro on this page (this will disable any functionality of the plugin)" $data['status'] = 6; break; default: $data['status'] = 1; } $data['page_options'] = array(); $data['show_page_options'] = false; if (in_array($type, array('post', 'front_page'))) { $data['show_page_options'] = true; $data['page_options'] = MetaBoxes::getPageOptions($this->getCurrentPostId(), $type); } $data['post_id'] = ($type === 'front_page') ? 0 : $this->getCurrentPostId(); ObjectCache::wpacu_cache_set('wpacu_settings_frontend_data', $data); $this->parseTemplate('settings-frontend', $data, true); } elseif ($isDashboardEditView && ! isset($_GET['wpacu_just_hardcoded'])) { // AJAX call (not the classic WP one) from the WP Dashboard // Send the altered value that has the initial position too // Taken front the front-end view $data = array(); $data['all']['scripts'] = $list['scripts']; $data['all']['styles'] = $list['styles']; $data = $this->alterAssetObj($data); $list['styles'] = $data['all']['styles']; $list['scripts'] = $data['all']['scripts']; if ( isset($_GET['wpacu_print']) ) { echo '<!-- '."\n".print_r(Misc::filterList($list), true)."\n".' -->'; } // e.g. for "Loaded" and "Unloaded" statuses $list['current_unloaded_all'] = isset($this->allUnloadedAssets) ? $this->allUnloadedAssets : array('styles' => array(), 'scripts' => array()); echo self::START_DEL_ENQUEUED . base64_encode(wp_json_encode($list)) . self::END_DEL_ENQUEUED; // Loaded via wp_enqueue_scripts() echo self::START_DEL_HARDCODED . '{wpacu_hardcoded_assets}' . self::END_DEL_HARDCODED; // Make the user aware of any hardcoded CSS/JS (if any) add_action('shutdown', static function() { // Do not allow further processes as cache plugins such as W3 Total Cache could alter the source code // and we need the non-minified version of the DOM (e.g. to determine the position of the elements) exit(); }); } elseif ($isDashboardEditView && isset($_GET['wpacu_just_hardcoded'])) { // AJAX call just for the hardcoded assets echo self::START_DEL_HARDCODED . '{wpacu_hardcoded_assets}' . self::END_DEL_HARDCODED; // Make the user aware of any hardcoded CSS/JS (if any) add_action('shutdown', static function() { // Do not allow further processes as cache plugins such as W3 Total Cache could alter the source code, // and we need the non-minified version of the DOM (e.g. to determine the position of the elements) exit(); }); } /* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */ } /** * @param $name * @param array $data (if present $data values are used within the included template) * @param bool|false $echo * @param bool|false $returnData (relevant when $echo is set to true) * @return string|array */ public function parseTemplate($name, $data = array(), $echo = false, $returnData = false) { $templateFile = apply_filters( 'wpacu_template_file', // tag dirname(__DIR__) . '/templates/' . $name . '.php', // value $name // extra argument ); if (! is_file($templateFile)) { return 'Template '.$templateFile.' not found.'; } ob_start(); include $templateFile; $result = ob_get_clean(); // $echo is set to true ($returnData is not relevant), thus print the output if ($echo) { echo $result; return true; } // $echo is set to false and $returnData to true if ($returnData) { return array( 'output' => $result, 'data' => $data ); } /// $echo is set to false (default) and $returnData to $false (default) return $result; } /** * */ public function ajaxGetJsonListCallback() { if ( ! isset($_POST['wpacu_nonce']) ) { echo 'Error: The security nonce was not sent for verification. Location: '.__METHOD__; return; } if ( ! wp_verify_nonce($_POST['wpacu_nonce'], 'wpacu_ajax_get_loaded_assets_nonce') ) { echo 'Error: The nonce security check has failed. Location: '.__METHOD__; return; } $postId = (int)Misc::getVar('post', 'post_id'); // if any (could be home page for instance) $pageUrl = Misc::getVar('post', 'page_url'); // post, page, custom post type, home page etc. $postStatus = $postId > 0 ? get_post_status($postId) : false; // Not homepage, but a post/page? Check if it's published in case AJAX call // wasn't stopped due to JS errors or other reasons if ($postId > 0 && ! in_array($postStatus, array('publish', 'private'))) { exit(__('The CSS/JS files will be available to manage once the post/page is published.', 'wp-asset-clean-up')); } if ($postId > 0) { $type = 'post'; } elseif ($postId == 0) { $type = 'front_page'; } $wpacuListE = $wpacuListH = ''; $settings = new Settings(); // If the post status is 'private' only direct method can be used to fetch the assets // as the remote post one will return a 404 error since the page is accessed as a guest visitor if (self::$domGetType === 'direct' || $postStatus === 'private') { $wpacuListE = Misc::getVar('post', 'wpacu_list_e'); $wpacuListH = Misc::getVar('post', 'wpacu_list_h'); } elseif (self::$domGetType === 'wp_remote_post') { $wpRemotePost = wp_remote_post($pageUrl, array( 'body' => array( WPACU_LOAD_ASSETS_REQ_KEY => 1 ) )); $contents = (is_array($wpRemotePost) && isset($wpRemotePost['body']) && (! is_wp_error($wpRemotePost))) ? $wpRemotePost['body'] : ''; // Enqueued List if ($contents && ( strpos($contents, self::START_DEL_ENQUEUED) !== false) && ( strpos($contents, self::END_DEL_ENQUEUED) !== false)) { // Enqueued CSS/JS (most of them or all) $wpacuListE = Misc::extractBetween( $contents, self::START_DEL_ENQUEUED, self::END_DEL_ENQUEUED ); } // Hardcoded List if ($contents && ( strpos($contents, self::START_DEL_HARDCODED) !== false) && ( strpos($contents, self::END_DEL_HARDCODED) !== false)) { // Hardcoded (if any) $wpacuListH = Misc::extractBetween( $contents, self::START_DEL_HARDCODED, self::END_DEL_HARDCODED ); } // The list of assets COULD NOT be retrieved via "WP Remote Post" for this server // EITHER the enqueued or hardcoded list of assets HAS TO BE RETRIEVED // Print out the 'error' response to make the user aware about it if ( ! ($wpacuListE || $wpacuListH) ) { // 'body' is set, and it's not an array if ( is_wp_error($wpRemotePost) ) { $wpRemotePost['response']['message'] = $wpRemotePost->get_error_message(); } elseif ( isset( $wpRemotePost['body']) ) { if ( trim( $wpRemotePost['body'] ) === '' ) { $wpRemotePost['body'] = '<strong>Error (blank page):</strong> It looks the targeted page is loading, but it has no content. The page seems to be blank. Please load it in incognito mode (when you are not logged-in) via your browser.'; } elseif ( ! is_array( $wpRemotePost['body'] ) ) { $wpRemotePost['body'] = strip_tags( $wpRemotePost['body'], '<p><a><strong><b><em><i>' ); } } $data = array( 'is_dashboard_view' => true, 'plugin_settings' => $settings->getAll(), 'wp_remote_post' => $wpRemotePost ); if (isset($type) && $type) { $data['page_options'] = MetaBoxes::getPageOptions( $postId, $type ); } $this->parseTemplate('meta-box-loaded', $data, true); exit(); } } $data = array( 'is_dashboard_view' => true, 'post_id' => $postId, 'plugin_settings' => $settings->getAll() ); // [START] Enqueued CSS/JS (most of them or all) $jsonE = base64_decode($wpacuListE); $data['all'] = (array)json_decode($jsonE); // Make sure if there are no STYLES enqueued, the list will be empty to avoid any notice errors if (! isset($data['all']['styles'])) { $data['all']['styles'] = array(); } // Make sure if there are no SCRIPTS enqueued, the list will be empty to avoid any notice errors if (! isset($data['all']['scripts'])) { $data['all']['scripts'] = array(); } // [END] Enqueued CSS/JS (most of them or all) // [START] Hardcoded (if any) if ($wpacuListH) { // Only set the following variables if there is at least one hardcoded LINK/STYLE/SCRIPT $jsonH = base64_decode( $wpacuListH ); $data['all']['hardcoded'] = (array) json_decode( $jsonH, ARRAY_A ); if (isset($data['all']['hardcoded']['within_conditional_comments']) && ! empty($data['all']['hardcoded']['within_conditional_comments'])) { ObjectCache::wpacu_cache_set( 'wpacu_hardcoded_content_within_conditional_comments', $data['all']['hardcoded']['within_conditional_comments'] ); } } // [END] Hardcoded (if any) $data['current_unloaded_page_level'] = $this->getAssetsUnloadedPageLevel( $postId, true ); // e.g. for "Loaded" and "Unloaded" statuses $data['current_unloaded_all'] = isset($data['all']['current_unloaded_all']) ? (array)$data['all']['current_unloaded_all'] : array('styles' => array(), 'scripts' => array()); if ($data['plugin_settings']['assets_list_layout'] === 'by-location') { $data['all'] = Sorting::appendLocation($data['all']); } else { $data['all'] = Sorting::sortListByAlpha($data['all']); } $data['fetch_url'] = $pageUrl; $data['global_unload'] = $this->getGlobalUnload(); // [wpacu_pro] //ObjectCache::wpacu_cache_set('wpacu_data_global_unload', $data['global_unload']); // [/wpacu_pro] $data['is_bulk_unloadable'] = $data['bulk_unloaded_type'] = false; $data['bulk_unloaded']['post_type'] = array('styles' => array(), 'scripts' => array()); // Post Information if ($postId > 0) { $postData = get_post($postId); if (isset($postData->post_type) && $postData->post_type) { // Current Post Type $data['post_type'] = $postData->post_type; // Are there any assets unloaded for this specific post type? // (e.g. page, post, product (from WooCommerce) or other custom post type) $data['bulk_unloaded']['post_type'] = $this->getBulkUnload('post_type', $data['post_type']); $data['bulk_unloaded_type'] = 'post_type'; $data['is_bulk_unloadable'] = true; $data['post_type_has_tax_assoc'] = self::getAllSetTaxonomies($data['post_type']); } } // DO NOT alter any position as it's already verified and set // This AJAX call is for printing the assets that were already fetched $data = $this->alterAssetObj($data, false); $data['wpacu_type'] = $type; // e.g. LITE rules: Load it on this page & on all pages of a specific post type $data['load_exceptions_per_page'] = $this->getLoadExceptionsPageLevel($type, $postId); $data['load_exceptions_post_type'] = ($type === 'post' && $data['post_type']) ? $this->getLoadExceptionsPostType($data['post_type']) : array(); $data['handle_rows_contracted'] = self::getHandleRowStatus(); $data['total_styles'] = ! empty($data['all']['styles']) ? count($data['all']['styles']) : 0; $data['total_scripts'] = ! empty($data['all']['scripts']) ? count($data['all']['scripts']) : 0; $data['all_deps'] = $this->getAllDeps($data['all']); $data['preloads'] = Preloads::instance()->getPreloads(); $data['handle_load_logged_in'] = $this->getHandleLoadLoggedIn(); $data['handle_notes'] = $this->getHandleNotes(); $data['ignore_child'] = $this->getIgnoreChildren(); $data['is_for_singular'] = (Misc::getVar('post', 'is_for_singular') === 'true'); $data['page_options'] = array(); $data['show_page_options'] = false; if (in_array($type, array('post', 'front_page'))) { $data['show_page_options'] = true; $data['page_options'] = MetaBoxes::getPageOptions($postId, $type); } $this->parseTemplate('meta-box-loaded', $data, true); exit(); } /** * */ public function ajaxLoadRestrictedPageAreaCallback() { if ( ! isset( $_POST['wpacu_nonce'] ) || ! wp_verify_nonce( $_POST['wpacu_nonce'], 'wpacu_ajax_load_page_restricted_area_nonce' ) ) { echo 'Error: The security nonce is not valid.'; exit(); } $postId = (int)Misc::getVar('post', 'post_id'); // if any (could be home page for instance) $data = array(); $data['post_id'] = $this->currentPostId = $postId; $data['fetch_url'] = Misc::getPageUrl($postId); $data['show_page_options'] = true; $data['page_options'] = MetaBoxes::getPageOptions($postId); $post = get_post($postId); // Current Post Type $data['post_type'] = $post->post_type; $data['bulk_unloaded_type'] = 'post_type'; $data['is_bulk_unloadable'] = true; $data = $this->setPageTemplate($data); switch (assetCleanUpHasNoLoadMatches($data['fetch_url'])) { case 'is_set_in_settings': // The rules from "Settings" -> "Plugin Usage Preferences" -> "Do not load the plugin on certain pages" will be checked $data['status'] = 5; break; case 'is_set_in_page': // The following option from "Page Options" (within the CSS/JS manager of the targeted page) is set: "Do not load Asset CleanUp Pro on this page (this will disable any functionality of the plugin)" $data['status'] = 6; break; default: $data['status'] = 1; } $this->parseTemplate('meta-box-restricted-page-load', $data, true); exit(); } /** * */ public function ajaxPrintLoadedHardcodedAssets() { if ( ! isset( $_POST['wpacu_nonce'] ) || ! wp_verify_nonce( $_POST['wpacu_nonce'], 'wpacu_print_loaded_hardcoded_assets_nonce' ) ) { echo 'Error: The security nonce is not valid.'; exit(); } $wpacuListH = Misc::getVar('post', 'wpacu_list_h'); $wpacuSettingsJson = base64_decode(Misc::getVar('post', 'wpacu_settings')); $wpacuSettings = (array)json_decode($wpacuSettingsJson, ARRAY_A); // $data values are passed here // Only set the following variables if there is at least one hardcoded LINK/STYLE/SCRIPT $jsonH = base64_decode( $wpacuListH ); function wpacuPrintHardcodedManagementList($jsonH, $wpacuSettings) { $data = $wpacuSettings ?: array(); $data['do_not_print_list'] = true; $data['print_outer_html'] = false; $data['all']['hardcoded'] = (array)json_decode($jsonH, ARRAY_A); $totalHardcodedTags = 0; // default if (isset($data['all']['hardcoded']['within_conditional_comments']) && ! empty($data['all']['hardcoded']['within_conditional_comments'])) { ObjectCache::wpacu_cache_set( 'wpacu_hardcoded_content_within_conditional_comments', $data['all']['hardcoded']['within_conditional_comments'] ); } ob_start(); // $totalHardcodedTags is set here include_once WPACU_PLUGIN_DIR.'/templates/meta-box-loaded-assets/_assets-hardcoded-list.php'; // generate $hardcodedTagsOutput $output = ob_get_clean(); return wp_json_encode( array( 'output' => $output, 'total_hardcoded_assets' => $totalHardcodedTags ) ); } echo wpacuPrintHardcodedManagementList( $jsonH, $wpacuSettings ); exit(); } /** * */ public function ajaxCheckExternalUrlsForStatusCode() { if ( ! isset( $_POST['wpacu_nonce'] ) || ! wp_verify_nonce( $_POST['wpacu_nonce'], 'wpacu_ajax_check_external_urls_nonce' ) ) { echo 'Error: The security nonce is not valid.'; exit(); } if (! isset($_POST['action'], $_POST['wpacu_check_urls'])) { echo 'Error: The post parameters are not the right ones.'; exit(); } // Check privileges if (! Menu::userCanManageAssets()) { echo 'Error: Not enough privileges to perform this action.'; exit(); } $checkUrls = explode('-at-wpacu-at-', $_POST['wpacu_check_urls']); $checkUrls = array_filter(array_unique($checkUrls)); foreach ($checkUrls as $index => $checkUrl) { if (strpos($checkUrl, '//') === 0) { // starts with // (append the right protocol) if (strpos($checkUrl, 'fonts.googleapis.com') !== false) { $checkUrl = 'https:'.$checkUrl; } else { // either HTTP or HTTPS depending on the current page situation (that the admin has loaded) $checkUrl = (Misc::isHttpsSecure() ? 'https:' : 'http:') . $checkUrl; } } $response = wp_remote_get($checkUrl); // Remove 200 OK ones as the other ones will remain for highlighting if (wp_remote_retrieve_response_code($response) === 200) { unset($checkUrls[$index]); } } echo wp_json_encode($checkUrls); exit(); } /** * @return void */ public function ajaxFetchActivePluginsIconsFromWordPressOrg() { if ( ! isset($_POST['wpacu_nonce']) ) { echo 'Error: The security nonce was not sent for verification. Location: '.__METHOD__; return; } if ( ! wp_verify_nonce($_POST['wpacu_nonce'], 'wpacu_fetch_active_plugins_icons') ) { echo 'Error: The security check has failed. Location: '.__METHOD__; return; } if (! isset($_POST['action'])) { return; } if (! Menu::userCanManageAssets()) { return; } echo 'POST DATA: '.print_r($_POST, true)."\n\n"; echo '- Downloading from WordPress.org'."\n\n"; $activePluginsIcons = Misc::fetchActiveFreePluginsIconsFromWordPressOrg(); if ($activePluginsIcons && is_array($activePluginsIcons) && ! empty($activePluginsIcons)) { echo print_r($activePluginsIcons, true)."\n"; exit; } } /** * */ public function ajaxFetchActivePluginsJsFooterCode() { if (! Menu::isPluginPage()) { return; } if (! Menu::userCanManageAssets()) { return; } $forcePluginIconsDownload = isset($_GET['wpacu_force_plugin_icons_fetch']); $triggerPluginIconsDownload = $forcePluginIconsDownload || ! get_transient('wpacu_active_plugins_icons'); if (! $triggerPluginIconsDownload) { return; } ?> <script type="text/javascript" > jQuery(document).ready(function($) { var wpacuDataToSend = { 'action': '<?php echo WPACU_PLUGIN_ID.'_fetch_active_plugins_icons'; ?>', 'wpacu_nonce': '<?php echo wp_create_nonce('wpacu_fetch_active_plugins_icons'); ?>' }; jQuery.post(ajaxurl, wpacuDataToSend, function(response) { console.log(response); }); }); </script> <?php } /** * @param $data * @return mixed */ public function alterAssetObj($data, $alterPosition = true) { $siteUrl = get_site_url(); if (! empty($data['all']['styles'])) { $data['core_styles_loaded'] = false; foreach ($data['all']['styles'] as $key => $obj) { if (! isset($obj->handle)) { unset($data['all']['styles']['']); continue; } // From WordPress directories (false by default, unless it was set to true before: in Sorting.php for instance) if (! isset($data['all']['styles'][$key]->wp)) { $data['all']['styles'][$key]->wp = false; } if ($alterPosition) { if ( in_array( $obj->handle, $this->assetsInFooter['styles'] ) ) { $data['all']['styles'][ $key ]->position = 'body'; } else { $data['all']['styles'][ $key ]->position = 'head'; } } if (isset($data['all']['styles'][$key], $obj->src) && $obj->src) { $localSrc = Misc::getLocalSrc($obj->src); if (! empty($localSrc)) { $data['all']['styles'][$key]->baseUrl = $localSrc['base_url']; if (Sorting::matchesWpCoreCriteria($obj, 'styles')) { $data['all']['styles'][ $key ]->wp = true; $data['core_styles_loaded'] = true; } } // Determine source href (starting with '/' but not starting with '//') if (strpos($obj->src, '/') === 0 && strpos($obj->src, '//') !== 0) { $obj->srcHref = $siteUrl . $obj->src; } else { $obj->srcHref = $obj->src; } $data['all']['styles'][$key]->size = apply_filters('wpacu_get_asset_file_size', $data['all']['styles'][$key], 'for_print'); $data['all']['styles'][$key]->sizeRaw = apply_filters('wpacu_get_asset_file_size', $data['all']['styles'][$key], 'raw'); } } } if (! empty($data['all']['scripts'])) { $data['core_scripts_loaded'] = false; foreach ($data['all']['scripts'] as $key => $obj) { if (! isset($obj->handle)) { unset($data['all']['scripts']['']); continue; } // From WordPress directories (false by default, unless it was set to true before: in Sorting.php for instance) if (! isset($data['all']['scripts'][$key]->wp)) { $data['all']['scripts'][$key]->wp = false; } if ($alterPosition) { $initialScriptPos = ObjectCache::wpacu_cache_get( $obj->handle, 'wpacu_scripts_initial_positions' ); if ( $initialScriptPos === 'body' || in_array( $obj->handle, $this->assetsInFooter['scripts'] ) ) { $data['all']['scripts'][ $key ]->position = 'body'; } else { $data['all']['scripts'][ $key ]->position = 'head'; } } if (isset($data['all']['scripts'][$key])) { if (isset($obj->src) && $obj->src) { $localSrc = Misc::getLocalSrc($obj->src); if (! empty($localSrc)) { $data['all']['scripts'][$key]->baseUrl = $localSrc['base_url']; if (Sorting::matchesWpCoreCriteria($obj, 'scripts')) { $data['all']['scripts'][ $key ]->wp = true; $data['core_scripts_loaded'] = true; } } // Determine source href if (substr($obj->src, 0, 1) === '/' && substr($obj->src, 0, 2) !== '//') { $obj->srcHref = $siteUrl . $obj->src; } else { $obj->srcHref = $obj->src; } } if (in_array($obj->handle, array('jquery', 'jquery-core', 'jquery-migrate'))) { $data['all']['scripts'][$key]->wp = true; $data['core_scripts_loaded'] = true; } $data['all']['scripts'][$key]->size = apply_filters('wpacu_get_asset_file_size', $data['all']['scripts'][$key], 'for_print'); $data['all']['scripts'][$key]->sizeRaw = apply_filters('wpacu_get_asset_file_size', $data['all']['scripts'][$key], 'raw'); } } } return $data; } /** * This method retrieves only the assets that are unloaded per page * Including 404, date and search pages (they are considered as ONE page with the same rules for any URL variation) * * @param int $postId * @param bool $returnAsArray * * @return string|array (The returned value must be a JSON one) */ public function getAssetsUnloadedPageLevel($postId = 0, $returnAsArray = false) { $defaultEmptyArrayValue = array( 'styles' => array(), 'scripts' => array() ); // Post Type (Overwrites 'front' - home page - if we are in a singular post) if ($postId == 0) { $postId = (int)$this->getCurrentPostId(); } $isInAdminPageViaAjax = (is_admin() && defined('DOING_AJAX') && DOING_AJAX); $assetsRemovedPageLevel = wp_json_encode( $defaultEmptyArrayValue ); // For Home Page (latest blog posts) if ( $postId < 1 && ( $isInAdminPageViaAjax || Misc::isHomePage() ) ) { $assetsRemovedPageLevel = get_option( WPACU_PLUGIN_ID . '_front_page_no_load' ); } elseif ( $postId > 0 ) { // Singular Page $assetsRemovedPageLevel = get_post_meta( $postId, '_' . WPACU_PLUGIN_ID . '_no_load', true ); } @json_decode( $assetsRemovedPageLevel ); if ( ! ( Misc::jsonLastError() === JSON_ERROR_NONE ) || empty( $assetsRemovedPageLevel ) || $assetsRemovedPageLevel === '[]' ) { // Reset value to a JSON formatted one $assetsRemovedPageLevel = wp_json_encode( $defaultEmptyArrayValue ); } $assetsRemovedDecoded = json_decode( $assetsRemovedPageLevel, ARRAY_A ); if (! isset($assetsRemovedDecoded['styles'])) { $assetsRemovedDecoded['styles'] = array(); } if (! isset($assetsRemovedDecoded['scripts'])) { $assetsRemovedDecoded['scripts'] = array(); } /* [START] Unload CSS/JS on page request for debugging purposes */ $assetsUnloadedOnTheFly = $defaultEmptyArrayValue; if ( Misc::getVar( 'get', 'wpacu_unload_css' ) ) { $cssOnTheFlyList = $this->unloadAssetOnTheFly( 'css' ); if ( ! empty( $cssOnTheFlyList ) ) { foreach ( $cssOnTheFlyList as $cssHandle ) { if ( ! in_array( $cssHandle, $assetsRemovedDecoded['styles'] ) ) { $assetsRemovedDecoded['styles'][] = $assetsUnloadedOnTheFly['styles'][] = $cssHandle; } } } } if ( Misc::getVar( 'get', 'wpacu_unload_js' ) ) { $jsOnTheFlyList = $this->unloadAssetOnTheFly( 'js' ); if ( ! empty( $jsOnTheFlyList ) ) { foreach ( $jsOnTheFlyList as $jsHandle ) { if ( ! in_array( $jsHandle, $assetsRemovedDecoded['scripts'] ) ) { $assetsRemovedDecoded['scripts'][] = $assetsUnloadedOnTheFly['scripts'][] = $jsHandle; } } } } ObjectCache::wpacu_cache_add( 'wpacu_assets_unloaded_list_page_request', $assetsUnloadedOnTheFly ); /* [END] Unload CSS/JS on page request for debugging purposes */ $assetsRemovedPageLevel = wp_json_encode( $assetsRemovedDecoded ); if ($returnAsArray) { $assetsRemovedPageLevel = (array)@json_decode($assetsRemovedPageLevel); // Make sure there are no objects in the array to avoid any PHP errors later on in PHP 8+ foreach ( array( 'styles', 'scripts' ) as $assetType ) { if ( isset( $assetsRemovedPageLevel[ $assetType ] ) ) { $assetsRemovedPageLevel[ $assetType ] = (array)$assetsRemovedPageLevel[ $assetType ]; } } } // Hmm, perhaps one of the filters was incorrectly used and returned an array instead of a JSON format; autocorrect this to avoid PHP errors if (! $returnAsArray && is_array($assetsRemovedPageLevel)) { return wp_json_encode( $defaultEmptyArrayValue ); } return $assetsRemovedPageLevel; } /** * @param $allAssets * * @return array */ public function getAllDeps($allAssets) { $allDepsParentToChild = $allDepsChildToParent = array('styles' => array(), 'scripts' => array()); foreach (array('styles', 'scripts') as $assetType) { if ( ! (isset($allAssets[$assetType]) && ! empty($allAssets[$assetType])) ) { continue; } foreach ($allAssets[$assetType] as $assetObj) { if (isset($assetObj->deps) && ! empty($assetObj->deps)) { foreach ($assetObj->deps as $dep) { $allDepsParentToChild[$assetType][$dep][] = $assetObj->handle; $allDepsChildToParent[$assetType][$assetObj->handle][] = $dep; } } } } return array( 'parent_to_child' => $allDepsParentToChild, 'child_to_parent' => $allDepsChildToParent ); } /** * @param $obj * @param $format | 'for_print': Calculates the format in KB / MB - 'raw': The actual size in bytes * @return string */ public function getAssetFileSize($obj, $format = 'for_print') { if (isset($obj->src) && $obj->src) { $src = $obj->src; $siteUrl = site_url(); // Starts with / but not with // // Or starts with ../ (very rare cases) $isRelInternalPath = (strpos($src, '/') === 0 && strpos($src, '//') !== 0) || (strpos($src, '../') === 0); // Source starts with '//' - check if the file exists if (strpos($obj->src, '//') === 0) { list ($urlPrefix) = explode('//', $siteUrl); $srcToCheck = $urlPrefix . $obj->src; $hostSiteUrl = parse_url($siteUrl, PHP_URL_HOST); $hostSrc = parse_url($obj->src, PHP_URL_HOST); $siteUrlAltered = str_replace(array($hostSiteUrl, $hostSrc), '{site_host}', $siteUrl); $srcAltered = str_replace(array($hostSiteUrl, $hostSrc), '{site_host}', $srcToCheck); $srcMaybeRelPath = str_replace($siteUrlAltered, '', $srcAltered); $possibleStrips = array('?ver', '?cache='); foreach ($possibleStrips as $possibleStrip) { if ( strpos( $srcMaybeRelPath, $possibleStrip ) !== false ) { list ( $srcMaybeRelPath ) = explode( $possibleStrip, $srcMaybeRelPath ); } } if (is_file(Misc::getWpRootDirPath() . $srcMaybeRelPath)) { $fileSize = filesize(Misc::getWpRootDirPath() . $srcMaybeRelPath); if ($format === 'raw') { return (int)$fileSize; } return Misc::formatBytes($fileSize); } } // e.g. /?scss=1 (Simple Custom CSS Plugin) if (str_replace($siteUrl, '', $src) === '/?sccss=1') { $customCss = DynamicLoadedAssets::getSimpleCustomCss(); $sizeInBytes = mb_strlen($customCss); if ($format === 'raw') { return $sizeInBytes; } return Misc::formatBytes($sizeInBytes); } // External file? Use a different approach // Return an HTML code that will be parsed via AJAX through JavaScript $isExternalFile = (! $isRelInternalPath && (! (isset($obj->wp) && $obj->wp === 1)) && strpos($src, $siteUrl) !== 0); // e.g. /?scss=1 (Simple Custom CSS Plugin) From External Domain // /?custom-css (JetPack Custom CSS) $isLoadedOnTheFly = (strpos($src, '?sccss=1') !== false) || (strpos($src, '?custom-css') !== false); if ($isExternalFile || $isLoadedOnTheFly) { return '<a class="wpacu-external-file-size" data-src="' . $src . '" href="#">🔗 Get File Size</a>'. '<span style="display: none;"><img style="width: 20px; height: 20px;" alt="" align="top" width="20" height="20" src="'.includes_url('images/spinner-2x.gif').'"></span>'; } $forAssetType = $pathToFile = false; if ( stripos( $src, '.css' ) !== false ) { $forAssetType = 'css'; } elseif ( stripos( $src, '.js' ) !== false ) { $forAssetType = 'js'; } if ($forAssetType) { $pathToFile = OptimizeCommon::getLocalAssetPath( $src, $forAssetType ); } if ( ! is_file($pathToFile) ) { // Fallback, old code... // Local file? Core or from a plugin / theme? if ( strpos( $obj->src, $siteUrl ) !== false ) { // Local Plugin / Theme File // Could be a Staging site that is having the Live URL in the General Settings $src = ltrim( str_replace( $siteUrl, '', $obj->src ), '/' ); } elseif ( ( isset( $obj->wp ) && $obj->wp === 1 ) || $isRelInternalPath ) { // Local WordPress Core File $src = ltrim( $obj->src, '/' ); } $srcAlt = $src; if ( strpos( $src, '../' ) === 0 ) { $srcAlt = str_replace( '../', '', $srcAlt ); } $pathToFile = Misc::getWpRootDirPath() . $srcAlt; if ( strpos( $pathToFile, '?ver' ) !== false ) { list( $pathToFile ) = explode( '?ver', $pathToFile ); } // It can happen that the CSS/JS has extra parameters (rare cases) foreach ( array( '.css?', '.js?' ) as $needlePart ) { if ( strpos( $pathToFile, $needlePart ) !== false ) { list( $pathToFile ) = explode( '?', $pathToFile ); } } } if (is_file($pathToFile)) { $sizeInBytes = filesize($pathToFile); if ($format === 'raw') { return (int)$sizeInBytes; } return Misc::formatBytes($sizeInBytes); } return '<em>Error: Could not read '.$pathToFile.'</em>'; } if ($obj->handle === 'jquery' && isset($obj->src) && ! $obj->src) { return '"jquery-core" size'; } // External or nothing to be shown (perhaps due to an error) return ''; } /** * Source: https://stackoverflow.com/questions/2602612/remote-file-size-without-downloading-file */ public function ajaxGetExternalFileSize() { // Check nonce if ( ! isset( $_POST['wpacu_nonce'] ) || ! wp_verify_nonce( $_POST['wpacu_nonce'], 'wpacu_ajax_check_remote_file_size_nonce' ) ) { echo 'Error: The security nonce is not valid.'; exit(); } // Check privileges if (! Menu::userCanManageAssets()) { echo 'Error: Not enough privileges to perform this action.'; exit(); } // Assume failure. $result = -1; $remoteFile = Misc::getVar('post', 'wpacu_remote_file', false); if (! $remoteFile) { echo 'N/A (external file)'; exit; } // If it starts with // if (strpos($remoteFile, '//') === 0) { $remoteFile = 'http:'.$remoteFile; } // Check if the URL is valid if (! filter_var($remoteFile, FILTER_VALIDATE_URL)) { echo 'The asset\'s URL - '.$remoteFile.' - is not valid.'; exit(); } $curl = curl_init($remoteFile); // Issue a HEAD request and follow any redirects. curl_setopt($curl, CURLOPT_NOBODY, true); curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); $data = curl_exec($curl); curl_close($curl); $content_length = $status = 'unknown'; if ($data) { if (preg_match( '/^HTTP\/1\.[01] (\d\d\d)/', $data, $matches ) ) { $status = (int)$matches[1]; } if ( preg_match( '/Content-Length: (\d+)/', $data, $matches ) ) { $content_length = (int)$matches[1]; } // http://en.wikipedia.org/wiki/List_of_HTTP_status_codes if ( $status === 200 || ($status > 300 && $status <= 308) ) { $result = $content_length; } } if ($content_length === 'unknown') { // One more try $response = wp_remote_get($remoteFile); $responseCode = wp_remote_retrieve_response_code($response); if ($responseCode === 200) { $result = mb_strlen(wp_remote_retrieve_body($response)); } } echo Misc::formatBytes($result); if (stripos($remoteFile, '//fonts.googleapis.com/') !== false) { // Google Font APIS CDN echo ' + the sizes of the loaded "Google Font" files (see "url" from @font-face within the Source file)'; } elseif (stripos($remoteFile, '/font-awesome.css') || stripos($remoteFile, '/font-awesome.min.css')) { // FontAwesome CDN echo ' + the sizes of the loaded "FontAwesome" font files (see "url" from @font-face within the Source file)'; } exit(); } /** * @return bool */ public static function isSingularPage() { return (self::$vars['is_woo_shop_page'] || is_singular() || Misc::isBlogPage()); } /** * @return int|mixed|string */ public function getCurrentPostId() { if ($this->currentPostId > 0) { return $this->currentPostId; } if (Misc::isElementorMaintenanceModeOnForCurrentAdmin() && defined('WPACU_IS_ELEMENTOR_MAINTENANCE_MODE_TEMPLATE_ID')) { $this->currentPostId = WPACU_IS_ELEMENTOR_MAINTENANCE_MODE_TEMPLATE_ID; return $this->currentPostId; } // Are we on the `Shop` page from WooCommerce? // Only check option if function `is_shop` exists $wooCommerceShopPageId = function_exists('is_shop') ? get_option('woocommerce_shop_page_id') : 0; // Check if we are on the WooCommerce Shop Page // Do not mix the WooCommerce Search Page with the Shop Page if (function_exists('is_shop') && is_shop()) { $this->currentPostId = $wooCommerceShopPageId; if ($this->currentPostId > 0) { self::$vars['is_woo_shop_page'] = true; } } else { if ($wooCommerceShopPageId > 0 && Misc::isHomePage() && strpos(get_site_url(), '://') !== false) { list($siteUrlAfterProtocol) = explode('://', get_site_url()); $currentPageUrlAfterProtocol = parse_url(site_url(), PHP_URL_HOST) . $_SERVER['REQUEST_URI']; if ($siteUrlAfterProtocol != $currentPageUrlAfterProtocol && (strpos($siteUrlAfterProtocol, '/shop') !== false)) { self::$vars['woo_url_not_match'] = true; } } } // Blog Home Page (aka: Posts page) is not a singular page, it's checked separately if (Misc::isBlogPage()) { $this->currentPostId = get_option('page_for_posts'); } // It has to be a single page (no "Posts page") if (($this->currentPostId < 1) && is_singular()) { global $post; $this->currentPostId = isset($post->ID) ? $post->ID : 0; } // [wpacu_lite] // Undetectable? The page is not a singular one nor the home page // It's likely an archive, category page (WooCommerce), 404 page manageable in the Pro version etc. if (! $this->currentPostId && ! Misc::isHomePage()) { $this->isUpdateable = false; } // [/wpacu_lite] return $this->currentPostId; } /** * @return array|null|\WP_Post */ public function getCurrentPost() { // Already set? Return it if (! empty($this->currentPost)) { return $this->currentPost; } // Not set? Create and return it if (! $this->currentPost && $this->getCurrentPostId() > 0) { $this->currentPost = get_post($this->getCurrentPostId()); return $this->currentPost; } // Empty return $this->currentPost; } /** * Get all contracted rows * * @return array */ public static function getHandleRowStatus() { $handleRowStatus = array('styles' => array(), 'scripts' => array()); $handleRowStatusListJson = get_option(WPACU_PLUGIN_ID . '_global_data'); $globalKey = 'handle_row_contracted'; if ($handleRowStatusListJson) { $handleRowStatusList = @json_decode($handleRowStatusListJson, true); // Issues with decoding the JSON file? Return an empty list if (Misc::jsonLastError() !== JSON_ERROR_NONE) { return $handleRowStatus; } // Are new positions set for styles and scripts? foreach (array('styles', 'scripts') as $assetKey) { if ( isset( $handleRowStatusList[$assetKey][$globalKey] ) && ! empty( $handleRowStatusList[$assetKey][$globalKey] ) ) { $handleRowStatus[$assetKey] = $handleRowStatusList[$assetKey][$globalKey]; } } } return $handleRowStatus; } /** * @param $postType * * @return array */ public static function getAllSetTaxonomies($postType) { if ( ! $postType ) { return array(); } $postTaxonomies = get_object_taxonomies($postType); if ($postType === 'post') { $postFormatKey = array_search('post_format', $postTaxonomies); unset($postTaxonomies[$postFormatKey]); } if (empty($postTaxonomies)) { // There are no taxonomies associate with the $postType or $postType is not valid return array(); } $allPostTypeTaxonomyTerms = get_terms( array( 'taxonomy' => $postTaxonomies, 'hide_empty' => true, ) ); $finalList = array(); foreach ($allPostTypeTaxonomyTerms as $obj) { $taxonomyObj = get_taxonomy($obj->taxonomy); if ( ! $taxonomyObj->show_ui ) { continue; } $finalList[$taxonomyObj->label][] = (array)$obj; } if ( ! empty($finalList) ) { foreach ( array_keys( $finalList ) as $taxonomyLabel ) { usort( $finalList[ $taxonomyLabel ], static function( $a, $b ) { return strcasecmp( $a['name'], $b['name'] ); } ); } ksort($finalList); } return $finalList; } /** * @param $data * * @return mixed */ public function setPageTemplate($data) { global $template; $getPageTpl = get_post_meta($this->getCurrentPostId(), '_wp_page_template', true); // Could be a custom post type with no template set if (! $getPageTpl) { $getPageTpl = get_page_template(); if (in_array(basename($getPageTpl), array('single.php', 'page.php'))) { $getPageTpl = 'default'; } } if (! $getPageTpl) { return $data; } $data['page_template'] = $getPageTpl; $data['all_page_templates'] = wp_get_theme()->get_page_templates(); // Is the default template shown? Most of the time it is! if ($data['page_template'] === 'default') { $pageTpl = (isset($template) && $template) ? $template : get_page_template(); $data['page_template'] = basename( $pageTpl ); $data['all_page_templates'][ $data['page_template'] ] = 'Default Template'; } if (isset($template) && $template && defined('ABSPATH')) { $data['page_template_path'] = str_replace( Misc::getWpRootDirPath(), '', '/'.$template ); } return $data; } /** * @return bool */ public static function isWpDefaultSearchPage() { // It will not interfere with the WooCommerce search page // which is considered to be the "Shop" page that has its own unload rules return (is_search() && (! (function_exists('is_shop') && is_shop()))); } /** * @param $existingListJson * @param $existingListEmpty * * @return array */ public function existingList($existingListJson, $existingListEmpty) { $validJson = $notEmpty = true; if (! $existingListJson) { $existingList = $existingListEmpty; $notEmpty = false; } else { $existingList = json_decode($existingListJson, true); if (Misc::jsonLastError() !== JSON_ERROR_NONE) { $validJson = false; $existingList = $existingListEmpty; } } return array( 'list' => $existingList, 'valid_json' => $validJson, 'not_empty' => $notEmpty ); } /** * Situations when the assets will not be prevented from loading * e.g. test mode and a visitor accessing the page, an AJAX request from the Dashboard to print all the assets * * @param array $ignoreList * * @return bool */ public function preventAssetsSettings($ignoreList = array()) { // This request specifically asks for all the assets to be loaded in order to print them in the assets management list // This is for the AJAX requests within the Dashboard, thus the admin needs to see all the assets, // including ones marked for unload, in case he/she decides to change their rules if ($this->isGetAssetsCall && ! in_array('assets_call', $ignoreList)) { return true; } // Is test mode enabled? Unload assets ONLY for the admin if (self::isTestModeActive()) { return true; // visitors (non-logged in) will view the pages with all the assets loaded } $isSingularPage = defined('WPACU_CURRENT_PAGE_ID') && WPACU_CURRENT_PAGE_ID > 0 && is_singular(); if ($isSingularPage || Misc::isHomePage()) { if ($isSingularPage) { $pageOptions = MetaBoxes::getPageOptions( WPACU_CURRENT_PAGE_ID ); // Singular page } else { $pageOptions = MetaBoxes::getPageOptions(0, 'front_page'); // Home page } if (isset($pageOptions['no_assets_settings']) && $pageOptions['no_assets_settings']) { return true; } } return false; } /** * @param array $settings * * @return bool */ public static function isTestModeActive($settings = array()) { if (defined('WPACU_IS_TEST_MODE_ACTIVE')) { return WPACU_IS_TEST_MODE_ACTIVE; } if (! $settings) { $settings = self::instance()->settings; } $wpacuIsTestModeActive = isset($settings['test_mode']) && $settings['test_mode'] && ! Menu::userCanManageAssets(); define('WPACU_IS_TEST_MODE_ACTIVE', $wpacuIsTestModeActive); return $wpacuIsTestModeActive; } /** * @return bool */ public static function currentUserCanViewAssetsList() { if ( Main::instance()->settings['allow_manage_assets_to'] === 'chosen' && ! empty(Main::instance()->settings['allow_manage_assets_to_list']) ) { $wpacuCurrentUserId = get_current_user_id(); if ( ! in_array( $wpacuCurrentUserId, Main::instance()->settings['allow_manage_assets_to_list'] ) ) { return false; // the current logged-in admin is not in the list of "Allow managing assets to:" } } return true; } /** * @return bool */ public function frontendShow() { // The option is disabled if (! $this->settings['frontend_show']) { return false; } // The asset list is hidden via query string: /?wpacu_no_frontend_show if (isset($_REQUEST['wpacu_no_frontend_show'])) { return false; } // Page loaded via Yellow Pencil Editor within an iframe? Do not show it as it's irrelevant there if (isset($_GET['yellow_pencil_frame'], $_GET['yp_page_type'])) { return false; } // The option is enabled, but there are show exceptions, check if the list should be hidden if ($this->settings['frontend_show_exceptions']) { $frontendShowExceptions = trim( $this->settings['frontend_show_exceptions'] ); // We want to make sure the RegEx rules will be working fine if certain characters (e.g. Thai ones) are used $requestUriAsItIs = rawurldecode($_SERVER['REQUEST_URI']); if ( strpos( $frontendShowExceptions, "\n" ) !== false ) { foreach ( explode( "\n", $frontendShowExceptions ) as $frontendShowException ) { $frontendShowException = trim($frontendShowException); if ( strpos( $requestUriAsItIs, $frontendShowException ) !== false ) { return false; } } } elseif ( strpos( $requestUriAsItIs, $frontendShowExceptions ) !== false ) { return false; } } // Allows managing assets to chosen admins and the user is not in the list if ( ! self::currentUserCanViewAssetsList() ) { return false; } return true; } /** * Make administrator more aware if "TEST MODE" is enabled or not */ public function wpacuHtmlNoticeForAdmin() { add_action('wp_footer', static function() { if ((WPACU_GET_LOADED_ASSETS_ACTION === true) || (! apply_filters('wpacu_show_admin_console_notice', true)) || Plugin::preventAnyFrontendOptimization()) { return; } if ( ! (Menu::userCanManageAssets() && ! is_admin())) { return; } if (Main::instance()->settings['test_mode']) { $consoleMessage = sprintf(esc_html__('%s: "TEST MODE" ENABLED (any settings or unloads will be visible ONLY to you, the logged-in administrator)', 'wp-asset-clean-up'), WPACU_PLUGIN_TITLE); $testModeNotice = esc_html__('"Test Mode" is ENABLED. Any settings or unloads will be visible ONLY to you, the logged-in administrator.', 'wp-asset-clean-up'); } else { $consoleMessage = sprintf(esc_html__('%s: "LIVE MODE" (test mode is not enabled, thus, all the plugin changes are visible for everyone: you, the logged-in administrator and the regular visitors)', 'wp-asset-clean-up'), WPACU_PLUGIN_TITLE); $testModeNotice = esc_html__('The website is in LIVE MODE as "Test Mode" is not enabled. All the plugin changes are visible for everyone: logged-in administrators and regular visitors.', 'wp-asset-clean-up'); } ?> <!-- <?php echo sprintf(esc_html__('NOTE: These "%s: Page Speed Booster" messages are only shown to you, the HTML comment is not visible for the regular visitor.', 'wp-asset-clean-up'), WPACU_PLUGIN_TITLE); ?> <?php echo esc_html__($testModeNotice); ?> --> <script <?php echo Misc::getScriptTypeAttribute(); ?> data-wpacu-own-inline-script="true"> console.log('<?php echo esc_js($consoleMessage); ?>'); </script> <?php }); } /** * */ public function fallbacks() { // Fallback for the old filters (e.g. Pro version below 1.2.0.7) add_filter('wpacu_filter_styles_list_unload', function ($list) { return apply_filters('wpacu_filter_styles', $list); }); add_filter('wpacu_filter_scripts_list_unload', function ($list) { return apply_filters('wpacu_filter_scripts', $list); }); } }