2197 lines
		
	
	
		
			70 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			2197 lines
		
	
	
		
			70 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| namespace WpAssetCleanUp\OptimiseAssets;
 | ||
| 
 | ||
| use WpAssetCleanUp\CleanUp;
 | ||
| use WpAssetCleanUp\FileSystem;
 | ||
| use WpAssetCleanUp\HardcodedAssets;
 | ||
| use WpAssetCleanUp\Main;
 | ||
| use WpAssetCleanUp\Menu;
 | ||
| use WpAssetCleanUp\Misc;
 | ||
| use WpAssetCleanUp\ObjectCache;
 | ||
| use WpAssetCleanUp\Plugin;
 | ||
| use WpAssetCleanUp\Preloads;
 | ||
| use WpAssetCleanUp\Settings;
 | ||
| use WpAssetCleanUp\Tools;
 | ||
| 
 | ||
| /**
 | ||
|  * Class OptimizeCommon
 | ||
|  * @package WpAssetCleanUp
 | ||
|  */
 | ||
| class OptimizeCommon
 | ||
| {
 | ||
| 	/**
 | ||
| 	 * @var string
 | ||
| 	 */
 | ||
| 	public static $relPathPluginCacheDirDefault = '/cache/asset-cleanup/'; // keep forward slash at the end
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @var string
 | ||
| 	 */
 | ||
| 	public static $optimizedSingleFilesDir = 'item';
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @var float|int
 | ||
| 	 */
 | ||
| 	public static $cachedAssetFileExpiresIn = 86400; // 1 day in seconds
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @var array
 | ||
| 	 */
 | ||
| 	public static $wellKnownExternalHosts = array(
 | ||
| 		'googleapis.com',
 | ||
| 		'bootstrapcdn.com',
 | ||
| 		'cloudflare.com',
 | ||
| 		'jsdelivr.net'
 | ||
| 	);
 | ||
| 
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 */
 | ||
| 	public function init()
 | ||
| 	{
 | ||
| 		add_action('switch_theme',       array($this, 'clearCache' ));
 | ||
| 		add_action('after_switch_theme', array($this, 'clearCache' ));
 | ||
| 
 | ||
| 		// Is WP Rocket's page cache cleared? Clear Asset CleanUp's CSS cache files too
 | ||
| 		if ( isset($_GET['action']) && $_GET['action'] === 'purge_cache' ) {
 | ||
| 			// Leave its default parameters, no redirect needed
 | ||
| 			add_action('init', static function() {
 | ||
| 				OptimizeCommon::clearCache();
 | ||
| 			}, PHP_INT_MAX);
 | ||
| 		}
 | ||
| 
 | ||
| 		add_action('admin_post_assetcleanup_clear_assets_cache', static function() {
 | ||
| 			set_transient('wpacu_clear_assets_cache_via_link', true);
 | ||
| 			self::clearCache(true);
 | ||
| 		});
 | ||
| 
 | ||
| 		// When a post is moved to the trash / deleted
 | ||
| 		// clear its cache as its useless and there's no point in having extra files/directories in the caching directory
 | ||
| 		add_action('wp_trash_post', array($this, 'clearJsonStorageForPost')); // $postId is passed as a parameter
 | ||
| 		add_action('delete_post',   array($this, 'clearJsonStorageForPost')); // $postId is passed as a parameter
 | ||
| 
 | ||
| 		// When a post is edited are within the Dashboard
 | ||
| 		add_action('admin_init', static function() {
 | ||
| 			if (($postId = Misc::getVar('get', 'post')) && Misc::getVar('get', 'action') === 'edit') {
 | ||
| 				self::clearJsonStorageForPost($postId, true);
 | ||
| 			}
 | ||
| 		});
 | ||
| 
 | ||
| 		// Keep used resources to the minimum and trigger any clearing of the page's CSS/JS caching
 | ||
| 		// for the admin while he has the right privileges and a single post page is visited
 | ||
| 		add_action('wp', static function() {
 | ||
| 			if (! is_admin() && Menu::userCanManageAssets() && is_singular()) {
 | ||
| 				global $post;
 | ||
| 
 | ||
| 				if (isset($post->ID) && $post->ID) {
 | ||
| 					self::clearJsonStorageForPost($post->ID, true);
 | ||
| 				}
 | ||
| 			}
 | ||
| 		});
 | ||
| 
 | ||
| 		// Autoptimize Compatibility: Make sure Asset CleanUp's changes are applied
 | ||
| 		add_filter('autoptimize_filter_html_before_minify', static function($htmlSource) {
 | ||
| 			return self::alterHtmlSource($htmlSource, true);
 | ||
| 		});
 | ||
| 
 | ||
| 		if (Misc::isPluginActive('cache-enabler/cache-enabler.php')) {
 | ||
| 			if (defined('CE_VERSION') && version_compare(CE_VERSION, '1.6.0', '<')) {
 | ||
| 				// Cache Enabler: BEFORE 1.6.0 (1.5.5 and below)
 | ||
| 				// Make sure HTML changes are applied to cached pages from "Cache Enabler" plugin
 | ||
| 				add_filter( 'cache_enabler_before_store', static function( $htmlSource ) {
 | ||
| 					return self::alterHtmlSource( $htmlSource ); // deprecated, include it in case other users have an older version of "Cache Enabler"
 | ||
| 				}, 1, 1 );
 | ||
| 			} else {
 | ||
| 				// Cache Enabler: 1.6.0+
 | ||
| 				global $cache_enabler_constants;
 | ||
| 
 | ||
| 				if (isset($cache_enabler_constants['CACHE_ENABLER_VERSION']) && version_compare($cache_enabler_constants['CACHE_ENABLER_VERSION'], '1.6.0', '>=')) {
 | ||
| 					add_filter( 'cache_enabler_page_contents_before_store', static function( $htmlSource ) {
 | ||
| 						return self::alterHtmlSource( $htmlSource );
 | ||
| 					}, 1, 1 );
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		// In case HTML Minify is enabled in W3 Total Cache, make sure any settings (e.g. JS combine) in Asset CleanUp will be applied
 | ||
| 		add_filter('w3tc_minify_before', static function ($htmlSource) {
 | ||
| 			return self::alterHtmlSource($htmlSource);
 | ||
| 		}, 1, 1);
 | ||
| 
 | ||
| 		// LiteSpeed Cache (partial compatibility)
 | ||
| 		add_filter('litespeed_optm_html_head', static function($htmlHead) {
 | ||
| 			if (! Main::instance()->preventAssetsSettings()) {
 | ||
| 				$htmlHead = OptimizeCss::ignoreDependencyRuleAndKeepChildrenLoaded($htmlHead);
 | ||
| 				$htmlHead = OptimizeJs::ignoreDependencyRuleAndKeepChildrenLoaded($htmlHead);
 | ||
| 			}
 | ||
| 			return $htmlHead;
 | ||
| 		});
 | ||
| 		add_filter('litespeed_optm_html_foot', static function($htmlFoot) {
 | ||
| 			if (! Main::instance()->preventAssetsSettings()) {
 | ||
| 				$htmlFoot = OptimizeCss::ignoreDependencyRuleAndKeepChildrenLoaded($htmlFoot);
 | ||
| 				$htmlFoot = OptimizeJs::ignoreDependencyRuleAndKeepChildrenLoaded($htmlFoot);
 | ||
| 			}
 | ||
| 			return $htmlFoot;
 | ||
| 		});
 | ||
| 
 | ||
| 		// Make sure HTML changes, especially rules such as the ones from "Ignore dependency rules and keep 'children' loaded"
 | ||
| 		// are applied to cached pages from "WP Rocket" plugin
 | ||
| 		if (Misc::isPluginActive('wp-rocket/wp-rocket.php')) {
 | ||
| 			add_filter('rocket_buffer', static function($htmlSource) {
 | ||
| 				return self::alterHtmlSource($htmlSource, true);
 | ||
| 			});
 | ||
| 		}
 | ||
| 
 | ||
| 		// "Hide My WP Ghost – Security Plugin" - Make sure the alter the HTML (some files might be cached) before the security plugin proceeds with the alteration of the paths
 | ||
| 		// This way, "Hide My WP Ghost – Security Plugin" would process the paths to the CSS/JS files that are already cached from /wp-content/cache/
 | ||
| 		if ( ( ! (defined('DOING_AJAX') && DOING_AJAX) ) // not when /wp-admin/admin-ajax.php is called
 | ||
|             && class_exists('\HMWP_Classes_ObjController')
 | ||
| 		    && method_exists('\HMWP_Models_Rewrite', 'find_replace')
 | ||
| 		    && apply_filters('hmwp_process_buffer', true) // only when processing the buffer is turned ON
 | ||
| 		) {
 | ||
| 			add_filter('hmwp_process_buffer', '__return_false');
 | ||
| 			add_filter('wpacu_print_info_comments_in_cached_assets', '__return_false'); // hide comments revealing the paths to serve the purpose of "Hide My WP Ghost – Security Plugin"
 | ||
| 
 | ||
| 			add_filter('wpacu_html_source_after_optimization', function($htmlSource) {
 | ||
| 				return \HMWP_Classes_ObjController::getClass('HMWP_Models_Rewrite')->find_replace($htmlSource);
 | ||
| 			});
 | ||
| 		}
 | ||
| 
 | ||
| 		add_action('wp_loaded', array($this, 'maybeAlterHtmlSource'), 1);
 | ||
| 
 | ||
| 		HardcodedAssets::init();
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 */
 | ||
| 	public function maybeAlterHtmlSource()
 | ||
| 	{
 | ||
| 		if (is_admin()) {
 | ||
| 			// Don't apply any changes if not in the front-end view (e.g. Dashboard view)
 | ||
| 			return;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (is_feed()) {
 | ||
| 			// The plugin should be inactive for feed URLs
 | ||
| 			return;
 | ||
| 		}
 | ||
| 
 | ||
| 		/*
 | ||
| 		 * CASE 1: The admin is logged-in and manages the assets in the front-end view
 | ||
| 		 * */
 | ||
| 		if (HardcodedAssets::useBufferingForEditFrontEndView()) {
 | ||
| 			// Alter the HTML via "shutdown" action hook to catch hardcoded CSS/JS that is added via output buffering such as the ones in "Smart Slider 3"
 | ||
| 			// via HardcodedAssets.php
 | ||
| 			return;
 | ||
| 		}
 | ||
| 
 | ||
| 		/*
 | ||
| 		 * CASE (most common): The admin is logged-in, but "Manage in the front-end" is deactivated OR the visitor is just a guest
 | ||
| 		 * */
 | ||
| 		ob_start(static function($htmlSource) {
 | ||
| 			// Do not do any optimization if "Test Mode" is Enabled
 | ||
| 			if (! Menu::userCanManageAssets() && Main::instance()->settings['test_mode']) {
 | ||
| 				return $htmlSource;
 | ||
| 			}
 | ||
| 
 | ||
| 			return self::alterHtmlSource($htmlSource);
 | ||
| 		});
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $htmlSource
 | ||
| 	 * @param $triggerOnlyOnce bool
 | ||
| 	 *
 | ||
| 	 * @return mixed|string|string[]|void|null
 | ||
| 	 */
 | ||
| 	public static function alterHtmlSource($htmlSource, $triggerOnlyOnce = false)
 | ||
| 	{
 | ||
| 		// e.g. if it was called from "autoptimize_filter_html_before_minify", then there's no point in triggering it again from a different hook
 | ||
| 		if (defined('WPACU_ALTER_HTML_SOURCE_DONE')) {
 | ||
| 			return $htmlSource;
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($triggerOnlyOnce && ! defined('WPACU_ALTER_HTML_SOURCE_DONE')) {
 | ||
| 			define('WPACU_ALTER_HTML_SOURCE_DONE', 1);
 | ||
| 		}
 | ||
| 
 | ||
| 		if (is_feed()) {
 | ||
| 			// The plugin should not do any alterations for the feed content
 | ||
| 			return $htmlSource;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Dashboard View
 | ||
| 		// Return the HTML as it is without performing any optimisations to save resources
 | ||
| 		// Since the page has to be as clean as possible when fetching the assets
 | ||
| 		if (Main::instance()->isGetAssetsCall) {
 | ||
| 			return $htmlSource;
 | ||
| 		}
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source' ); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		// Front-end View
 | ||
| 		// The printing of the hardcoded assets is made via "wpacu_final_frontend_output" filter hook
 | ||
| 		// located within "shutdown" action hook only if the user is logged-in and has the right permissions
 | ||
| 
 | ||
| 		// This is useful to avoid changing the DOM via wp_loaded action hook
 | ||
| 		// In order to check how fast the page loads without the DOM changes (for debugging purposes)
 | ||
| 		$wpacuNoHtmlChanges = isset($_REQUEST['wpacu_no_html_changes']) || ( defined('WPACU_NO_HTML_CHANGES') && WPACU_NO_HTML_CHANGES );
 | ||
| 
 | ||
| 		// Not a normal WordPress page load
 | ||
| 		// e.g. it could be JS content loaded dynamically such as /?wpml-app=ate-widget
 | ||
| 		if ( ! (did_action('wp_head') && did_action('wp_footer')) && Plugin::preventAnyFrontendOptimization('', $htmlSource) ) {
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source', 'end' ); /* [/wpacu_timing] */
 | ||
| 			return $htmlSource;
 | ||
| 		}
 | ||
| 
 | ||
| 		if ( $wpacuNoHtmlChanges || Plugin::preventAnyFrontendOptimization() ) {
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source', 'end' ); /* [/wpacu_timing] */
 | ||
| 			return $htmlSource;
 | ||
| 		}
 | ||
| 
 | ||
| 		$htmlSource = apply_filters( 'wpacu_html_source_before_optimization', $htmlSource );
 | ||
| 
 | ||
| 		// For the admin
 | ||
| 		$anyHardCodedAssetsList = HardcodedAssets::getAll( $htmlSource, false );
 | ||
| 
 | ||
| 		// The admin is editing the CSS/JS list within the front-end view
 | ||
| 		if (HardcodedAssets::useBufferingForEditFrontEndView()) {
 | ||
| 			ObjectCache::wpacu_cache_set('wpacu_hardcoded_assets_encoded', base64_encode( wp_json_encode($anyHardCodedAssetsList) ));
 | ||
| 		}
 | ||
| 
 | ||
| 		$htmlSource = OptimizeCss::alterHtmlSource( $htmlSource );
 | ||
| 		$htmlSource = OptimizeJs::alterHtmlSource( $htmlSource );
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source_cleanup' ); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_remove_html_comments'); /* [/wpacu_timing] */
 | ||
| 		$htmlSource = Main::instance()->settings['remove_html_comments'] ? CleanUp::removeHtmlComments( $htmlSource, false ) : $htmlSource;
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_remove_html_comments', 'end'); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_remove_meta_generators'); /* [/wpacu_timing] */
 | ||
| 		$htmlSource = Main::instance()->settings['remove_generator_tag'] ? CleanUp::removeMetaGenerators( $htmlSource ) : $htmlSource;
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_remove_meta_generators', 'end'); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		$htmlSource = preg_replace('#<link(.*)data-wpacu-style-handle=\'(.*)\'#Umi', '<link \\1', $htmlSource);
 | ||
| 		$htmlSource = preg_replace('#<link(\s+)rel=\'stylesheet\' id=\'#Umi', '<link rel=\'stylesheet\' id=\'', $htmlSource);
 | ||
| 
 | ||
| 		$htmlSource = str_replace(Preloads::DEL_STYLES_PRELOADS, '', $htmlSource);
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source_cleanup', 'end' ); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		if ( in_array( Main::instance()->settings['disable_xmlrpc'], array( 'disable_all', 'disable_pingback' ) ) ) {
 | ||
| 			// Also clean it up from the <head> in case it's hardcoded
 | ||
| 			$htmlSource = CleanUp::cleanPingbackLinkRel( $htmlSource );
 | ||
| 		}
 | ||
| 
 | ||
| 		// A script like this one shouldn't be in an AMP page
 | ||
| 		if (defined('WPACU_DO_EXTRA_CHECKS_FOR_AMP') && strpos($htmlSource, '<style amp-boilerplate>') !== false && (strpos($htmlSource, '<style amp-custom>') !== false || strpos($htmlSource, '<html amp ') !== false)) {
 | ||
| 			$htmlSource = str_replace(Misc::preloadAsyncCssFallbackOutput(true), '', $htmlSource);
 | ||
| 		}
 | ||
| 
 | ||
| 		$htmlSource = apply_filters( 'wpacu_html_source', $htmlSource ); // legacy
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source', 'end' ); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		// [wpacu_debug]
 | ||
| 		if (isset($_GET['wpacu_debug'])) {
 | ||
| 			$htmlSource = self::applyDebugTiming($htmlSource);
 | ||
| 		}
 | ||
| 		// [wpacu_debug]
 | ||
| 
 | ||
| 		return apply_filters( 'wpacu_html_source_after_optimization', $htmlSource );
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $htmlSource
 | ||
| 	 *
 | ||
| 	 * @return string|string[]
 | ||
| 	 */
 | ||
| 	public static function applyDebugTiming($htmlSource)
 | ||
| 	{
 | ||
| 		$timingKeys = array(
 | ||
| 			'prepare_optimize_files_css',
 | ||
| 			'prepare_optimize_files_js',
 | ||
| 
 | ||
| 			// All HTML alteration via "wp_loaded" action hook
 | ||
| 			'alter_html_source',
 | ||
| 
 | ||
| 			// HTML CleanUp
 | ||
| 			'alter_html_source_cleanup',
 | ||
| 			'alter_html_source_for_remove_html_comments',
 | ||
| 			'alter_html_source_for_remove_meta_generators',
 | ||
| 
 | ||
| 			// CSS
 | ||
| 			'alter_html_source_for_optimize_css',
 | ||
| 			'alter_html_source_unload_ignore_deps_css',
 | ||
| 			'alter_html_source_for_google_fonts_optimization_removal',
 | ||
| 			'alter_html_source_for_inline_css',
 | ||
| 			'alter_html_source_original_to_optimized_css',
 | ||
| 			'alter_html_source_for_preload_css',
 | ||
| 
 | ||
| 			'alter_html_source_for_combine_css',
 | ||
| 			'alter_html_source_for_minify_inline_style_tags',
 | ||
| 
 | ||
| 			// JS
 | ||
| 			'alter_html_source_for_optimize_js',
 | ||
| 			'alter_html_source_unload_ignore_deps_js',
 | ||
| 
 | ||
| 			'alter_html_source_original_to_optimized_js',
 | ||
| 			'alter_html_source_for_preload_js',
 | ||
| 			'alter_html_source_for_combine_js',
 | ||
| 
 | ||
| 			'fetch_strip_hardcoded_assets',
 | ||
| 			'fetch_all_hardcoded_assets',
 | ||
| 
 | ||
| 			'output_css_js_manager',
 | ||
| 
 | ||
| 			'style_loader_tag',
 | ||
| 			'script_loader_tag'
 | ||
| 		);
 | ||
| 
 | ||
| 		foreach ( $timingKeys as $timingKey ) {
 | ||
| 			$htmlSource = Misc::printTimingFor($timingKey, $htmlSource);
 | ||
| 		}
 | ||
| 
 | ||
| 		return $htmlSource;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $htmlSource
 | ||
| 	 * @param string $for
 | ||
| 	 *
 | ||
| 	 * @return \DOMDocument
 | ||
| 	 */
 | ||
| 	public static function getDomLoadedTag($htmlSource, $for = '')
 | ||
| 	{
 | ||
| 		$htmlSourceBefore = $htmlSource;
 | ||
| 
 | ||
| 		$domTag = Misc::initDOMDocument();
 | ||
| 
 | ||
| 		$cleanerDomRegEx = '';
 | ||
| 
 | ||
| 		// [HTML CleanUp]
 | ||
| 		if ($for === 'removeHtmlComments') {
 | ||
| 			// They could contain anything
 | ||
| 			$cleanerDomRegEx = '';
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($for === 'removeMetaGenerators') {
 | ||
| 			$cleanerDomRegEx = array('@<(noscript|style|script)[^>]*?>.*?</\\1>@si', '#<(link|img)([^<>]+)/?>#iU');
 | ||
| 		}
 | ||
| 		// [/HTML CleanUp]
 | ||
| 
 | ||
| 		// [CSS Optimisation]
 | ||
| 		if ($for === 'combineCss') {
 | ||
| 			$cleanerDomRegEx = array('@<(noscript|style|script)[^>]*?>.*?</\\1>@si', '#<(meta|img)([^<>]+)/?>#iU');
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($for === 'minifyInlineStyleTags') {
 | ||
| 			$cleanerDomRegEx = array('@<(noscript|script)[^>]*?>.*?</\\1>@si', '#<(meta|link|img)([^<>]+)/?>#iU');
 | ||
| 		}
 | ||
| 		// [/CSS Optimisation]
 | ||
| 
 | ||
| 		// [JS Optimisation]
 | ||
| 		if ($for === 'moveInlinejQueryAfterjQuerySrc') {
 | ||
| 			$cleanerDomRegEx = '@<(noscript|style)[^>]*?>.*?</\\1>@si';
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($for === 'minifyInlineScriptTags') {
 | ||
| 			$cleanerDomRegEx = array('@<(noscript|style)[^>]*?>.*?</\\1>@si', '#<(meta|link|img)([^<>]+)/?>#iU');
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($for === 'combineJs') {
 | ||
| 			$cleanerDomRegEx = '@<(noscript|style)[^>]*?>.*?</\\1>@si';
 | ||
| 		}
 | ||
| 		// [/JS Optimisation]
 | ||
| 
 | ||
| 		// Default: Strip just the NOSCRIPT tags
 | ||
| 		if ($cleanerDomRegEx !== '') {
 | ||
| 			$htmlSource = preg_replace( $cleanerDomRegEx, '', $htmlSource );
 | ||
| 		}
 | ||
| 
 | ||
| 		if (Main::instance()->isFrontendEditView) {
 | ||
| 			$htmlSource = preg_replace( '@<form action="#wpacu_wrap_assets" method="post">.*?</form>@si', '', $htmlSource );
 | ||
| 		}
 | ||
| 
 | ||
| 		// Avoid "Warning: DOMDocument::loadHTML(): Empty string supplied as input"
 | ||
| 		// Just in case $htmlSource has been altered incorrectly for any reason, fallback to the original $htmlSource value ($htmlSourceBefore)
 | ||
| 		if ( ! $htmlSource ) {
 | ||
| 			$domTag->loadHTML($htmlSourceBefore);
 | ||
| 			return $domTag;
 | ||
| 		}
 | ||
| 
 | ||
| 		$domTag->loadHTML($htmlSource);
 | ||
| 		return $domTag;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $htmlSource
 | ||
| 	 * @param $params
 | ||
| 	 *
 | ||
| 	 * @return array|mixed|string|string[]
 | ||
| 	 */
 | ||
| 	public static function matchAndReplaceLinkTags($htmlSource, $params = array())
 | ||
| 	{
 | ||
| 		if (isset($params['as']) && $params['as']) {
 | ||
| 			$fallbackToRegex = false;
 | ||
| 
 | ||
| 			/*
 | ||
| 			  * Option 1: DOM + Regular Expression (Best)
 | ||
| 			 */
 | ||
| 			if ( Misc::isDOMDocumentOn() ) {
 | ||
| 				$dom = Misc::initDOMDocument();
 | ||
| 
 | ||
| 				$dom->loadHTML($htmlSource);
 | ||
| 
 | ||
| 				$selector = new \DOMXPath($dom);
 | ||
| 
 | ||
| 				$domTagQuery = $selector->query('//link[@as="'.$params['as'].'"]');
 | ||
| 
 | ||
| 				if (count($domTagQuery) < 1) {
 | ||
| 					// No LINK tags found with the specified "as" attribute? Stop here!
 | ||
| 					return $htmlSource;
 | ||
| 				}
 | ||
| 
 | ||
| 				foreach($domTagQuery as $link) {
 | ||
| 					if ( ! $link->hasAttributes() ) {
 | ||
| 						continue;
 | ||
| 					}
 | ||
| 
 | ||
| 					$linkTagParts = array();
 | ||
| 					$linkTagParts[] = '<link ';
 | ||
| 
 | ||
| 					foreach ($link->attributes as $attr) {
 | ||
| 						$attrName = $attr->nodeName;
 | ||
| 						$attrValue = $attr->nodeValue;
 | ||
| 
 | ||
| 						if ($attrName) {
 | ||
| 							if ($attrValue !== '') {
 | ||
| 								$linkTagParts[] = '(\s+|)' . preg_quote($attrName, '/') . '(\s+|)=(\s+|)(|"|\')' . preg_quote($attrValue, '/') . '(|"|\')(|\s+)';
 | ||
| 							} else {
 | ||
| 								$linkTagParts[] = '(\s+|)' . preg_quote($attrName, '/') . '(|((\s+|)=(\s+|)(|"|\')(|"|\')))';
 | ||
| 							}
 | ||
| 						}
 | ||
| 					}
 | ||
| 
 | ||
| 					$linkTagParts[] = '(|\s+)(|/)>';
 | ||
| 
 | ||
| 					$linkTagFinalRegExPart = implode('', $linkTagParts);
 | ||
| 
 | ||
| 					preg_match_all(
 | ||
| 						'#'.$linkTagFinalRegExPart.'#Umi',
 | ||
| 						$htmlSource,
 | ||
| 						$matchSourceFromTag,
 | ||
| 						PREG_SET_ORDER
 | ||
| 					);
 | ||
| 
 | ||
| 					// It always has to be a match from the DOM generated tag
 | ||
| 					// Otherwise, default it to RegEx
 | ||
| 					if ( empty($matchSourceFromTag) || ! (isset($matchSourceFromTag[0]) && ! empty($matchSourceFromTag[0])) ) {
 | ||
| 						$fallbackToRegex = true;
 | ||
| 						break;
 | ||
| 					}
 | ||
| 
 | ||
| 					$matchesSourcesFromTags[] = $matchSourceFromTag[0];
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			/*
 | ||
| 			  * Option 2: Regular Expression (Fallback)
 | ||
| 			 */
 | ||
| 			if ($fallbackToRegex || ! Misc::isDOMDocumentOn()) {
 | ||
| 				preg_match_all( '#<link[^>]*(as(\s+|)=(\s+|)(|"|\')'.$params['as'].'(|"|\'))[^>]*>#Umi', $htmlSource, $matchesSourcesFromTags, PREG_SET_ORDER );
 | ||
| 			}
 | ||
| 
 | ||
| 			// Are there any preloaded / prefetched scripts that are inside the unloaded list?
 | ||
| 			// Strip the preloading tag as it's not relevant, since the script was unloaded
 | ||
| 			// These can be generated via plugins such as "Pre* Party Resource Hints" where users can manually insert scripts to preload
 | ||
| 			if ( ! empty( $matchesSourcesFromTags ) ) {
 | ||
| 				foreach ( $matchesSourcesFromTags as $matchedLink ) {
 | ||
| 					$matchedLinkTag = isset( $matchedLink[0] ) ? $matchedLink[0] : '';
 | ||
| 
 | ||
| 					if ( ! ( $matchedLinkTag && strpos( $matchedLinkTag, ' href' ) !== false ) ) {
 | ||
| 						continue;
 | ||
| 					}
 | ||
| 
 | ||
| 					foreach ( $params['unloaded_assets_rel_sources'] as $unloadedAssetRelSource ) {
 | ||
| 						if ( strpos( $matchedLinkTag, $unloadedAssetRelSource ) !== false ) {
 | ||
| 							$htmlSource = str_replace( $matchedLinkTag, '', $htmlSource );
 | ||
| 						}
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return $htmlSource;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return string
 | ||
| 	 */
 | ||
| 	public static function getRelPathPluginCacheDir()
 | ||
| 	{
 | ||
| 		// In some cases, hosting companies put restriction for writable folders
 | ||
| 		// Pantheon, for instance, allows only /wp-content/uploads/ to be writable
 | ||
| 		// For security reasons, do not allow ../
 | ||
| 		return ((defined('WPACU_CACHE_DIR') && strpos(WPACU_CACHE_DIR, '../') === false)
 | ||
| 			? WPACU_CACHE_DIR
 | ||
| 			: self::$relPathPluginCacheDirDefault);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * The following output is ONLY used for fetching purposes
 | ||
| 	 * It will not be part of the final output
 | ||
| 	 *
 | ||
| 	 * @param $htmlSourceToFetchFrom
 | ||
| 	 * @param $params
 | ||
| 	 *
 | ||
| 	 * @return string|string[]|null
 | ||
| 	 */
 | ||
| 	public static function cleanerHtmlSource($htmlSourceToFetchFrom, $params = array('strip_content_between_conditional_comments'))
 | ||
| 	{
 | ||
| 		if (in_array('for_fetching_link_tags', $params)) {
 | ||
| 			$htmlSourceToFetchFrom = preg_replace( array('@<(style|script|noscript)[^>]*?>.*?</\\1>@si', '#<(meta|img)([^<>]+)/?>#iU'), '', $htmlSourceToFetchFrom );
 | ||
| 		} else {
 | ||
| 			// Strip NOSCRIPT tags
 | ||
| 			$htmlSourceToFetchFrom = preg_replace( '@<(noscript)[^>]*?>.*?</\\1>@si', '', $htmlSourceToFetchFrom );
 | ||
| 		}
 | ||
| 
 | ||
| 		// Case: Return the HTML source without any conditional comments and the content within them
 | ||
| 		if (in_array('strip_content_between_conditional_comments', $params)) {
 | ||
| 			preg_match_all('#<!--\[if(.*?)]>(<!-->|-->|\s|)(.*?)(<!--<!|<!)\[endif]-->#si', $htmlSourceToFetchFrom, $matchedContent);
 | ||
| 
 | ||
| 			if (isset($matchedContent[0]) && ! empty($matchedContent[0])) {
 | ||
| 				foreach ($matchedContent[0] as $conditionalHtmlContent) {
 | ||
| 					$htmlSourceToFetchFrom = str_replace($conditionalHtmlContent, '', $htmlSourceToFetchFrom);
 | ||
| 				}
 | ||
| 
 | ||
| 				return $htmlSourceToFetchFrom;
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return $htmlSourceToFetchFrom;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Is this a regular WordPress page (not feed, REST API etc.)?
 | ||
| 	 * If not, do not proceed with any CSS/JS combine
 | ||
| 	 *
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function doCombineIsRegularPage()
 | ||
| 	{
 | ||
| 		// In particular situations, do not process this
 | ||
| 		if (strpos($_SERVER['REQUEST_URI'], '/'.Misc::getPluginsDir().'/') !== false
 | ||
| 		    && strpos($_SERVER['REQUEST_URI'], '/wp-content/themes/') !== false) {
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (Misc::endsWith($_SERVER['REQUEST_URI'], '/comments/feed/')) {
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (str_replace('//', '/', site_url() . '/feed/') === $_SERVER['REQUEST_URI']) {
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (is_feed()) { // any kind of feed page
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		return true;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $isFile
 | ||
| 	 * @param $localAssetPath
 | ||
| 	 * @param $assetHandle
 | ||
| 	 * @param $fileVer
 | ||
| 	 *
 | ||
| 	 * @return string
 | ||
| 	 */
 | ||
| 	public static function generateUniqueNameForCachedAsset($isFile, $localAssetPath, $assetHandle, $fileVer)
 | ||
| 	{
 | ||
| 		if ($isFile) {
 | ||
| 			$relPathToFileFiltered = str_replace(Misc::getWpRootDirPath(), '', $localAssetPath);
 | ||
| 
 | ||
| 			// Some people might use plugins to hide the fact that they are using WordPress
 | ||
| 			// Strip such information from the cached asset names as it's irrelevant for the visitor anyway
 | ||
| 			// if the cached file name loaded in the browser contain references to WordPress
 | ||
| 			foreach (array('wp-content/plugins', 'wp-content/themes', 'wp-', 'wordpress') as $toStripFromFileName) {
 | ||
| 				$relPathToFileFiltered = str_replace( $toStripFromFileName, '', $relPathToFileFiltered );
 | ||
| 			}
 | ||
| 
 | ||
| 			$relPathToFileFiltered = ltrim($relPathToFileFiltered, '/');
 | ||
| 
 | ||
| 			$sanitizedRelPathToFileFiltered = str_replace('/', '__', $relPathToFileFiltered);
 | ||
| 			$sanitizedRelPathToFileFiltered = sanitize_title($sanitizedRelPathToFileFiltered);
 | ||
| 			$uniqueOptimizedAssetName = $sanitizedRelPathToFileFiltered;
 | ||
| 		} else {
 | ||
| 			$uniqueOptimizedAssetName = sanitize_title( $assetHandle );
 | ||
| 		}
 | ||
| 
 | ||
| 		$uniqueOptimizedAssetName .= '-v' . $fileVer;
 | ||
| 
 | ||
| 		return $uniqueOptimizedAssetName;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $href
 | ||
| 	 * @param $assetType
 | ||
| 	 *
 | ||
| 	 * @return bool|string
 | ||
| 	 */
 | ||
| 	public static function getLocalAssetPath($href, $assetType)
 | ||
| 	{
 | ||
| 		// Check if it starts without "/" or a protocol; e.g. "wp-content/theme/style.css", "wp-content/theme/script.js"
 | ||
| 		if (strpos($href, '/') !== 0 &&
 | ||
| 		    strpos($href, '//') !== 0 &&
 | ||
| 		    stripos($href, 'http://') !== 0 &&
 | ||
| 			stripos($href, 'https://') !== 0
 | ||
| 		) {
 | ||
| 			$href = '/'.$href; // append the forward slash to be processed as relative later on
 | ||
| 		}
 | ||
| 
 | ||
| 		// starting with "/", but not with "//"
 | ||
| 		$isRelHref = (strpos($href, '/') === 0 && strpos($href, '//') !== 0);
 | ||
| 
 | ||
| 		if (! $isRelHref) {
 | ||
| 			$href = self::isSourceFromSameHost($href);
 | ||
| 
 | ||
| 			if (! $href) {
 | ||
| 				return false;
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		$hrefRelPath = self::getSourceRelPath($href);
 | ||
| 
 | ||
| 		if (strpos($hrefRelPath, '/') === 0) {
 | ||
| 			$hrefRelPath = substr($hrefRelPath, 1);
 | ||
| 		}
 | ||
| 
 | ||
| 		$localAssetPossiblePaths = array(Misc::getWpRootDirPath() . $hrefRelPath);
 | ||
| 
 | ||
| 		// Perhaps the URL starts with / (not //) and site_url() was not used
 | ||
| 		$parseSiteUrlPath = (string)parse_url(site_url(), PHP_URL_PATH);
 | ||
| 
 | ||
| 		// This is in case we have something like this in the source (hardcoded or generated through a plugin)
 | ||
| 		// /blog/wp-content/plugins/custom-plugin-slug/script.js
 | ||
| 		// and the site_url() is equal with https://www.mysite.com/blog
 | ||
| 		if ($parseSiteUrlPath !== '/' && strlen($parseSiteUrlPath) > 1 && strpos($href, $parseSiteUrlPath) === 0) {
 | ||
| 			$relPathFromWpRootDir = str_replace($parseSiteUrlPath, '', $href);
 | ||
| 			$altHrefRelPath = str_replace('//', '/', Misc::getWpRootDirPath() . $relPathFromWpRootDir);
 | ||
| 			$localAssetPossiblePaths[] = $altHrefRelPath;
 | ||
| 		}
 | ||
| 
 | ||
| 		foreach ($localAssetPossiblePaths as $localAssetPath) {
 | ||
| 			if ( strpos( $localAssetPath, '?ver' ) !== false ) {
 | ||
| 				list( $localAssetPathAlt, ) = explode( '?ver', $localAssetPath );
 | ||
| 				$localAssetPath = $localAssetPathAlt;
 | ||
| 			}
 | ||
| 
 | ||
| 			// Not using "?ver="
 | ||
| 			if ( strpos( $localAssetPath, '.' . $assetType . '?' ) !== false ) {
 | ||
| 				list( $localAssetPathAlt, ) = explode( '.' . $assetType . '?', $localAssetPath );
 | ||
| 				$localAssetPath = $localAssetPathAlt . '.' . $assetType;
 | ||
| 			}
 | ||
| 
 | ||
| 			if ( strrchr( $localAssetPath, '.' ) === '.' . $assetType && is_file( $localAssetPath ) ) {
 | ||
| 				return $localAssetPath;
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return false;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $assetHref
 | ||
| 	 *
 | ||
| 	 * @return array|false|string|string[]
 | ||
| 	 */
 | ||
| 	public static function getPathToAssetDir($assetHref)
 | ||
| 	{
 | ||
| 		$posLastSlash   = strrpos($assetHref, '/');
 | ||
| 		$pathToAssetDir = substr($assetHref, 0, $posLastSlash);
 | ||
| 
 | ||
| 		$parseUrl = parse_url($pathToAssetDir);
 | ||
| 
 | ||
| 		if (isset($parseUrl['scheme']) && $parseUrl['scheme'] !== '') {
 | ||
| 			$pathToAssetDir = str_replace(
 | ||
| 				array('http://'.$parseUrl['host'], 'https://'.$parseUrl['host']),
 | ||
| 				'',
 | ||
| 				$pathToAssetDir
 | ||
| 			);
 | ||
| 		} elseif (strpos($pathToAssetDir, '//') === 0) {
 | ||
| 			$pathToAssetDir = str_replace(
 | ||
| 				array('//'.$parseUrl['host'], '//'.$parseUrl['host']),
 | ||
| 				'',
 | ||
| 				$pathToAssetDir
 | ||
| 			);
 | ||
| 		}
 | ||
| 
 | ||
| 		return $pathToAssetDir;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $sourceTag
 | ||
| 	 *
 | ||
| 	 * @return array|bool
 | ||
| 	 */
 | ||
| 	public static function getLocalCleanSourceFromTag($sourceTag)
 | ||
| 	{
 | ||
| 		$sourceFromTag = Misc::getValueFromTag($sourceTag);
 | ||
| 
 | ||
| 		if (! $sourceFromTag) {
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Check if it starts without "/" or a protocol; e.g. "wp-content/theme/style.css", "wp-content/theme/script.js"
 | ||
| 		if (strpos($sourceFromTag, '/')   !== 0 &&
 | ||
| 		    strpos($sourceFromTag, '//')  !== 0 &&
 | ||
| 		    stripos($sourceFromTag, 'http://')   !== 0 &&
 | ||
| 		    stripos($sourceFromTag, 'https://')  !== 0
 | ||
| 		) {
 | ||
| 			$sourceFromTag = '/'.$sourceFromTag; // append the forward slash to be processed as relative later on
 | ||
| 		}
 | ||
| 
 | ||
| 		// Perhaps the URL starts with / (not //) and site_url() was not used
 | ||
| 		$altFilePathForRelSource = $isRelPath = false;
 | ||
| 		$parseSiteUrlPath = (string)parse_url(site_url(), PHP_URL_PATH);
 | ||
| 
 | ||
| 		// This is in case we have something like this in the HTML source (hardcoded or generated through a plugin)
 | ||
| 		// <link href="/blog/wp-content/plugins/custom-plugin-slug/script.js" rel="preload" as="script" type="text/javascript">
 | ||
| 		// and the site_url() is equal with https://www.mysite.com/blog
 | ||
| 		if ($parseSiteUrlPath !== '/' && strlen($parseSiteUrlPath) > 1 && strpos($sourceFromTag, $parseSiteUrlPath) !== false) {
 | ||
| 			$relPathFromRootDir = str_replace($parseSiteUrlPath, '', $sourceFromTag);
 | ||
| 			$altFilePathForRelSource = str_replace('//', '/', Misc::getWpRootDirPath() . $relPathFromRootDir);
 | ||
| 		} elseif (strpos($sourceFromTag, '/') === 0 && strpos($sourceFromTag, '//') !== 0) {
 | ||
| 			$altFilePathForRelSource = str_replace('//', '/', Misc::getWpRootDirPath() . $sourceFromTag);
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($altFilePathForRelSource && (strpos($altFilePathForRelSource, '.css?') !== false || strpos($altFilePathForRelSource, '.js?') !== false)) {
 | ||
| 			list($altFilePathForRelSource) = explode('?', $altFilePathForRelSource);
 | ||
| 		}
 | ||
| 
 | ||
| 		if ( $altFilePathForRelSource && (is_file(Misc::getWpRootDirPath() . $sourceFromTag) || is_file($altFilePathForRelSource)) ) {
 | ||
| 			$isRelPath = true;
 | ||
| 		}
 | ||
| 
 | ||
| 		// In case the match was something like "src='//mydomain.com/file.js'"
 | ||
| 		// Leave nothing to chance as often the prefix is stripped
 | ||
| 		$cleanSiteUrl = str_replace(array('http://', 'https://'), '//', site_url());
 | ||
| 
 | ||
| 		if ($isRelPath || (stripos($sourceFromTag, $cleanSiteUrl) !== false) || (stripos($sourceFromTag, site_url()) !== false)) {
 | ||
| 			$cleanSourceUrlFromTag = trim($sourceFromTag, '?&');
 | ||
| 			$afterQuestionMark = WPACU_PLUGIN_VERSION;
 | ||
| 
 | ||
| 			// Is it a dynamic URL? Keep the full path
 | ||
| 			if (strpos($cleanSourceUrlFromTag, '.php') !== false ||
 | ||
| 			    strpos($cleanSourceUrlFromTag, '/?') !== false ||
 | ||
| 			    strpos($cleanSourceUrlFromTag, rtrim(site_url(), '/').'?') !== false) {
 | ||
| 				list(,$afterQuestionMark) = explode('?', $sourceFromTag);
 | ||
| 			} elseif (strpos($sourceFromTag, '?') !== false) {
 | ||
| 				list($cleanSourceUrlFromTag, $afterQuestionMark) = explode('?', $sourceFromTag);
 | ||
| 			}
 | ||
| 
 | ||
| 			if (! $afterQuestionMark) {
 | ||
| 				return false;
 | ||
| 			}
 | ||
| 
 | ||
| 			return array('source' => $cleanSourceUrlFromTag, 'after_question_mark' => $afterQuestionMark);
 | ||
| 		}
 | ||
| 
 | ||
| 		return false;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $href
 | ||
| 	 *
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function isSourceFromSameHost($href)
 | ||
| 	{
 | ||
| 		// Check the host name
 | ||
| 		$siteDbUrl   = get_option('siteurl');
 | ||
| 		$siteUrlHost = strtolower(parse_url($siteDbUrl, PHP_URL_HOST));
 | ||
| 
 | ||
| 		$cdnUrls = self::getAnyCdnUrls();
 | ||
| 
 | ||
| 		// Are there any CDN urls set? Check them out
 | ||
| 		if (! empty($cdnUrls)) {
 | ||
| 			$hrefAlt = $href;
 | ||
| 
 | ||
| 			foreach ($cdnUrls as $cdnUrl) {
 | ||
| 				$hrefCleanedArray = self::getCleanHrefAfterCdnStrip(trim($cdnUrl), $hrefAlt);
 | ||
| 				$cdnNoPrefix = $hrefCleanedArray['cdn_no_prefix'];
 | ||
| 				$hrefAlt = $hrefCleanedArray['rel_href'];
 | ||
| 
 | ||
| 				if ($hrefAlt !== $href && stripos($href, '//'.$cdnNoPrefix) !== false) {
 | ||
| 					return $href;
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		if (strpos($href, '//') === 0) {
 | ||
| 			list ($urlPrefix) = explode('//', $siteDbUrl);
 | ||
| 			$href = $urlPrefix . $href;
 | ||
| 		}
 | ||
| 
 | ||
| 		/*
 | ||
| 		 * Validate it first
 | ||
| 		 */
 | ||
| 		$assetHost = strtolower(parse_url($href, PHP_URL_HOST));
 | ||
| 
 | ||
| 		if (preg_match('#'.$assetHost.'#si', implode('', self::$wellKnownExternalHosts))) {
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Different host name (most likely 3rd party one such as fonts.googleapis.com or an external CDN)
 | ||
| 		// Do not add it to the combine list
 | ||
| 		if ($assetHost !== $siteUrlHost) {
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		return $href;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $href
 | ||
| 	 *
 | ||
| 	 * @return mixed
 | ||
| 	 */
 | ||
| 	public static function getSourceRelPath($href)
 | ||
| 	{
 | ||
| 		// Already starts with / but not with //
 | ||
| 		// Path is relative, just return it
 | ||
| 		if (strpos($href, '/') === 0 && strpos($href, '//') !== 0) {
 | ||
| 			return $href;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Starts with // (protocol is missing)
 | ||
| 		// Add a dummy one to validate the whole URL and get the host
 | ||
| 		if (strpos($href, '//') === 0) {
 | ||
| 			$href = (Misc::isHttpsSecure() ? 'https:' : 'http:') . $href;
 | ||
| 		}
 | ||
| 
 | ||
| 		$parseUrl = parse_url($href);
 | ||
| 		$hrefHost = isset($parseUrl['host']) ? $parseUrl['host'] : false;
 | ||
| 
 | ||
| 		if (! $hrefHost) {
 | ||
| 			return $href;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Sometimes host is different on Staging websites such as the ones from Siteground
 | ||
| 		// e.g. staging1.domain.com and domain.com
 | ||
| 		// We need to make sure that the URI path is fetched correctly based on the host value from the $href
 | ||
| 		$siteDbUrl      = get_option('siteurl');
 | ||
| 		$parseDbSiteUrl = parse_url($siteDbUrl);
 | ||
| 
 | ||
| 		$dbSiteUrlHost = $parseDbSiteUrl['host'];
 | ||
| 
 | ||
| 		$finalBaseUrl = str_replace($dbSiteUrlHost, $hrefHost, $siteDbUrl);
 | ||
| 
 | ||
| 		$hrefAlt = $finalRelPath = $href;
 | ||
| 
 | ||
| 		$cdnUrls = self::getAnyCdnUrls();
 | ||
| 
 | ||
| 		// Are there any CDN urls set? Filter them out in order to retrieve the relative path
 | ||
| 		if (! empty($cdnUrls)) {
 | ||
| 			foreach ($cdnUrls as $cdnUrl) {
 | ||
| 				$hrefCleanArray = self::getCleanHrefAfterCdnStrip(trim($cdnUrl), $hrefAlt);
 | ||
| 				$cdnNoPrefix = $hrefCleanArray['cdn_no_prefix'];
 | ||
| 
 | ||
| 				$finalRelPath = str_replace(
 | ||
| 					array('http://'.$cdnNoPrefix, 'https://'.$cdnNoPrefix, '//'.$cdnNoPrefix),
 | ||
| 					'',
 | ||
| 					$finalRelPath
 | ||
| 				);
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		if (strpos($finalRelPath, 'http') === 0) {
 | ||
| 			list(,$noProtocol) = explode('://', $finalBaseUrl);
 | ||
| 			$finalBaseUrls = array(
 | ||
| 				'http://'.$noProtocol,
 | ||
| 				'https://'.$noProtocol
 | ||
| 			);
 | ||
| 		} else {
 | ||
| 			$finalBaseUrls = array($finalBaseUrl);
 | ||
| 		}
 | ||
| 
 | ||
| 		$finalRelPath = str_replace($finalBaseUrls, '', $finalRelPath);
 | ||
| 
 | ||
| 		if (defined('WP_ROCKET_CACHE_BUSTING_URL') && function_exists('get_current_blog_id') && get_current_blog_id()) {
 | ||
| 			$finalRelPath = str_replace(
 | ||
| 				array(WP_ROCKET_CACHE_BUSTING_URL . get_current_blog_id(), WP_ROCKET_CACHE_BUSTING_URL),
 | ||
| 				'',
 | ||
| 				$finalRelPath
 | ||
| 			);
 | ||
| 		}
 | ||
| 
 | ||
| 		return $finalRelPath;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $cdnUrl
 | ||
| 	 * @param $hrefAlt
 | ||
| 	 *
 | ||
| 	 * @return array
 | ||
| 	 */
 | ||
| 	public static function getCleanHrefAfterCdnStrip($cdnUrl, $hrefAlt)
 | ||
| 	{
 | ||
| 		if (strpos($cdnUrl, '//') !== false) {
 | ||
| 			$parseUrl = parse_url($cdnUrl);
 | ||
| 			$cdnNoPrefix = $parseUrl['host'];
 | ||
| 
 | ||
| 			if (isset($parseUrl['path']) && $parseUrl['path'] !== '') {
 | ||
| 				$cdnNoPrefix .= $parseUrl['path'];
 | ||
| 			}
 | ||
| 		} else {
 | ||
| 			$cdnNoPrefix = $cdnUrl; // CNAME
 | ||
| 		}
 | ||
| 
 | ||
| 		$hrefAlt = str_ireplace(array('http://' . $cdnNoPrefix, 'https://' . $cdnNoPrefix, '//'.$cdnNoPrefix), '', $hrefAlt);
 | ||
| 
 | ||
| 		return array('cdn_no_prefix' => $cdnNoPrefix, 'rel_href' => $hrefAlt);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $jsonStorageFile
 | ||
| 	 * @param $relPathAssetCacheDir
 | ||
| 	 * @param $assetType
 | ||
| 	 * @param $forType
 | ||
| 	 *
 | ||
| 	 * @return array|mixed|object
 | ||
| 	 */
 | ||
| 	public static function getAssetCachedData($jsonStorageFile, $relPathAssetCacheDir, $assetType, $forType = 'combine')
 | ||
| 	{
 | ||
| 		if ($forType === 'combine') {
 | ||
| 			// Only clean request URIs allowed
 | ||
| 			if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
 | ||
| 				list($requestUri) = explode('?', $_SERVER['REQUEST_URI']);
 | ||
| 			} else {
 | ||
| 				$requestUri = $_SERVER['REQUEST_URI'];
 | ||
| 			}
 | ||
| 
 | ||
| 			$requestUriPart = $requestUri;
 | ||
| 
 | ||
| 			// Same results for Homepage (any pagination), 404 Not Found & Date archive pages
 | ||
| 			// The JSON files will get stored in the root directory of the targeted website
 | ||
| 			if ($requestUri === '/' || is_404() || is_date() || Misc::isHomePage()) {
 | ||
| 				$requestUriPart = '';
 | ||
| 			}
 | ||
| 
 | ||
| 			// Treat the pagination pages the same as the main page (same it's done for the unloading rules)
 | ||
| 			if (($currentPageNo = get_query_var('paged')) && (is_archive() || is_singular())) {
 | ||
| 				$paginationBase = isset($GLOBALS['wp_rewrite']->pagination_base) ? $GLOBALS['wp_rewrite']->pagination_base : 'page';
 | ||
| 				$requestUriPart = str_replace('/'.$paginationBase.'/'.$currentPageNo.'/', '', $requestUriPart);
 | ||
| 			}
 | ||
| 
 | ||
| 			$dirToFilename = WP_CONTENT_DIR . dirname($relPathAssetCacheDir) . '/_storage/'
 | ||
| 			                 . parse_url(site_url(), PHP_URL_HOST) .
 | ||
| 			                 $requestUriPart . '/';
 | ||
| 
 | ||
| 			$dirToFilename = str_replace('//', '/', $dirToFilename);
 | ||
| 
 | ||
| 			$assetsFile = $dirToFilename . self::filterStorageFileName($jsonStorageFile);
 | ||
| 		} elseif ($forType === 'item') {
 | ||
| 			$dirToFilename = WP_CONTENT_DIR . dirname($relPathAssetCacheDir) . '/_storage/'.self::$optimizedSingleFilesDir.'/';
 | ||
| 			$assetsFile = $dirToFilename . $jsonStorageFile;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (! is_file($assetsFile)) {
 | ||
| 			return array();
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($assetType === 'css' || $assetType === 'js') {
 | ||
| 			$cachedAssetsFileExpiresIn = self::$cachedAssetFileExpiresIn;
 | ||
| 		} else {
 | ||
| 			return array();
 | ||
| 		}
 | ||
| 
 | ||
| 		// Delete cached file after it expired as it will be regenerated
 | ||
| 		if (filemtime($assetsFile) < (time() - $cachedAssetsFileExpiresIn)) {
 | ||
| 			self::clearAssetCachedData($jsonStorageFile);
 | ||
| 			return array();
 | ||
| 		}
 | ||
| 
 | ||
| 		$optionValue = FileSystem::fileGetContents($assetsFile);
 | ||
| 
 | ||
| 		if ($optionValue) {
 | ||
| 			$optionValueArray = @json_decode($optionValue, ARRAY_A);
 | ||
| 
 | ||
| 			if ($forType === 'combine') {
 | ||
| 				if (! empty($optionValueArray)) {
 | ||
| 					foreach ($optionValueArray as $assetsValues) {
 | ||
| 						foreach ($assetsValues as $finalValues) {
 | ||
| 							// Check if the combined CSS file exists (e.g. maybe it was removed by mistake from the caching directory
 | ||
| 							// Or it wasn't created in the first place due to an error
 | ||
| 							if ($assetType === 'css' && isset($finalValues['uri_to_final_css_file'], $finalValues['link_hrefs'])
 | ||
| 							    && is_file(WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() . $finalValues['uri_to_final_css_file'])) {
 | ||
| 								return $optionValueArray;
 | ||
| 							}
 | ||
| 
 | ||
| 							// Check if the combined JS file exists (e.g. maybe it was removed by mistake from the caching directory
 | ||
| 							// Or it wasn't created in the first place due to an error
 | ||
| 							if ($assetType === 'js' && isset($finalValues['uri_to_final_js_file'], $finalValues['script_srcs'])
 | ||
| 							    && is_file(WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir() . $finalValues['uri_to_final_js_file'])) {
 | ||
| 								return $optionValueArray;
 | ||
| 							}
 | ||
| 						}
 | ||
| 					}
 | ||
| 				}
 | ||
| 			} elseif ($forType === 'item') {
 | ||
| 				return $optionValueArray;
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		// File exists, but it's invalid or outdated; Delete it as it has to be re-generated
 | ||
| 		self::clearAssetCachedData($jsonStorageFile);
 | ||
| 
 | ||
| 		return array();
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $jsonStorageFile
 | ||
| 	 * @param $relPathAssetCacheDir
 | ||
| 	 * @param $list
 | ||
| 	 * @param $forType
 | ||
| 	 */
 | ||
| 	public static function setAssetCachedData($jsonStorageFile, $relPathAssetCacheDir, $list, $forType = 'combine')
 | ||
| 	{
 | ||
| 		// Combine CSS/JS JSON Storage
 | ||
| 		if ($forType === 'combine') {
 | ||
| 			// Only clean request URIs allowed
 | ||
| 			if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
 | ||
| 				list($requestUri) = explode('?', $_SERVER['REQUEST_URI']);
 | ||
| 			} else {
 | ||
| 				$requestUri = $_SERVER['REQUEST_URI'];
 | ||
| 			}
 | ||
| 
 | ||
| 			$requestUriPart = $requestUri;
 | ||
| 
 | ||
| 			// Same results for Homepage (any pagination), 404 Not Found & Date archive pages
 | ||
| 			if ($requestUri === '/' || is_404() || is_date() || Misc::isHomePage()) {
 | ||
| 				$requestUriPart = '';
 | ||
| 			}
 | ||
| 
 | ||
| 			// Treat the pagination pages the same as the main page (same it's done for the unloading rules)
 | ||
| 			if (($currentPage = get_query_var('paged')) && (is_archive() || is_singular())) {
 | ||
| 				$paginationBase = isset($GLOBALS['wp_rewrite']->pagination_base) ? $GLOBALS['wp_rewrite']->pagination_base : 'page';
 | ||
| 				$requestUriPart = str_replace('/'.$paginationBase.'/'.$currentPage.'/', '', $requestUriPart);
 | ||
| 			}
 | ||
| 
 | ||
| 			$dirToFilename = WP_CONTENT_DIR . dirname($relPathAssetCacheDir) . '/_storage/'
 | ||
| 			                 . parse_url(site_url(), PHP_URL_HOST) .
 | ||
| 			                 $requestUriPart . '/';
 | ||
| 
 | ||
| 			$dirToFilename = str_replace('//', '/', $dirToFilename);
 | ||
| 
 | ||
| 			if (! is_dir($dirToFilename)) {
 | ||
| 				$makeFileDir = @mkdir($dirToFilename, FS_CHMOD_DIR, true);
 | ||
| 
 | ||
| 				if (! $makeFileDir) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			$assetsFile = $dirToFilename . self::filterStorageFileName($jsonStorageFile);
 | ||
| 
 | ||
| 			// CSS/JS JSON FILE DATA
 | ||
| 			$assetsValue = $list;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Optimize single CSS/JS item JSON Storage
 | ||
| 		if ($forType === 'item') {
 | ||
| 			$dirToFilename = WP_CONTENT_DIR . dirname($relPathAssetCacheDir) . '/_storage/'.self::$optimizedSingleFilesDir.'/';
 | ||
| 
 | ||
| 			$dirToFilename = str_replace('//', '/', $dirToFilename);
 | ||
| 
 | ||
| 			if (! is_dir($dirToFilename)) {
 | ||
| 				$makeFileDir = @mkdir($dirToFilename, FS_CHMOD_DIR, true);
 | ||
| 
 | ||
| 				if (! $makeFileDir) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			$assetsFile = $dirToFilename . $jsonStorageFile;
 | ||
| 			$assetsValue = $list;
 | ||
| 		}
 | ||
| 
 | ||
| 		FileSystem::filePutContents($assetsFile, $assetsValue);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $jsonStorageFile
 | ||
| 	 */
 | ||
| 	public static function clearAssetCachedData($jsonStorageFile)
 | ||
| 	{
 | ||
| 		if (strpos($jsonStorageFile, '-combined') !== false) {
 | ||
| 			/*
 | ||
| 	        * #1: Combined CSS/JS JSON
 | ||
| 	        */
 | ||
| 			// Only clean request URIs allowed
 | ||
| 			if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
 | ||
| 				list($requestUri) = explode('?', $_SERVER['REQUEST_URI']);
 | ||
| 			} else {
 | ||
| 				$requestUri = $_SERVER['REQUEST_URI'];
 | ||
| 			}
 | ||
| 
 | ||
| 			$requestUriPart = $requestUri;
 | ||
| 
 | ||
| 			// Same results for Homepage (any pagination), 404 Not Found & Date archive pages
 | ||
| 			if ($requestUri === '/' || is_404() || is_date() || Misc::isHomePage()) {
 | ||
| 				$requestUriPart = '';
 | ||
| 			}
 | ||
| 
 | ||
| 			// Treat the pagination pages the same as the main page (same it's done for the unloading rules)
 | ||
| 			if (($currentPage = get_query_var('paged')) && (is_archive() || is_singular())) {
 | ||
| 				$paginationBase = isset($GLOBALS['wp_rewrite']->pagination_base) ? $GLOBALS['wp_rewrite']->pagination_base : 'page';
 | ||
| 				$requestUriPart = str_replace('/'.$paginationBase.'/'.$currentPage.'/', '', $requestUriPart);
 | ||
| 			}
 | ||
| 
 | ||
| 			$dirToFilename = WP_CONTENT_DIR . self::getRelPathPluginCacheDir() . '_storage/'
 | ||
| 			                 . parse_url(site_url(), PHP_URL_HOST) .
 | ||
| 			                 $requestUriPart;
 | ||
| 
 | ||
| 			// If it doesn't have "/" at the end, append it (it will prevent double forward slashes)
 | ||
| 			if (substr($dirToFilename, - 1) !== '/') {
 | ||
| 				$dirToFilename .= '/';
 | ||
| 			}
 | ||
| 
 | ||
| 			$assetsFile = $dirToFilename . self::filterStorageFileName($jsonStorageFile);
 | ||
| 		} elseif (strpos($jsonStorageFile, '_optimize_') !== false) {
 | ||
| 			/*
 | ||
| 			 * #2: Optimized CSS/JS JSON
 | ||
| 			 */
 | ||
| 			$dirToFilename = WP_CONTENT_DIR . self::getRelPathPluginCacheDir() . '_storage/'.self::$optimizedSingleFilesDir.'/';
 | ||
| 			$assetsFile = $dirToFilename . $jsonStorageFile;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (is_file($assetsFile)) { // avoid E_WARNING errors | check if it exists first
 | ||
| 			@unlink($assetsFile);
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Clears all CSS & JS cache
 | ||
| 	 *
 | ||
| 	 * @param bool $redirectAfter
 | ||
| 	 */
 | ||
| 	public static function clearCache($redirectAfter = false)
 | ||
| 	{
 | ||
| 		if (self::doNotClearCache()) {
 | ||
| 			return;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Any actions before clearing the cache?
 | ||
| 		do_action('wpacu_clear_cache_before');
 | ||
| 
 | ||
| 		// No settings available? Must be triggered very early before 'init' action hook; Get the settings!
 | ||
| 		if ( ! isset(Main::instance()->settings['clear_cached_files_after']) ) {
 | ||
| 			$wpacuSettingsClass = new Settings();
 | ||
| 			Main::instance()->settings = $wpacuSettingsClass->getAll();
 | ||
| 		}
 | ||
| 
 | ||
| 		$isUriRequest = isset($_GET['wpacu_clear_cache_print']);
 | ||
| 		$isAjaxCallOrUriRequest = (isset($_REQUEST['action']) && $_REQUEST['action'] === WPACU_PLUGIN_ID . '_clear_cache' && is_admin()) || $isUriRequest;
 | ||
| 		$clearedOutput = $keptOutput = array();
 | ||
| 
 | ||
| 		/*
 | ||
| 		 * STEP 1: Clear all JSON & all assets (.css & .js) files older than $clearFilesOlderThan days
 | ||
| 		 */
 | ||
| 		$skipFiles       = array('index.php', '.htaccess');
 | ||
| 		$fileExtToRemove = array('.json', '.css', '.js');
 | ||
| 
 | ||
| 		$clearFilesOlderThanXDays = (int)Main::instance()->settings['clear_cached_files_after']; // days
 | ||
| 
 | ||
| 		$assetCleanUpCacheDir = WP_CONTENT_DIR . self::getRelPathPluginCacheDir();
 | ||
| 		$storageDir           = $assetCleanUpCacheDir . '_storage';
 | ||
| 
 | ||
| 		/*
 | ||
| 		 * Targeted directories:
 | ||
| 		 *
 | ||
| 		 * $storageDir.'/item/'
 | ||
| 		 * $assetCleanUpCacheDir.'/css/'
 | ||
| 		 * $assetCleanUpCacheDir.'/js/'
 | ||
| 		 *
 | ||
| 		 * SKIP anything else from $storageDir apart from "item"
 | ||
| 		 * If a lot of posts are on the website and combine CSS/JS it could lead to memory errors (to be cleared later on)
 | ||
| 		 */
 | ||
| 
 | ||
| 		$userIdDirs = array();
 | ||
| 
 | ||
| 		if (is_dir($assetCleanUpCacheDir)) {
 | ||
| 			$storageEmptyDirs = $allClearableAssets = $allAssetsToKeep = array();
 | ||
| 
 | ||
| 			$siteHost = (string)parse_url(site_url(), PHP_URL_HOST);
 | ||
| 			$siteUri  = (string)parse_url(site_url(), PHP_URL_PATH);
 | ||
| 
 | ||
| 			$relPathToPossibleDir = $storageDir.'/'.$siteHost . $siteUri;
 | ||
| 
 | ||
| 			$targetedDirs = array(
 | ||
| 				$storageDir.'/item/',
 | ||
| 				$assetCleanUpCacheDir.'css/',
 | ||
| 				$assetCleanUpCacheDir.'js/',
 | ||
| 				// Possible common directories with fewer files
 | ||
| 				$relPathToPossibleDir.'/category/',
 | ||
| 				$relPathToPossibleDir.'/author/',
 | ||
| 				$relPathToPossibleDir.'/tag/'
 | ||
| 			);
 | ||
| 
 | ||
| 			foreach ( $targetedDirs as $targetedDir ) {
 | ||
| 				$targetedDir = rtrim(str_replace('//', '/', $targetedDir), '/'); // clean it
 | ||
| 
 | ||
| 				if ( ! is_dir($targetedDir) ) { continue; }
 | ||
| 
 | ||
| 				$dirItems = new \RecursiveDirectoryIterator( $targetedDir, \RecursiveDirectoryIterator::SKIP_DOTS );
 | ||
| 
 | ||
| 				foreach (
 | ||
| 					new \RecursiveIteratorIterator(
 | ||
| 						$dirItems,
 | ||
| 						\RecursiveIteratorIterator::SELF_FIRST,
 | ||
| 						\RecursiveIteratorIterator::CATCH_GET_CHILD
 | ||
| 					) as $item
 | ||
| 				) {
 | ||
| 					$fileMtime    = filemtime($item);
 | ||
| 					$fileBaseName = trim( strrchr( $item, '/' ), '/' );
 | ||
| 					$fileExt      = strrchr( $fileBaseName, '.' );
 | ||
| 
 | ||
| 					if ( is_file( $item ) && in_array( $fileExt, $fileExtToRemove ) && ( ! in_array( $fileBaseName, $skipFiles ) ) ) {
 | ||
| 						$isJsonFile = ( $fileExt === '.json' );
 | ||
| 						$isAssetFile = in_array( $fileExt, array( '.css', '.js' ) );
 | ||
| 
 | ||
| 						// Remove all JSONs & .css & .js (depending on other things as well) ONLY if they are older than $clearFilesOlderThanXDays days (at least one day)
 | ||
| 						$clearOlderThanInSeconds = self::$cachedAssetFileExpiresIn; // minimum
 | ||
| 
 | ||
| 						if ($clearFilesOlderThanXDays > 0) {
 | ||
| 							$clearOlderThanInSeconds = (86400 * $clearFilesOlderThanXDays); // 1 day = 86400 seconds
 | ||
| 						}
 | ||
| 
 | ||
| 						// Conditions to delete the cached CSS/JS file:
 | ||
| 						// 1) It's older than $clearOlderThanInSeconds since its content was modified
 | ||
| 						// 2) It's not within the most recent cached files from /_storage/_recent_items/ (the latest cached assets should always be kept)
 | ||
| 
 | ||
| 						// $clearFilesOlderThanXDays is taken from
 | ||
| 						// "Settings" -> "Plugin Usage Preferences" -> "Clear cached CSS/JS files older than (x) days"
 | ||
| 						$isAssetFileToClear = ( $isAssetFile &&
 | ||
| 					                          ( strtotime( '-' . $clearOlderThanInSeconds . ' seconds' ) > $fileMtime ) );
 | ||
| 
 | ||
| 						if ( $isJsonFile || $isAssetFileToClear ) {
 | ||
| 							if ( $isJsonFile ) {
 | ||
| 								// Clear the JSON files as new ones will be generated
 | ||
| 								@unlink($item);
 | ||
| 								// [clear output]
 | ||
| 								if ($isAjaxCallOrUriRequest && ! is_file($item)) { $clearedOutput[] = $item. ' (storage file)'; }
 | ||
| 								// [/clear output]
 | ||
| 							}
 | ||
| 
 | ||
| 							if ( $isAssetFileToClear ) {
 | ||
| 								$allClearableAssets[] = $item;
 | ||
| 							}
 | ||
| 						}
 | ||
| 					} elseif ( is_dir( $item ) && ( strpos( $item, '/css/logged-in/' ) !== false || strpos( $item, '/js/logged-in/' ) !== false ) ) {
 | ||
| 						$userIdDirs[] = $item;
 | ||
| 					} elseif ( $item != $storageDir && strpos( $item, $storageDir ) !== false ) {
 | ||
| 						$storageEmptyDirs[] = $item;
 | ||
| 					}
 | ||
| 				}
 | ||
| 
 | ||
| 				Misc::rmDir($targetedDir); // if it's empty, remove it
 | ||
| 			}
 | ||
| 
 | ||
| 			if ( ! defined('WPACU_SITE_URL_HOST') ) {
 | ||
| 				define( 'WPACU_SITE_URL_HOST', parse_url(site_url(), PHP_URL_HOST) );
 | ||
| 			}
 | ||
| 
 | ||
| 			// Clear all JSON files separately from the storage directory as it will be rebuilt
 | ||
| 			self::rmNonEmptyJsonStorageDir($storageDir);
 | ||
| 
 | ||
| 			// Now go through the JSONs and collect the latest assets, so they would be kept
 | ||
| 			// Finally, collect the rest of $allAssetsToKeep from the database transients (if any)
 | ||
| 			// Do not check if they are expired or not as their assets could still be referenced
 | ||
| 			// until those pages will be accessed in a non-cached way
 | ||
| 			global $wpdb;
 | ||
| 
 | ||
| 			if (in_array(Main::instance()->settings['fetch_cached_files_details_from'], array('db', 'db_disk'))) {
 | ||
| 				$sqlGetCacheTransients = <<<SQL
 | ||
| SELECT option_value FROM `{$wpdb->options}`
 | ||
| WHERE `option_name` LIKE '%transient_wpacu_css_optimize%' OR `option_name` LIKE '%transient_wpacu_js_optimize%'
 | ||
| SQL;
 | ||
| 				$cacheDbTransients   = $wpdb->get_col( $sqlGetCacheTransients );
 | ||
| 
 | ||
| 				if (! empty($cacheDbTransients)) {
 | ||
| 					foreach ($cacheDbTransients as $optionValue) {
 | ||
| 						$jsonValueArray = @json_decode($optionValue, ARRAY_A);
 | ||
| 
 | ||
| 						if (isset($jsonValueArray['optimize_uri'])) {
 | ||
| 							$allAssetsToKeep[] = rtrim(Misc::getWpRootDirPath(), '/') . $jsonValueArray['optimize_uri'];
 | ||
| 						}
 | ||
| 					}
 | ||
| 				}
 | ||
| 			} elseif (Main::instance()->settings['fetch_cached_files_details_from'] === 'disk') {
 | ||
| 				// Since the asset's info is retrieved ONLY from the disk, any transients in the database are irrelevant, thus clear them
 | ||
| 				$sqlClearCacheTransients = <<<SQL
 | ||
| DELETE FROM `{$wpdb->options}`
 | ||
| WHERE `option_name` LIKE '%transient_wpacu_css_optimize%' OR `option_name` LIKE '%transient_wpacu_js_optimize%'
 | ||
| SQL;
 | ||
| 				$wpdb->query( $sqlClearCacheTransients );
 | ||
| 			}
 | ||
| 
 | ||
| 			/* [clear output] */
 | ||
| 			if ($isAjaxCallOrUriRequest) {
 | ||
| 				foreach ($allAssetsToKeep as $assetToKeep) {
 | ||
| 					$keptOutput[] = $assetToKeep . ' (cached asset file)';
 | ||
| 				}
 | ||
| 			}
 | ||
| 			/* [/clear output] */
 | ||
| 
 | ||
| 			sort($allAssetsToKeep);
 | ||
| 			$allAssetsToKeep = array_unique($allAssetsToKeep);
 | ||
| 
 | ||
| 			// Finally clear the matched assets, except the active ones
 | ||
| 			foreach ($allClearableAssets as $assetFile) {
 | ||
| 				if (in_array($assetFile, $allAssetsToKeep)) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 				@unlink($assetFile);
 | ||
| 				/* [clear output] */if ($isAjaxCallOrUriRequest && ! is_file($assetFile)) { $clearedOutput[] = $assetFile. ' (cached asset file)'; }/* [/clear output] */
 | ||
| 			}
 | ||
| 
 | ||
| 			foreach (array_reverse($storageEmptyDirs) as $storageEmptyDir) {
 | ||
| 				Misc::rmDir($storageEmptyDir);
 | ||
| 				/* [clear output] */if ($isAjaxCallOrUriRequest && ! is_dir($storageEmptyDir)) { $clearedOutput[] = $storageEmptyDir. ' (storage empty directory)'; }/* [/clear output] */
 | ||
| 			}
 | ||
| 
 | ||
| 			// Remove empty dirs from /css/logged-in/ and /js/logged-in/
 | ||
| 			if (! empty($userIdDirs)) {
 | ||
| 				foreach ($userIdDirs as $userIdDir) {
 | ||
| 					Misc::rmDir($userIdDir); // it needs to be empty, otherwise, it will not be removed
 | ||
| 					/* [clear output] */if ($isAjaxCallOrUriRequest && ! is_dir($userIdDir)) { $clearedOutput[] = $userIdDir. ' (user empty directory)'; }/* [/clear output] */
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		self::clearAllCacheOldLegacyDirs();
 | ||
| 		self::clearAllCacheInlineContentFromTagsNonStatic();
 | ||
| 
 | ||
| 		/*
 | ||
| 		 * STEP 2: Remove all transients related to the Minify CSS/JS files feature
 | ||
| 		 */
 | ||
| 		$toolsClass = new Tools();
 | ||
| 		$toolsClass->clearAllCacheTransients();
 | ||
| 
 | ||
| 		// Make sure all the caching files/folders are there in case the plugin was upgraded
 | ||
| 		Plugin::createCacheFoldersFiles(array('css', 'js'));
 | ||
| 
 | ||
| 		if ($isAjaxCallOrUriRequest) {
 | ||
| 			if (! empty($clearedOutput)) {
 | ||
| 				echo 'The following files/directories have been cleared:'."\n";
 | ||
| 				if ($isUriRequest) { echo '<br />'; }
 | ||
| 
 | ||
| 				foreach ($clearedOutput as $clearedInfo) {
 | ||
| 					echo esc_html($clearedInfo)."\n";
 | ||
| 					if ($isUriRequest) { echo '<br />'; }
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			if (! empty($keptOutput)) {
 | ||
| 				echo "\n".'The following files have been kept:'."\n";
 | ||
| 				if ($isUriRequest) { echo '<br />'; }
 | ||
| 
 | ||
| 				foreach ($keptOutput as $keptInfo) {
 | ||
| 					echo esc_html($keptInfo)."\n";
 | ||
| 					if ($isUriRequest) { echo '<br />'; }
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		// Any actions after clearing the cache?
 | ||
| 		do_action('wpacu_clear_cache_after');
 | ||
| 
 | ||
| 		// [START - Clear cache for other plugins if they are enabled]
 | ||
| 		// If, for any reason, someone uses Cache Enabler and want to prevent clearing its cache after Asset CleanUp Pro clears its own cache
 | ||
| 		// they can do so via the following code (e.g. in functions.php of their Child theme):
 | ||
| 		// add_filter('wpacu_clear_cache_enabler_cache', '__return_false');
 | ||
| 		if (assetCleanUpClearCacheEnablerCache()) {
 | ||
| 			if ($isAjaxCallOrUriRequest) {
 | ||
| 				echo '<br />"Cache Enabler" plugin is active. The following action was called: "cache_enabler_clear_complete_cache"';
 | ||
| 			}
 | ||
| 			do_action('cache_enabler_clear_complete_cache'); // Cache Enabler
 | ||
| 		}
 | ||
| 
 | ||
| 		// [END - Clear cache for other plugins if they are enabled]
 | ||
| 
 | ||
| 		set_transient('wpacu_last_clear_cache', time());
 | ||
| 
 | ||
| 		if ($isUriRequest) {
 | ||
| 			exit();
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($redirectAfter && wp_get_referer()) {
 | ||
| 			wp_safe_redirect(wp_get_referer());
 | ||
| 			exit();
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Special Case: Any CSS/JS files from /wp-content/cache//asset-cleanup/(css|js)/item/inline/
 | ||
| 	 * These files are never loaded as static, externally (from LINK or SCRIPT tag);
 | ||
| 	 * Their content is just pulled (if not expired) into the STYLE/SCRIPT inline tag
 | ||
| 	 * If there are any expired files there, remove them
 | ||
| 	 *
 | ||
| 	 * @return void
 | ||
| 	 */
 | ||
| 	public static function clearAllCacheInlineContentFromTagsNonStatic()
 | ||
| 	{
 | ||
| 		foreach (array('.css', '.js') as $assetExt) {
 | ||
| 			$assetTypeDir = ($assetExt === '.css') ? OptimizeCss::getRelPathCssCacheDir() : OptimizeJs::getRelPathJsCacheDir();
 | ||
| 
 | ||
| 			$assetsInlineTagsContentDir = WP_CONTENT_DIR . $assetTypeDir . self::$optimizedSingleFilesDir . '/inline/';
 | ||
| 
 | ||
| 			if ( is_dir( $assetsInlineTagsContentDir ) ) {
 | ||
| 				$assetInlineTagsContentDirFiles = scandir( $assetsInlineTagsContentDir );
 | ||
| 
 | ||
| 				foreach ( $assetInlineTagsContentDirFiles as $assetFile ) {
 | ||
| 					if ( strpos( $assetFile, $assetExt ) === false ) {
 | ||
| 						continue;
 | ||
| 					}
 | ||
| 
 | ||
| 					$fullPathToFile = $assetsInlineTagsContentDir . $assetFile;
 | ||
| 
 | ||
| 					$isExpired = ( ( time() - 1 * self::$cachedAssetFileExpiresIn ) > filemtime( $fullPathToFile ) );
 | ||
| 
 | ||
| 					if ( $isExpired ) {
 | ||
| 						@unlink( $fullPathToFile );
 | ||
| 					}
 | ||
| 				}
 | ||
| 
 | ||
| 				}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return void
 | ||
| 	 */
 | ||
| 	public static function clearAllCacheOldLegacyDirs()
 | ||
| 	{
 | ||
| 		if (is_dir(WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() .'min')) { Misc::rmDir( WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() .'min' ); }
 | ||
| 		if (is_dir(WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir()   .'min')) { Misc::rmDir( WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir()   .'min' ); }
 | ||
| 		if (is_dir(WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() .'one')) { Misc::rmDir( WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() .'one' ); }
 | ||
| 		if (is_dir(WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir()   .'one')) { Misc::rmDir( WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir()   .'one' ); }
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Alias for clearCache() - some developers might have implemented the old clearAllCache()
 | ||
| 	 *
 | ||
| 	 * @param bool $redirectAfter
 | ||
| 	 */
 | ||
| 	public static function clearAllCache($redirectAfter = false)
 | ||
| 	{
 | ||
| 		self::clearCache($redirectAfter);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * This is usually done when the plugin is deactivated
 | ||
| 	 * e.g. if you use Autoptimize, and it remains active, you will likely want to have its caching cleared with traces from Asset CleanUp
 | ||
| 	 */
 | ||
| 	public static function clearOtherPluginsCache()
 | ||
| 	{
 | ||
| 		self::clearAutoptimizeCache();
 | ||
| 		self::clearCacheEnablerCache();
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return void
 | ||
| 	 */
 | ||
| 	public static function clearAutoptimizeCache()
 | ||
| 	{
 | ||
| 		if ( assetCleanUpClearAutoptimizeCache() && Misc::isPluginActive('autoptimize/autoptimize.php')
 | ||
| 		     && class_exists('\autoptimizeCache')
 | ||
| 		     && method_exists('\autoptimizeCache', 'clearall') ) {
 | ||
| 			\autoptimizeCache::clearall();
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param string $triggeredFrom (e.g. ajax_call)
 | ||
| 	 *
 | ||
| 	 * @return void
 | ||
| 	 */
 | ||
| 	public static function clearCacheEnablerCache($triggeredFrom = '')
 | ||
| 	{
 | ||
| 		$isCacheEnablerActive = Misc::isPluginActive('cache-enabler/cache-enabler.php');
 | ||
| 
 | ||
| 		// [IF AJAX CALL]
 | ||
| 		if ($triggeredFrom === 'ajax_call') {
 | ||
| 			if ($isCacheEnablerActive) {
 | ||
| 				echo '"Cache Enabler" plugin is active.<br />';
 | ||
| 			} else {
 | ||
| 				echo '"Cache Enabler" plugin is NOT active.<br />';
 | ||
| 				exit();
 | ||
| 			}
 | ||
| 
 | ||
| 			if (assetCleanUpClearCacheEnablerCache()) {
 | ||
| 				echo '"Cache Enabler" plugin is set to have its cache cleared.<br />';
 | ||
| 			} else {
 | ||
| 				echo '"Cache Enabler" plugin is set to not have its caching cleared via "WPACU_DO_NOT_ALSO_CLEAR_CACHE_ENABLER_CACHE" constant';
 | ||
| 				exit();
 | ||
| 			}
 | ||
| 		}
 | ||
| 		// [/IF AJAX CALL]
 | ||
| 
 | ||
| 		if ($isCacheEnablerActive && assetCleanUpClearCacheEnablerCache()) {
 | ||
| 			do_action('cache_enabler_clear_complete_cache');
 | ||
| 		}
 | ||
| 
 | ||
| 		// [IF AJAX CALL]
 | ||
| 		if ($triggeredFrom === 'ajax_call') {
 | ||
| 			if (did_action('cache_enabler_clear_complete_cache')) {
 | ||
| 				echo '"Cache Enabler" plugin had its "cache_enabler_clear_complete_cache" action triggered.<br />';
 | ||
| 			}
 | ||
| 
 | ||
| 			exit();
 | ||
| 		}
 | ||
| 		// [/IF AJAX CALL]
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param bool $includeHtmlTags
 | ||
| 	 *
 | ||
| 	 * @return array
 | ||
| 	 */
 | ||
| 	public static function getStorageStats($includeHtmlTags = true)
 | ||
| 	{
 | ||
| 		$assetCleanUpCacheDir = WP_CONTENT_DIR . self::getRelPathPluginCacheDir();
 | ||
| 
 | ||
| 		if (is_dir($assetCleanUpCacheDir)) {
 | ||
| 			$dirItems = new \RecursiveDirectoryIterator($assetCleanUpCacheDir, \RecursiveDirectoryIterator::SKIP_DOTS);
 | ||
| 			$fileDirs = $fileDirsWithCssJs = array();
 | ||
| 
 | ||
| 			// All files
 | ||
| 			$totalFiles = 0;
 | ||
| 			$totalSize = 0;
 | ||
| 
 | ||
| 			// Just .css & .js
 | ||
| 			$totalSizeAssets = 0;
 | ||
| 			$totalFilesAssets = 0;
 | ||
| 
 | ||
| 			foreach (new \RecursiveIteratorIterator($dirItems, \RecursiveIteratorIterator::SELF_FIRST, \RecursiveIteratorIterator::CATCH_GET_CHILD) as $item) {
 | ||
| 				$fileBaseName = trim(strrchr($item, '/'), '/');
 | ||
| 				$fileExt = strrchr($fileBaseName, '.');
 | ||
| 
 | ||
| 				if ($item->isFile()) {
 | ||
| 					$fileSize = $item->getSize();
 | ||
| 
 | ||
| 					$fileDir = trim(dirname($item));
 | ||
| 					$fileDirs[$fileDir][] = $fileSize;
 | ||
| 
 | ||
| 					$totalSize += $fileSize;
 | ||
| 					$totalFiles ++;
 | ||
| 
 | ||
| 					if (in_array($fileExt, array('.css', '.js'))) {
 | ||
| 						$fileDirsWithCssJs[] = $fileDir;
 | ||
| 						$totalSizeAssets += $fileSize;
 | ||
| 						$totalFilesAssets ++;
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			ksort($fileDirs, SORT_ASC);
 | ||
| 
 | ||
| 			return array(
 | ||
| 				'total_size'         => Misc::formatBytes($totalSize, 2, '', $includeHtmlTags),
 | ||
| 				'total_files'        => $totalFiles,
 | ||
| 
 | ||
| 				'total_size_assets'  => Misc::formatBytes($totalSizeAssets, 2, '', $includeHtmlTags),
 | ||
| 				'total_files_assets' => $totalFilesAssets,
 | ||
| 
 | ||
| 				'dirs_files_sizes'   => $fileDirs,
 | ||
| 				'dirs_css_js'        => array_unique($fileDirsWithCssJs)
 | ||
| 			);
 | ||
| 		}
 | ||
| 
 | ||
| 		return array();
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Prevent clear cache function in the following situations
 | ||
| 	 *
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function doNotClearCache()
 | ||
| 	{
 | ||
| 		// WooCommerce GET or AJAX call
 | ||
| 		if (isset($_GET['wc-ajax']) && $_GET['wc-ajax']) {
 | ||
| 			return true;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (defined('WC_DOING_AJAX') && WC_DOING_AJAX === true) {
 | ||
| 			return true;
 | ||
| 		}
 | ||
| 
 | ||
| 		return false;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $fileName
 | ||
| 	 *
 | ||
| 	 * @return array|string|string[]
 | ||
| 	 */
 | ||
| 	public static function filterStorageFileName($fileName)
 | ||
| 	{
 | ||
| 		$filterString = '';
 | ||
| 
 | ||
| 		if (is_404()) {
 | ||
| 			$filterString = '-404-not-found';
 | ||
| 		} elseif (is_date()) {
 | ||
| 			$filterString = '-date';
 | ||
| 		} elseif (Misc::isHomePage()) {
 | ||
| 			$filterString = '-homepage';
 | ||
| 		}
 | ||
| 
 | ||
| 		$current_user = wp_get_current_user();
 | ||
| 
 | ||
| 		if (isset($current_user->ID) && $current_user->ID > 0) {
 | ||
| 			$fileName = str_replace(
 | ||
| 				'{maybe-extra-info}',
 | ||
| 				$filterString.'-logged-in',
 | ||
| 				$fileName
 | ||
| 			);
 | ||
| 		} else {
 | ||
| 			// Just clear {maybe-extra-info}
 | ||
| 			$fileName = str_replace('{maybe-extra-info}', $filterString, $fileName);
 | ||
| 		}
 | ||
| 
 | ||
| 		return $fileName;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param string $anyCdnUrl
 | ||
| 	 *
 | ||
| 	 * @return array|string|string[]
 | ||
| 	 */
 | ||
| 	public static function filterWpContentUrl($anyCdnUrl = '')
 | ||
| 	{
 | ||
| 		$wpContentUrl = WP_CONTENT_URL;
 | ||
| 
 | ||
| 		$parseContentUrl = parse_url($wpContentUrl);
 | ||
| 		$parseBaseUrl = parse_url(site_url());
 | ||
| 
 | ||
| 		// Perhaps WPML plugin is used and the content URL is different from the current domain which might be for a different language
 | ||
| 		if ( ($parseContentUrl['host'] !== $parseBaseUrl['host']) &&
 | ||
| 		     (isset($_SERVER['HTTP_HOST'], $parseContentUrl['path']) && $_SERVER['HTTP_HOST'] !== $parseContentUrl['host']) &&
 | ||
| 			 is_dir(rtrim(ABSPATH, '/') . $parseContentUrl['path']) ) {
 | ||
| 			$wpContentUrl = str_replace($parseContentUrl['host'], $parseBaseUrl['host'], $wpContentUrl);
 | ||
| 		}
 | ||
| 
 | ||
| 		// Is the page loaded via SSL, but the site url from the database starts with 'http://'
 | ||
| 		// Then use '//' in front of CSS/JS generated via Asset CleanUp
 | ||
| 		if (Misc::isHttpsSecure() && strpos($wpContentUrl, 'http://') !== false) {
 | ||
| 			$wpContentUrl = str_replace('http://', '//', $wpContentUrl);
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($anyCdnUrl) {
 | ||
| 			$wpContentUrl = str_replace(site_url(), self::cdnToUrlFormat($anyCdnUrl, 'raw'), $wpContentUrl);
 | ||
| 		}
 | ||
| 
 | ||
| 		return $wpContentUrl;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $assetContent
 | ||
| 	 * @param $forAssetType
 | ||
| 	 *
 | ||
| 	 * @return string|string[]
 | ||
| 	 */
 | ||
| 	public static function stripSourceMap($assetContent, $forAssetType)
 | ||
| 	{
 | ||
| 		if ($forAssetType === 'css') {
 | ||
| 			$sourceMappingURLStr = '/*# sourceMappingURL=';
 | ||
| 			$sourceMappingURLStrReplaceStart = '/*';
 | ||
| 		} else {
 | ||
| 			$sourceMappingURLStr = '//# sourceMappingURL=';
 | ||
| 			$sourceMappingURLStrReplaceStart = '//';
 | ||
| 		}
 | ||
| 
 | ||
| 		$assetContent = trim($assetContent);
 | ||
| 
 | ||
| 		if (strpos($assetContent, "\n") !== false) {
 | ||
| 			$allContentLines = explode("\n", $assetContent);
 | ||
| 			$lastContentLine = end($allContentLines);
 | ||
| 
 | ||
| 			if (strpos($lastContentLine, $sourceMappingURLStr) !== false) {
 | ||
| 				return str_replace( $sourceMappingURLStr, $sourceMappingURLStrReplaceStart.'# Current File Updated by '.WPACU_PLUGIN_TITLE.' - Original Source Map: ', $assetContent );
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return $assetContent;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $for ("css" or "js")
 | ||
| 	 *
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function appendInlineCodeToCombineAssetType($for)
 | ||
| 	{
 | ||
| 		$settingsIndex = '_combine_loaded_'.$for.'_append_handle_extra';
 | ||
| 		return (Misc::isWpVersionAtLeast('5.5') &&
 | ||
| 		        isset(Main::instance()->settings[$settingsIndex]) &&
 | ||
| 	            Main::instance()->settings[$settingsIndex]);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * URLs with query strings are not loading Optimised Assets (e.g. combine CSS files into one file)
 | ||
| 	 * However, there are exceptions such as the ones below (preview, debugging purposes)
 | ||
| 	 *
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function loadOptimizedAssetsIfQueryStrings()
 | ||
| 	{
 | ||
| 		$isPreview = (isset($_GET['preview_id'], $_GET['preview_nonce'], $_GET['preview'])
 | ||
| 		              || isset($_GET['preview'])); // show the CSS/JS as combined IF the option is enabled despite the query string (for debugging purposes)
 | ||
| 
 | ||
| 		if ($isPreview) {
 | ||
| 			return true;
 | ||
| 		}
 | ||
| 
 | ||
| 		$ignoreQueryStrings = array(
 | ||
| 			'wpacu_no_css_minify',
 | ||
| 			'wpacu_no_js_minify',
 | ||
| 			'wpacu_no_css_combine',
 | ||
| 			'wpacu_no_js_combine',
 | ||
| 			'wpacu_debug',
 | ||
| 			'wpacu_preload',
 | ||
| 			'wpacu_skip_test_mode',
 | ||
| 		);
 | ||
| 
 | ||
| 		$queryStringsToIgnoreFromTheURIForOptimizingAssets = array(
 | ||
| 			'_ga',
 | ||
| 			'_ke',
 | ||
| 			'adgroupid',
 | ||
| 			'adid',
 | ||
| 			'age-verified',
 | ||
| 			'ao_noptimize',
 | ||
| 			'campaignid',
 | ||
| 			'ck_subscriber_id', // ConvertKit's query parameter
 | ||
| 			'cn-reloaded',
 | ||
| 			'dclid',
 | ||
| 			'dm_i', // dotdigital
 | ||
| 			'dm_t', // dotdigital
 | ||
| 			'ef_id',
 | ||
| 			'epik', // Pinterest
 | ||
| 			'fb_action_ids',
 | ||
| 			'fb_action_types',
 | ||
| 			'fb_source',
 | ||
| 			'fbclick',
 | ||
| 			'fbclid',
 | ||
| 			'gclid',
 | ||
| 			'gclsrc',
 | ||
| 			'mc_cid',
 | ||
| 			'mc_eid',
 | ||
| 			'mkt_tok',      // Marketo (tracking users)
 | ||
| 			'msclkid',      // Microsoft Click ID
 | ||
| 			'mtm_campaign',
 | ||
| 			'mtm_cid',
 | ||
| 			'mtm_content',
 | ||
| 			'mtm_keyword',
 | ||
| 			'mtm_medium',
 | ||
| 			'mtm_source',
 | ||
| 			'pk_campaign',  // Piwik PRO URL builder
 | ||
| 			'pk_cid',       // Piwik PRO URL builder
 | ||
| 			'pk_content',   // Piwik PRO URL builder
 | ||
| 			'pk_keyword',   // Piwik PRO URL builder
 | ||
| 			'pk_medium',    // Piwik PRO URL builder
 | ||
| 			'pk_source',    // Piwik PRO URL builder
 | ||
| 			'ref',
 | ||
| 			'SSAID',
 | ||
| 			'sscid',
 | ||
| 			'usqp',
 | ||
| 			'utm_campaign',
 | ||
| 			'utm_content',
 | ||
| 			'utm_expid',
 | ||
| 			'utm_expid',
 | ||
| 			'utm_medium',
 | ||
| 			'utm_referrer',
 | ||
| 			'utm_source',
 | ||
| 			'utm_term',
 | ||
| 		);
 | ||
| 
 | ||
| 		$isQueryString = false;
 | ||
| 
 | ||
| 		foreach (array_merge($ignoreQueryStrings, $queryStringsToIgnoreFromTheURIForOptimizingAssets) as $ignoreQueryString) {
 | ||
| 			if (isset($_GET[$ignoreQueryString])) {
 | ||
| 				$isQueryString = true;
 | ||
| 				break;
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return $isQueryString;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Possible values returned: 'db', 'disk'
 | ||
| 	 *
 | ||
| 	 * @return mixed|string
 | ||
| 	 */
 | ||
| 	public static function fetchCachedFilesFrom()
 | ||
| 	{
 | ||
| 		if (Main::instance()->settings['fetch_cached_files_details_from'] === 'db_disk') {
 | ||
| 			if ( ! isset( $GLOBALS['wpacu_from_location_inc'] ) ) {
 | ||
| 				$GLOBALS['wpacu_from_location_inc'] = 1;
 | ||
| 			}
 | ||
| 			$fromLocation = ( $GLOBALS['wpacu_from_location_inc'] % 2 ) ? 'db' : 'disk';
 | ||
| 		} else {
 | ||
| 			$fromLocation = Main::instance()->settings['fetch_cached_files_details_from'];
 | ||
| 		}
 | ||
| 
 | ||
| 		return $fromLocation;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * The following custom methods of transients work for both (MySQL) database and local storage
 | ||
| 	 * By default, the data is stored in the disk only
 | ||
| 	 *
 | ||
| 	 * @param $transient
 | ||
| 	 *
 | ||
| 	 * @return bool|mixed
 | ||
| 	 */
 | ||
| 	public static function getTransient($transient)
 | ||
| 	{
 | ||
| 		$fromLocation = self::fetchCachedFilesFrom();
 | ||
| 
 | ||
| 		$contents = '';
 | ||
| 
 | ||
| 		// Stored in the "Disk": Local record
 | ||
| 		if ($fromLocation === 'disk') {
 | ||
| 			$dirToFilename = WP_CONTENT_DIR . self::getRelPathPluginCacheDir() . '_storage/'.self::$optimizedSingleFilesDir.'/';
 | ||
| 			$assetsFile = $dirToFilename . $transient.'.json';
 | ||
| 
 | ||
| 			if (is_file($assetsFile)) {
 | ||
| 				$contents = trim(FileSystem::fileGetContents($assetsFile));
 | ||
| 
 | ||
| 				if (! $contents) {
 | ||
| 					// The file is empty or the contents could not be retrieved
 | ||
| 					// If a PHP reading error was triggered, it should be logged in the "error_log" file
 | ||
| 					return false;
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			return $contents;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Stored in the "Database"
 | ||
| 		// MySQL record: $fromLocation default 'db'
 | ||
| 		return get_transient($transient);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $transientName
 | ||
| 	 */
 | ||
| 	public static function deleteTransient($transientName)
 | ||
| 	{
 | ||
| 		$fetchFrom = Main::instance()->settings['fetch_cached_files_details_from'];
 | ||
| 
 | ||
| 		if (in_array($fetchFrom, array('db', 'db_disk'))) {
 | ||
| 			// MySQL record
 | ||
| 			delete_transient( $transientName );
 | ||
| 		}
 | ||
| 
 | ||
| 		if (in_array($fetchFrom, array('disk', 'db_disk'))) {
 | ||
| 			// File record (in case there is any)
 | ||
| 			self::clearAssetCachedData( $transientName . '.json' );
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $transient
 | ||
| 	 * @param $value
 | ||
| 	 * @param int $expiration
 | ||
| 	 */
 | ||
| 	public static function setTransient($transient, $value, $expiration = 0)
 | ||
| 	{
 | ||
| 		$fetchFrom = Main::instance()->settings['fetch_cached_files_details_from'];
 | ||
| 
 | ||
| 		if (in_array($fetchFrom, array('db', 'db_disk'))) {
 | ||
| 			// MySQL record
 | ||
| 			set_transient( $transient, $value, $expiration );
 | ||
| 		}
 | ||
| 
 | ||
| 		if (in_array($fetchFrom, array('disk', 'db_disk'))) {
 | ||
| 			// File record
 | ||
| 			self::setAssetCachedData(
 | ||
| 				$transient . '.json',
 | ||
| 				OptimizeCss::getRelPathCssCacheDir(),
 | ||
| 				$value,
 | ||
| 				'item'
 | ||
| 			);
 | ||
| 		}
 | ||
| 
 | ||
| 		}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return array
 | ||
| 	 */
 | ||
| 	public static function getAnyCdnUrls()
 | ||
| 	{
 | ||
| 		if (! Main::instance()->settings['cdn_rewrite_enable']) {
 | ||
| 			return array();
 | ||
| 		}
 | ||
| 
 | ||
| 		$cdnUrls = array();
 | ||
| 
 | ||
| 		$cdnCssUrl = trim(Main::instance()->settings['cdn_rewrite_url_css']) ?: '';
 | ||
| 		$cdnJsUrl  = trim(Main::instance()->settings['cdn_rewrite_url_js'])  ?: '';
 | ||
| 
 | ||
| 		if ($cdnCssUrl) {
 | ||
| 			$cdnUrls['css'] = $cdnCssUrl;
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($cdnJsUrl) {
 | ||
| 			$cdnUrls['js'] = $cdnJsUrl;
 | ||
| 		}
 | ||
| 
 | ||
| 		return $cdnUrls;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $cdnUrl
 | ||
| 	 * @param $getType
 | ||
| 	 *
 | ||
| 	 * @return string
 | ||
| 	 */
 | ||
| 	public static function cdnToUrlFormat($cdnUrl, $getType)
 | ||
| 	{
 | ||
| 		if (! $cdnUrl) {
 | ||
| 			return site_url();
 | ||
| 		}
 | ||
| 
 | ||
| 		$cdnUrlFinal = $cdnUrl;
 | ||
| 
 | ||
| 		// CNAME (not URL) was added
 | ||
| 		if (strpos($cdnUrl, '//') === false) {
 | ||
| 			$cdnUrlFinal = '//'.$cdnUrl;
 | ||
| 		}
 | ||
| 
 | ||
| 		// The URL will start with //
 | ||
| 		if ($getType === 'rel') {
 | ||
| 			$cdnUrlFinal = trim(str_ireplace(array('http://', 'https://'), '//', $cdnUrl));
 | ||
| 		}
 | ||
| 
 | ||
| 		return rtrim($cdnUrlFinal, '/'); // no trailing slash after the CDN URL
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * This is related to the cached CSS/JS combined files from _storage directory located within getRelPathPluginCacheDir() caching directory
 | ||
| 	 *
 | ||
| 	 * @param $postId
 | ||
| 	 * @param bool $checkTiming | if set to "true" it will check if the caching timing expires and if it did, then delete the file
 | ||
| 	 */
 | ||
| 	public static function clearJsonStorageForPost($postId, $checkTiming = false)
 | ||
| 	{
 | ||
| 		$postPermalink  = get_permalink($postId);
 | ||
| 		$requestUriPath = (string)parse_url($postPermalink, PHP_URL_PATH);
 | ||
| 
 | ||
| 		$dirToFilename = WP_CONTENT_DIR . self::getRelPathPluginCacheDir() . '/_storage/'
 | ||
| 		                 . parse_url(site_url(), PHP_URL_HOST) . '/'. $requestUriPath;
 | ||
| 
 | ||
| 		$dirToFilename = str_replace('//', '/', $dirToFilename);
 | ||
| 
 | ||
| 		$clearOlderThanInSeconds = self::$cachedAssetFileExpiresIn;
 | ||
| 
 | ||
| 		$clearFilesOlderThanXDays = Main::instance()->settings['clear_cached_files_after'];
 | ||
| 
 | ||
| 		if ($clearFilesOlderThanXDays > 0) {
 | ||
| 			$clearOlderThanInSeconds += (86400 * $clearFilesOlderThanXDays);
 | ||
| 		}
 | ||
| 
 | ||
| 		if (is_dir($dirToFilename)) {
 | ||
| 			$filesInDir = scandir($dirToFilename);
 | ||
| 
 | ||
| 			if (! empty($filesInDir)) {
 | ||
| 				foreach ($filesInDir as $wpacuFile) {
 | ||
| 					if ( $wpacuFile === '.' || $wpacuFile === '..' ) {
 | ||
| 						continue;
 | ||
| 					}
 | ||
| 
 | ||
| 					$pathToFile = $dirToFilename . $wpacuFile;
 | ||
| 
 | ||
| 					if (strrchr($wpacuFile, '.') === '.json' && is_file($pathToFile)) {
 | ||
| 						if ($checkTiming) {
 | ||
| 							$isExpired = ( strtotime( '-' . $clearOlderThanInSeconds . ' seconds' ) > filemtime($pathToFile) );
 | ||
| 
 | ||
| 							if (! $isExpired) {
 | ||
| 								// Not expired yet, do not remove it by skipping this loop
 | ||
| 								continue;
 | ||
| 							}
 | ||
| 						}
 | ||
| 
 | ||
| 						@unlink($dirToFilename . $wpacuFile);
 | ||
| 					}
 | ||
| 				}
 | ||
| 
 | ||
| 				Misc::rmDir($dirToFilename);
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $targetDir
 | ||
| 	 */
 | ||
| 	public static function rmNonEmptyJsonStorageDir($targetDir)
 | ||
| 	{
 | ||
| 		$dirFiles = glob($targetDir . '/*');
 | ||
| 
 | ||
| 		foreach ($dirFiles as $targetFile) {
 | ||
| 			if (is_dir($targetFile)) {
 | ||
| 				self::rmNonEmptyJsonStorageDir($targetFile);
 | ||
| 			} elseif(strrchr($targetFile, '.') === '.json') {
 | ||
| 				@unlink($targetFile);
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		if (strpos($targetDir, WPACU_SITE_URL_HOST) !== false) {
 | ||
| 			Misc::rmDir($targetDir);
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $assetContentSha1
 | ||
| 	 * @param $assetType
 | ||
| 	 *
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function originalContentIsAlreadyMarkedAsMinified($assetContentSha1, $assetType)
 | ||
| 	{
 | ||
| 		$optionToCheck = WPACU_PLUGIN_ID . '_global_data';
 | ||
| 		$globalKey = 'already_minified'; // HEAD or BODY
 | ||
| 
 | ||
| 		$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
 | ||
| 		$existingListJson = get_option($optionToCheck);
 | ||
| 
 | ||
| 		$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
 | ||
| 		$existingList = $existingListData['list'];
 | ||
| 
 | ||
| 		return isset( $existingList[ $assetType ]['already_minified'] ) && in_array( $assetContentSha1, $existingList[ $assetType ]['already_minified'] );
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $assetContentSha1
 | ||
| 	 * @param $assetType
 | ||
| 	 */
 | ||
| 	public static function originalContentMarkAsAlreadyMinified($assetContentSha1, $assetType)
 | ||
| 	{
 | ||
| 		$optionToUpdate = WPACU_PLUGIN_ID . '_global_data';
 | ||
| 		$globalKey = 'already_minified'; // HEAD or BODY
 | ||
| 
 | ||
| 		$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
 | ||
| 		$existingListJson = get_option($optionToUpdate);
 | ||
| 
 | ||
| 		$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
 | ||
| 		$existingList = $existingListData['list'];
 | ||
| 
 | ||
| 		// Limit it to 100 maximum entries
 | ||
| 		$totalEntries = isset($existingList[$assetType]['already_minified']) ? count($existingList[$assetType]['already_minified']) : 0;
 | ||
| 
 | ||
| 		if ($totalEntries === 100) {
 | ||
| 			return; // stop here
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($totalEntries < 1) { // declare the array if no entries are there
 | ||
| 			$existingList[$assetType]['already_minified'] = array();
 | ||
| 		} else if ($totalEntries < 100) { // append to the array
 | ||
| 			$existingList[$assetType]['already_minified'][] = $assetContentSha1;
 | ||
| 		} else if ($totalEntries > 100) { // already passed the number, trim the list
 | ||
| 			$existingList[$assetType]['already_minified'] = array_slice($existingList[$assetType]['already_minified'], 0, 100);
 | ||
| 		}
 | ||
| 
 | ||
| 		update_option($optionToUpdate, wp_json_encode(Misc::filterList($existingList)));
 | ||
| 	}
 | ||
| 
 | ||
| 	// [START] For debugging purposes
 | ||
| 	/**
 | ||
| 	 * @return array
 | ||
| 	 */
 | ||
| 	public static function getAlreadyMarkedAsMinified()
 | ||
| 	{
 | ||
| 		$alreadyMinified = array();
 | ||
| 
 | ||
| 		$optionToUpdate = WPACU_PLUGIN_ID . '_global_data';
 | ||
| 		$globalKey = 'already_minified';
 | ||
| 
 | ||
| 		$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
 | ||
| 		$existingListJson = get_option($optionToUpdate);
 | ||
| 
 | ||
| 		$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
 | ||
| 		$existingList = $existingListData['list'];
 | ||
| 
 | ||
| 		if (isset($existingList['styles']['already_minified'])) {
 | ||
| 			$alreadyMinified['styles'] = $existingList['styles']['already_minified'];
 | ||
| 		}
 | ||
| 
 | ||
| 		if (isset($existingList['scripts']['already_minified'])) {
 | ||
| 			$alreadyMinified['scripts'] = $existingList['scripts']['already_minified'];
 | ||
| 		}
 | ||
| 
 | ||
| 		return $alreadyMinified;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 */
 | ||
| 	public static function removeAlreadyMarkedAsMinified()
 | ||
| 	{
 | ||
| 		$optionToUpdate = WPACU_PLUGIN_ID . '_global_data';
 | ||
| 		$globalKey = 'already_minified';
 | ||
| 
 | ||
| 		$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
 | ||
| 		$existingListJson = get_option($optionToUpdate);
 | ||
| 
 | ||
| 		$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
 | ||
| 		$existingList = $existingListData['list'];
 | ||
| 
 | ||
| 		if (isset($existingList['styles']['already_minified'])) {
 | ||
| 			unset($existingList['styles']['already_minified']);
 | ||
| 		}
 | ||
| 
 | ||
| 		if (isset($existingList['scripts']['already_minified'])) {
 | ||
| 			unset($existingList['scripts']['already_minified']);
 | ||
| 		}
 | ||
| 
 | ||
| 		update_option($optionToUpdate, wp_json_encode(Misc::filterList($existingList)));
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 */
 | ||
| 	public static function limitAlreadyMarkedAsMinified()
 | ||
| 	{
 | ||
| 		$optionToUpdate = WPACU_PLUGIN_ID . '_global_data';
 | ||
| 		$globalKey = 'already_minified';
 | ||
| 
 | ||
| 		$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
 | ||
| 		$existingListJson = get_option($optionToUpdate);
 | ||
| 
 | ||
| 		$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
 | ||
| 		$existingList = $existingListData['list'];
 | ||
| 
 | ||
| 		$maxEntries = 100;
 | ||
| 
 | ||
| 		// Limit it to $maxEntries maximum entries
 | ||
| 		foreach (array('styles', 'scripts') as $assetType) {
 | ||
| 			$totalEntries = isset( $existingList[ $assetType ]['already_minified'] ) ? count( $existingList[ $assetType ]['already_minified'] ) : 0;
 | ||
| 			if ($totalEntries > $maxEntries) {
 | ||
| 				$existingList[ $assetType ]['already_minified'] = array_slice( $existingList[ $assetType ]['already_minified'], 0, $maxEntries );
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		update_option($optionToUpdate, wp_json_encode(Misc::filterList($existingList)));
 | ||
| 	}
 | ||
| 	// [END] For debugging purposes
 | ||
| 
 | ||
| 	}
 |