1537 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1537 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| namespace WpAssetCleanUp\OptimiseAssets;
 | ||
| 
 | ||
| use WpAssetCleanUp\ObjectCache;
 | ||
| use WpAssetCleanUp\Plugin;
 | ||
| use WpAssetCleanUp\Preloads;
 | ||
| use WpAssetCleanUp\FileSystem;
 | ||
| use WpAssetCleanUp\CleanUp;
 | ||
| use WpAssetCleanUp\Main;
 | ||
| use WpAssetCleanUp\MetaBoxes;
 | ||
| use WpAssetCleanUp\Misc;
 | ||
| 
 | ||
| /**
 | ||
|  * Class OptimizeCss
 | ||
|  * @package WpAssetCleanUp
 | ||
|  */
 | ||
| class OptimizeCss
 | ||
| {
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 */
 | ||
| 	const MOVE_NOSCRIPT_TO_BODY_FOR_CERTAIN_LINK_TAGS = '<span style="display: none;" data-name=wpacu-delimiter data-content="ASSET CLEANUP NOSCRIPT FOR ASYNC PRELOADS"></span>';
 | ||
| 
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 */
 | ||
| 	public function init()
 | ||
| 	{
 | ||
| 		add_action('init', array($this, 'triggersAfterInit'));
 | ||
| 		add_action('wp_footer', static function() {
 | ||
| 			if ( Plugin::preventAnyFrontendOptimization() || Main::isTestModeActive() ) { return; }
 | ||
| 
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer( 'prepare_optimize_files_css' ); /* [/wpacu_timing] */
 | ||
| 			self::prepareOptimizeList();
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer( 'prepare_optimize_files_css', 'end' ); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 			echo self::MOVE_NOSCRIPT_TO_BODY_FOR_CERTAIN_LINK_TAGS;
 | ||
| 		}, PHP_INT_MAX);
 | ||
| 
 | ||
| 		add_filter('wpacu_html_source_after_optimization', static function($htmlSource) {
 | ||
| 			// Are any the marks still there & weren't replaced? Strip them to have a clean HTML output!
 | ||
| 			return str_replace(self::MOVE_NOSCRIPT_TO_BODY_FOR_CERTAIN_LINK_TAGS, '', $htmlSource);
 | ||
| 		});
 | ||
| 
 | ||
| 		add_filter('wpacu_add_noscript_certain_link_tags', array($this, 'appendNoScriptCertainLinkTags'));
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 */
 | ||
| 	public function triggersAfterInit()
 | ||
| 	{
 | ||
| 		if (self::isInlineCssEnabled()) {
 | ||
| 			$allPatterns = self::getAllInlineChosenPatterns();
 | ||
| 
 | ||
| 			if (! empty($allPatterns)) {
 | ||
| 				// Make "Inline CSS Files" compatible with "Optimize CSS Delivery" from WP Rocket
 | ||
| 				add_filter('rocket_async_css_regex_pattern', static function($regex) {
 | ||
| 					return '/(?=<link(?!.*wpacu-to-be-inlined.*)[^>]*\s(rel\s*=\s*[\'"]stylesheet["\']))<link(?!.*wpacu-to-be-inlined.*)[^>]*\shref\s*=\s*[\'"]([^\'"]+)[\'"](.*)>/iU';
 | ||
| 				});
 | ||
| 
 | ||
| 				add_filter('style_loader_tag', static function($styleTag) use ($allPatterns) {
 | ||
| 					foreach ($allPatterns as $patternToCheck) {
 | ||
| 						preg_match_all( '#<link[^>]*stylesheet[^>]*('.$patternToCheck.').*(>)#Usmi', $styleTag, $matchesSourcesFromTags, PREG_SET_ORDER );
 | ||
| 
 | ||
| 						if ( ! empty( $matchesSourcesFromTags ) ) {
 | ||
| 							return str_replace( '<link ', '<link wpacu-to-be-inlined=\'1\' ', $styleTag );
 | ||
| 						}
 | ||
| 					}
 | ||
| 
 | ||
| 					return $styleTag;
 | ||
| 				}, 10, 1);
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return array
 | ||
| 	 */
 | ||
| 	public static function getAllInlineChosenPatterns()
 | ||
| 	{
 | ||
| 		$inlineCssFilesPatterns = trim(Main::instance()->settings['inline_css_files_list']);
 | ||
| 
 | ||
| 		$allPatterns = array();
 | ||
| 
 | ||
| 		if (strpos($inlineCssFilesPatterns, "\n")) {
 | ||
| 			// Multiple values (one per line)
 | ||
| 			foreach (explode("\n", $inlineCssFilesPatterns) as $inlinePattern) {
 | ||
| 				$allPatterns[] = trim($inlinePattern);
 | ||
| 			}
 | ||
| 		} else {
 | ||
| 			// Only one value?
 | ||
| 			$allPatterns[] = trim($inlineCssFilesPatterns);
 | ||
| 		}
 | ||
| 
 | ||
| 		// Strip any empty values
 | ||
| 		return array_filter($allPatterns);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 */
 | ||
| 	public static function prepareOptimizeList()
 | ||
| 	{
 | ||
| 		if ( ! self::isWorthCheckingForOptimization() || Plugin::preventAnyFrontendOptimization() ) {
 | ||
| 			return;
 | ||
| 		}
 | ||
| 
 | ||
| 		global $wp_styles;
 | ||
| 
 | ||
| 		$allStylesHandles = ObjectCache::wpacu_cache_get('wpacu_all_styles_handles');
 | ||
| 		if (empty($allStylesHandles)) {
 | ||
| 			return;
 | ||
| 		}
 | ||
| 
 | ||
| 		// [Start] Collect for caching
 | ||
| 		$wpStylesDone       = isset($wp_styles->done)       && is_array($wp_styles->done)       ? $wp_styles->done       : array();
 | ||
| 		$wpStylesRegistered = isset($wp_styles->registered) && is_array($wp_styles->registered) ? $wp_styles->registered : array();
 | ||
| 
 | ||
| 		// Collect all enqueued clean (no query strings) HREFs to later compare them against any hardcoded CSS
 | ||
| 		$allEnqueuedCleanLinkHrefs = array();
 | ||
| 
 | ||
| 		if (! empty($wpStylesDone) && ! empty($wpStylesRegistered)) {
 | ||
| 			foreach ( $wpStylesDone as $index => $styleHandle ) {
 | ||
| 				if ( isset( Main::instance()->wpAllStyles['registered'][ $styleHandle ]->src ) && ( $src = Main::instance()->wpAllStyles['registered'][ $styleHandle ]->src ) ) {
 | ||
| 					$localAssetPath = OptimizeCommon::getLocalAssetPath( $src, 'css' );
 | ||
| 
 | ||
| 					if ( ! $localAssetPath || ! is_file( $localAssetPath ) ) {
 | ||
| 						continue; // not a local file
 | ||
| 					}
 | ||
| 
 | ||
| 					ob_start();
 | ||
| 					$wp_styles->do_item( $styleHandle );
 | ||
| 					$linkSourceTag = trim( ob_get_clean() );
 | ||
| 
 | ||
| 					// Check if the CSS has any 'data-wpacu-skip' attribute; if it does, do not alter it
 | ||
| 					if ( preg_match( '#data-wpacu-skip([=>/ ])#i', $linkSourceTag ) ) {
 | ||
| 						unset( $wpStylesDone[ $index ] );
 | ||
| 						continue;
 | ||
| 					}
 | ||
| 
 | ||
| 					$cleanLinkHrefFromTagArray = OptimizeCommon::getLocalCleanSourceFromTag( $linkSourceTag );
 | ||
| 
 | ||
| 					if ( isset( $cleanLinkHrefFromTagArray['source'] ) && $cleanLinkHrefFromTagArray['source'] ) {
 | ||
| 						$allEnqueuedCleanLinkHrefs[] = $cleanLinkHrefFromTagArray['source'];
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		$cssOptimizeList = array();
 | ||
| 
 | ||
| 		if (! empty($wpStylesDone) && ! empty($wpStylesRegistered)) {
 | ||
| 			$isMinifyCssFilesEnabled = MinifyCss::isMinifyCssEnabled() && in_array(Main::instance()->settings['minify_loaded_css_for'], array('href', 'all', ''));
 | ||
| 
 | ||
| 			foreach ( $wpStylesDone as $handle ) {
 | ||
| 				if ( ! isset( $wpStylesRegistered[ $handle ]->src ) ) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				$value = $wpStylesRegistered[ $handle ];
 | ||
| 
 | ||
| 				$localAssetPath = OptimizeCommon::getLocalAssetPath( $value->src, 'css' );
 | ||
| 				if ( ! $localAssetPath || ! is_file( $localAssetPath ) ) {
 | ||
| 					continue; // not a local file
 | ||
| 				}
 | ||
| 
 | ||
| 				$optimizeValues = self::maybeOptimizeIt(
 | ||
| 					$value,
 | ||
| 					array( 'local_asset_path' => $localAssetPath, 'is_minify_css_enabled' => $isMinifyCssFilesEnabled )
 | ||
| 				);
 | ||
| 
 | ||
| 				ObjectCache::wpacu_cache_set( 'wpacu_maybe_optimize_it_css_' . $handle, $optimizeValues );
 | ||
| 
 | ||
| 				if ( ! empty( $optimizeValues ) ) {
 | ||
| 					$cssOptimizeList[] = $optimizeValues;
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		if (empty($cssOptimizeList)) {
 | ||
| 			return;
 | ||
| 		}
 | ||
| 
 | ||
| 		ObjectCache::wpacu_cache_add('wpacu_css_enqueued_hrefs', $allEnqueuedCleanLinkHrefs);
 | ||
| 		ObjectCache::wpacu_cache_add('wpacu_css_optimize_list', $cssOptimizeList);
 | ||
| 		// [End] Collect for caching
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $value
 | ||
| 	 * @param array $fileAlreadyChecked
 | ||
| 	 *
 | ||
| 	 * @return mixed
 | ||
| 	 */
 | ||
| 	public static function maybeOptimizeIt($value, $fileAlreadyChecked = array())
 | ||
| 	{
 | ||
| 		if ($optimizeValues = ObjectCache::wpacu_cache_get('wpacu_maybe_optimize_it_css_'.$value->handle)) {
 | ||
| 			return $optimizeValues;
 | ||
| 		}
 | ||
| 
 | ||
| 		global $wp_version;
 | ||
| 
 | ||
| 		$src = isset($value->src) ? $value->src : false;
 | ||
| 
 | ||
| 		if (! $src) {
 | ||
| 			return array();
 | ||
| 		}
 | ||
| 
 | ||
| 		$doFileMinify = true;
 | ||
| 
 | ||
| 		$isMinifyCssFilesEnabled = (isset($fileAlreadyChecked['is_minify_css_enabled']) && $fileAlreadyChecked['is_minify_css_enabled'])
 | ||
| 			? $fileAlreadyChecked['is_minify_css_enabled']
 | ||
| 			: MinifyCss::isMinifyCssEnabled() && in_array(Main::instance()->settings['minify_loaded_css_for'], array('href', 'all', ''));
 | ||
| 
 | ||
| 		if ( ! $isMinifyCssFilesEnabled || MinifyCss::skipMinify($src, $value->handle) ) {
 | ||
| 			$doFileMinify = false;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Default (it will be later replaced with the last time the file was modified, which is more accurate)
 | ||
| 		$dbVer = (isset($value->ver) && $value->ver) ? $value->ver : $wp_version;
 | ||
| 
 | ||
| 		$isCssFile = false;
 | ||
| 
 | ||
| 		// Already checked? Do not reuse OptimizeCommon::getLocalAssetPath() and is_file()
 | ||
| 		if (isset($fileAlreadyChecked['local_asset_path']) && $fileAlreadyChecked['local_asset_path']) {
 | ||
| 			$localAssetPath = $fileAlreadyChecked['local_asset_path'];
 | ||
| 			$checkCond = $localAssetPath;
 | ||
| 		} else {
 | ||
| 			$localAssetPath = OptimizeCommon::getLocalAssetPath( $src, 'css' );
 | ||
| 			$checkCond = $localAssetPath && is_file($localAssetPath);
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($checkCond) {
 | ||
| 			if ($fileMTime = @filemtime($localAssetPath)) {
 | ||
| 				$dbVer = $fileMTime;
 | ||
| 			}
 | ||
| 			$isCssFile = true;
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($isCssFile) {
 | ||
| 			// This is the safest one as handle names for specific static can change on very page load
 | ||
| 			// as some developers have a habit of adding the UNIX time or other random string to a handle (e.g. for debugging)
 | ||
| 			$uniqueAssetStr = md5 ( str_replace(Misc::getWpRootDirPath(), '', $localAssetPath) );
 | ||
| 		} else {
 | ||
| 			$uniqueAssetStr = md5( $value->handle );
 | ||
| 		}
 | ||
| 
 | ||
| 		$transientName = 'wpacu_css_optimize_'.$uniqueAssetStr;
 | ||
| 
 | ||
| 		$skipCache = false;
 | ||
| 
 | ||
| 		if (isset($_GET['wpacu_no_cache']) || (defined('WPACU_NO_CACHE') && WPACU_NO_CACHE === true)) {
 | ||
| 			$skipCache = true;
 | ||
| 		}
 | ||
| 
 | ||
| 	    if (! $skipCache) {
 | ||
| 			$savedValues = OptimizeCommon::getTransient($transientName);
 | ||
| 
 | ||
| 			if ( $savedValues ) {
 | ||
| 				$savedValuesArray = json_decode( $savedValues, ARRAY_A );
 | ||
| 
 | ||
| 				if ( $savedValuesArray['ver'] !== $dbVer ) {
 | ||
| 					// New File Version? Delete transient as it will be re-created with the new version
 | ||
| 					OptimizeCommon::deleteTransient($transientName);
 | ||
| 				} else {
 | ||
| 					$localPathToCssOptimized = str_replace( '//', '/', Misc::getWpRootDirPath() . $savedValuesArray['optimize_uri'] );
 | ||
| 
 | ||
| 					// Read the file from its caching (that makes the processing faster)
 | ||
| 					if ( isset( $savedValuesArray['source_uri'] ) && is_file( $localPathToCssOptimized ) ) {
 | ||
| 						if (Main::instance()->settings['fetch_cached_files_details_from'] === 'db_disk') {
 | ||
| 							$GLOBALS['wpacu_from_location_inc']++;
 | ||
| 						}
 | ||
| 						return array(
 | ||
| 							$savedValuesArray['source_uri'],
 | ||
| 							$savedValuesArray['optimize_uri'],
 | ||
| 							$value->src,
 | ||
| 							$value->handle
 | ||
| 						);
 | ||
| 					}
 | ||
| 
 | ||
| 					// If nothing valid gets returned above, make sure the transient gets deleted as it's re-added later on
 | ||
| 					OptimizeCommon::deleteTransient($transientName);
 | ||
| 				}
 | ||
| 			}
 | ||
| 	    }
 | ||
| 
 | ||
| 		// Check if it starts without "/" or a protocol; e.g. "wp-content/theme/style.css"
 | ||
| 		if (strpos($src, '/') !== 0 &&
 | ||
| 		    strpos($src, '//') !== 0 &&
 | ||
| 		    stripos($src, 'http://') !== 0 &&
 | ||
| 		    stripos($src, 'https://') !== 0
 | ||
| 		) {
 | ||
| 			$src = '/'.$src; // append the forward slash to be processed as relative later on
 | ||
| 		}
 | ||
| 
 | ||
| 		// Starts with '/', but not with '//'
 | ||
| 		if (strpos($src, '/') === 0 && strpos($src, '//') !== 0) {
 | ||
| 			$src = site_url() . $src;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (Main::instance()->settings['cache_dynamic_loaded_css'] &&
 | ||
| 		    $value->handle === 'sccss_style' &&
 | ||
| 		    in_array('simple-custom-css/simple-custom-css.php', Misc::getActivePlugins())
 | ||
| 		) {
 | ||
| 			$pathToAssetDir = '';
 | ||
| 			$sourceBeforeOptimization = $value->src;
 | ||
| 
 | ||
| 			if (! ($cssContent = DynamicLoadedAssets::getAssetContentFrom('simple-custom-css', $value))) {
 | ||
| 				return array();
 | ||
| 			}
 | ||
| 		} elseif (Main::instance()->settings['cache_dynamic_loaded_css'] &&
 | ||
| 		          ((strpos($src, '/?') !== false) || (strpos($src, rtrim(site_url(),'/').'?') !== false) || (strpos($src, '.php?') !== false) || Misc::endsWith($src, '.php')) &&
 | ||
| 		          (strpos($src, rtrim(site_url(), '/')) !== false)
 | ||
| 		) {
 | ||
| 			$pathToAssetDir = '';
 | ||
| 			$sourceBeforeOptimization = str_replace('&', '&', $value->src);
 | ||
| 
 | ||
| 			if (! ($cssContent = DynamicLoadedAssets::getAssetContentFrom('dynamic', $value))) {
 | ||
| 				return array();
 | ||
| 			}
 | ||
| 		} else {
 | ||
| 			if (! $isCssFile) {
 | ||
| 				return array();
 | ||
| 			}
 | ||
| 
 | ||
| 			/*
 | ||
| 			 * This is a local .CSS file
 | ||
| 			 */
 | ||
| 			$pathToAssetDir = OptimizeCommon::getPathToAssetDir($src);
 | ||
| 
 | ||
| 			$cssContent = FileSystem::fileGetContents($localAssetPath, 'combine_css_imports');
 | ||
| 
 | ||
| 			$sourceBeforeOptimization = str_replace(Misc::getWpRootDirPath(), '/', $localAssetPath);
 | ||
| 		}
 | ||
| 
 | ||
| 		$cssContent = trim($cssContent);
 | ||
| 
 | ||
| 		/*
 | ||
| 		 * [START] CSS Content Optimization
 | ||
| 		*/
 | ||
| 		// If there are no changes from this point, do not optimize (keep the file where it is)
 | ||
| 		$cssContentBefore = $cssContent;
 | ||
| 
 | ||
| 		if ($cssContent) { // only proceed with extra alterations if there is some content there (save resources)
 | ||
| 			if ( Main::instance()->settings['google_fonts_display'] ) {
 | ||
| 				// Any "font-display" enabled in "Settings" - "Google Fonts"?
 | ||
| 				$cssContent = FontsGoogle::alterGoogleFontUrlFromCssContent( $cssContent );
 | ||
| 			}
 | ||
| 
 | ||
| 			// Move any @imports to top; This also strips any @imports to Google Fonts if the option is chosen
 | ||
| 			$cssContent = self::importsUpdate( $cssContent );
 | ||
| 		}
 | ||
| 
 | ||
| 		// If it stays like this, it means there is content there, even if only comments
 | ||
| 		$cssContentBecomesEmptyAfterMin = false;
 | ||
| 
 | ||
| 		if ($doFileMinify && $cssContent) { // only bother to minify it if it has any content, save resources
 | ||
| 			// Minify this file?
 | ||
| 			$cssContentBeforeMin = trim($cssContent);
 | ||
| 			$cssContentAfterMin  = MinifyCss::applyMinification($cssContent);
 | ||
| 
 | ||
| 			$cssContent = $cssContentAfterMin;
 | ||
| 
 | ||
| 			if ($cssContentBeforeMin && $cssContentAfterMin === '') {
 | ||
| 				// It had content, but became empty after minification, most likely it had only comments (e.g. a default child theme's style)
 | ||
| 				$cssContentBecomesEmptyAfterMin = true;
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($cssContentBecomesEmptyAfterMin || $cssContent === '') {
 | ||
| 			$cssContent = '/**/';
 | ||
| 		} else {
 | ||
| 			if ( Main::instance()->settings['google_fonts_remove'] ) {
 | ||
| 				$cssContent = FontsGoogleRemove::cleanFontFaceReferences( $cssContent );
 | ||
| 			}
 | ||
| 
 | ||
| 			// No changes were made, thus, there's no point in changing the original file location
 | ||
| 			if ( $isCssFile && ! $cssContentBecomesEmptyAfterMin && trim( $cssContentBefore ) === trim( $cssContent ) ) {
 | ||
| 				// There's no point in changing the original CSS (static) file location
 | ||
| 				return false;
 | ||
| 			}
 | ||
| 
 | ||
| 			// Continue, as changes are to be made
 | ||
| 			// Does it have a source map? Strip it
 | ||
| 			if (strpos($cssContent, '/*# sourceMappingURL=') !== false) {
 | ||
| 				$cssContent = OptimizeCommon::stripSourceMap($cssContent, 'css');
 | ||
| 			}
 | ||
| 
 | ||
| 			$cssContent = self::maybeFixCssContent( $cssContent, $pathToAssetDir . '/' ); // Path
 | ||
| 		}
 | ||
| 		/*
 | ||
|          * [END] CSS Content Optimization
 | ||
| 		*/
 | ||
| 
 | ||
| 		// Relative path to the new file
 | ||
| 		// Save it to /wp-content/cache/css/{OptimizeCommon::$optimizedSingleFilesDir}/
 | ||
| 		/*
 | ||
| 		if ($fileVer !== $wp_version) {
 | ||
| 			if (is_array($fileVer)) {
 | ||
| 				// Convert to string if it's an array (rare cases)
 | ||
| 				$fileVer = implode('-', $fileVer);
 | ||
| 			}
 | ||
| 			$fileVer = trim(str_replace(' ', '_', preg_replace('/\s+/', ' ', $fileVer)));
 | ||
| 			$fileVer = (strlen($fileVer) > 50) ? substr(md5($fileVer), 0, 20) : $fileVer; // don't end up with too long filenames
 | ||
| 		}
 | ||
| 		*/
 | ||
| 		$fileVer = sha1($cssContent);
 | ||
| 
 | ||
| 		$uniqueCachedAssetName = OptimizeCommon::generateUniqueNameForCachedAsset($isCssFile, $localAssetPath, $value->handle, $fileVer);
 | ||
| 
 | ||
| 		$newFilePathUri  = self::getRelPathCssCacheDir() . OptimizeCommon::$optimizedSingleFilesDir . '/' . $uniqueCachedAssetName;
 | ||
| 		$newFilePathUri .= '.css';
 | ||
| 
 | ||
| 		if ($cssContent === '') {
 | ||
| 			$cssContent = '/**/';
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($cssContent === '/**/') {
 | ||
| 			// Leave a signature that the file is empty, thus it would be faster to take further actions upon it later on, saving resources)
 | ||
| 			$newFilePathUri = str_replace('.css', '-wpacu-empty-file.css', $newFilePathUri);
 | ||
| 		}
 | ||
| 
 | ||
| 		$newLocalPath    = WP_CONTENT_DIR . $newFilePathUri; // Ful Local path
 | ||
| 		$newLocalPathUrl = WP_CONTENT_URL . $newFilePathUri; // Full URL path
 | ||
| 
 | ||
| 		if ($cssContent && $cssContent !== '/**/' && apply_filters('wpacu_print_info_comments_in_cached_assets', true)) {
 | ||
| 			$cssContent = '/*!' . $sourceBeforeOptimization . '*/' . $cssContent;
 | ||
| 		}
 | ||
| 
 | ||
| 		$saveFile = FileSystem::filePutContents($newLocalPath, $cssContent);
 | ||
| 
 | ||
| 		if (! $saveFile && ! $cssContent) {
 | ||
| 			// Fallback to the original CSS if the optimized version can't be created or updated
 | ||
| 			return array();
 | ||
| 		}
 | ||
| 
 | ||
| 		$saveValues = array(
 | ||
| 			'source_uri'   => OptimizeCommon::getSourceRelPath($src),
 | ||
| 			'optimize_uri' => OptimizeCommon::getSourceRelPath($newLocalPathUrl),
 | ||
| 			'ver'          => $dbVer
 | ||
| 		);
 | ||
| 
 | ||
| 		// Re-add transient
 | ||
| 		OptimizeCommon::setTransient($transientName, wp_json_encode($saveValues));
 | ||
| 
 | ||
| 		return array(
 | ||
| 			OptimizeCommon::getSourceRelPath($src), // Original SRC (Relative path)
 | ||
| 			OptimizeCommon::getSourceRelPath($newLocalPathUrl), // New SRC (Relative path)
 | ||
| 			$value->src, // SRC (as it is)
 | ||
| 			$value->handle
 | ||
| 		);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $htmlSource
 | ||
| 	 *
 | ||
| 	 * @return mixed|void
 | ||
| 	 */
 | ||
| 	public static function alterHtmlSource($htmlSource)
 | ||
| 	{
 | ||
| 		// There has to be at least one "<link" or "<style", otherwise, it could be a feed request or something similar (not page, post, homepage etc.)
 | ||
| 		if ( (stripos($htmlSource, '<link') === false && stripos($htmlSource, '<style') === false) || isset($_GET['wpacu_no_optimize_css']) ) {
 | ||
| 			return $htmlSource;
 | ||
| 		}
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_optimize_css'); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		// Are there any assets unloaded where their "children" are ignored?
 | ||
| 		// Since they weren't dequeued the WP way (to avoid unloading the "children"), they will be stripped here
 | ||
| 		if (! Main::instance()->preventAssetsSettings()) {
 | ||
| 			/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_unload_ignore_deps_css'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
 | ||
| 			$htmlSource = self::ignoreDependencyRuleAndKeepChildrenLoaded($htmlSource);
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
 | ||
| 		}
 | ||
| 
 | ||
| 		$htmlSource = self::stripAnyReferencesForUnloadedStyles($htmlSource);
 | ||
| 
 | ||
| 		if (self::isWorthCheckingForOptimization()) {
 | ||
| 			/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_original_to_optimized_css'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
 | ||
| 			// 'wpacu_css_optimize_list' caching list is also checked; if it's empty, no optimization is made
 | ||
| 			$htmlSource = self::updateHtmlSourceOriginalToOptimizedCss($htmlSource);
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
 | ||
| 		}
 | ||
| 
 | ||
| 		if (! Main::instance()->preventAssetsSettings()) {
 | ||
| 			/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_preload_css'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
 | ||
| 			$htmlSource = Preloads::instance()->doChanges($htmlSource);
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
 | ||
| 		}
 | ||
| 
 | ||
| 		if (self::isInlineCssEnabled()) {
 | ||
| 			/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_inline_css'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
 | ||
| 			$htmlSource = self::doInline($htmlSource);
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
 | ||
| 		}
 | ||
| 
 | ||
| 		$proceedWithCombineOnThisPage = true;
 | ||
| 
 | ||
| 		$isSingularPage = defined('WPACU_CURRENT_PAGE_ID') && WPACU_CURRENT_PAGE_ID > 0 && is_singular();
 | ||
| 
 | ||
| 		// If "Do not combine CSS on this page" is checked in "Asset CleanUp: Options" side meta box
 | ||
| 		// Works for posts, pages and custom post types
 | ||
| 		if ($isSingularPage || Misc::isHomePage()) {
 | ||
| 			if ($isSingularPage) {
 | ||
| 				$pageOptions = MetaBoxes::getPageOptions( WPACU_CURRENT_PAGE_ID ); // Singular page
 | ||
| 			} else {
 | ||
| 				$pageOptions = MetaBoxes::getPageOptions(0, 'front_page'); // Home page
 | ||
| 			}
 | ||
| 
 | ||
| 			// 'no_css_optimize' refers to avoid the combination of CSS files
 | ||
| 			if ( (isset( $pageOptions['no_css_optimize'] )    && $pageOptions['no_css_optimize'])
 | ||
| 			  || (isset( $pageOptions['no_assets_settings'] ) && $pageOptions['no_assets_settings']) ) {
 | ||
| 				$proceedWithCombineOnThisPage = false;
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($proceedWithCombineOnThisPage) {
 | ||
| 			/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_combine_css'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
 | ||
| 			$htmlSource = CombineCss::doCombine($htmlSource);
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
 | ||
| 		}
 | ||
| 
 | ||
| 		if (self::isWorthCheckingForOptimization() && ! Main::instance()->preventAssetsSettings() && (MinifyCss::isMinifyCssEnabled() && in_array(Main::instance()->settings['minify_loaded_css_for'], array('inline', 'all')))) {
 | ||
| 			/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_minify_inline_style_tags'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
 | ||
| 			$htmlSource = MinifyCss::minifyInlineStyleTags($htmlSource);
 | ||
| 			/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
 | ||
| 		}
 | ||
| 
 | ||
| 		// Final cleanups
 | ||
| 		$htmlSource = preg_replace('#<link(\s+|)data-wpacu-link-rel-href-before=(["\'])' . '(.*)' . '(\1)#Usmi', '<link ', $htmlSource);
 | ||
| 		//$htmlSource = preg_replace('#<link(.*)data-wpacu-style-handle=\'(.*)\'#Umi', '<link \\1', $htmlSource);
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_google_fonts_optimization_removal'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
 | ||
| 		// Alter HTML Source for Google Fonts Optimization / Removal
 | ||
| 		$htmlSource = FontsGoogle::alterHtmlSource($htmlSource);
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		// NOSCRIPT fallback: Applies for Google Fonts (async) (Lite and Pro) /  Preloads (Async in Pro version) / Critical CSS (as LINK "stylesheet" tags will be async preloaded)
 | ||
| 		/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_add_async_preloads_noscript'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
 | ||
| 		$htmlSource = apply_filters('wpacu_add_noscript_certain_link_tags', $htmlSource);
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_optimize_css', 'end'); /* [/wpacu_timing] */
 | ||
| 
 | ||
| 		return $htmlSource;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return string
 | ||
| 	 */
 | ||
| 	public static function getRelPathCssCacheDir()
 | ||
| 	{
 | ||
| 		return OptimizeCommon::getRelPathPluginCacheDir().'css/'; // keep trailing slash at the end
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $firstLinkHref
 | ||
| 	 * @param $htmlSource
 | ||
| 	 *
 | ||
| 	 * @return string
 | ||
| 	 */
 | ||
| 	public static function getFirstLinkTag($firstLinkHref, $htmlSource)
 | ||
| 	{
 | ||
| 		preg_match_all('#<link[^>]*stylesheet[^>]*(>)#Umi', $htmlSource, $matches);
 | ||
| 		foreach ($matches[0] as $matchTag) {
 | ||
| 			if (strpos($matchTag, $firstLinkHref) !== false) {
 | ||
| 				return trim($matchTag);
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return '';
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 *
 | ||
| 	 * @param $cssContent
 | ||
| 	 * @param $appendBefore
 | ||
| 	 * @param $fix
 | ||
| 	 *
 | ||
| 	 * @return mixed
 | ||
| 	 */
 | ||
| 	public static function maybeFixCssContent($cssContent, $appendBefore, $fix = 'path')
 | ||
| 	{
 | ||
| 		// Updates (background | font etc.) URLs to the right path and others
 | ||
| 		if ($fix === 'path') {
 | ||
| 			// Clear any extra spaces between @import and the single/double quotes
 | ||
| 			$cssContent = preg_replace('/@import(\s+|)([\'"])/i', '@import \\2', $cssContent);
 | ||
| 
 | ||
| 			$cssContentPathReps = array(
 | ||
| 				// @import with url(), background-image etc.
 | ||
| 				'url("../' => 'url("'.$appendBefore.'../',
 | ||
| 				"url('../" => "url('".$appendBefore.'../',
 | ||
| 				'url(../'  => 'url('.$appendBefore.'../',
 | ||
| 
 | ||
| 				'url("./'  => 'url("'.$appendBefore.'./',
 | ||
| 				"url('./"  => "url('".$appendBefore.'./',
 | ||
| 				'url(./'   => 'url('.$appendBefore.'./',
 | ||
| 
 | ||
| 				// @import without URL
 | ||
| 				'@import "../' => '@import "'.$appendBefore.'../',
 | ||
| 				"@import '../" => "@import '".$appendBefore.'../',
 | ||
| 
 | ||
| 				'@import "./'  => '@import "'.$appendBefore.'./',
 | ||
| 				"@import './"  => "@import '".$appendBefore.'./'
 | ||
| 			);
 | ||
| 
 | ||
| 			$cssContent = str_replace(array_keys($cssContentPathReps), array_values($cssContentPathReps), $cssContent);
 | ||
| 
 | ||
| 			// Rare cases
 | ||
| 			$cssContent = preg_replace('/url\((\s+)http/i', 'url(http', $cssContent);
 | ||
| 
 | ||
| 			// Avoid Background URLs starting with "#", "data", "http" or "https" as they do not need to have a path updated
 | ||
| 			preg_match_all('/url\((?![\'"]?(?:#|data|http|https):)[\'"]?([^\'")]*)[\'"]?\)/i', $cssContent, $matches);
 | ||
| 
 | ||
| 			// If it starts with forward slash (/), it doesn't need fix, just skip it
 | ||
| 			// Also skip ../ types as they were already processed
 | ||
| 			$toSkipList = array("url('/", 'url("/', 'url(/');
 | ||
| 
 | ||
| 			foreach ($matches[0] as $match) {
 | ||
| 				$fullUrlMatch = trim($match);
 | ||
| 
 | ||
| 				foreach ($toSkipList as $toSkip) {
 | ||
| 					if (substr($fullUrlMatch, 0, strlen($toSkip)) === $toSkip) {
 | ||
| 						continue 2; // doesn't need any fix, go to the next match
 | ||
| 					}
 | ||
| 				}
 | ||
| 
 | ||
| 				// Go through all situations: with and without quotes, with traversal directory (e.g. ../../)
 | ||
| 				$alteredMatch = str_replace(
 | ||
| 					array('url("', "url('"),
 | ||
| 					array('url("' . $appendBefore, "url('" . $appendBefore),
 | ||
| 					$fullUrlMatch
 | ||
| 				);
 | ||
| 
 | ||
| 				$alteredMatch = trim($alteredMatch);
 | ||
| 
 | ||
| 				if (! in_array($fullUrlMatch[4], array("'", '"', '/', '.', '#'))) {
 | ||
| 					$alteredMatch = str_replace('url(', 'url(' . $appendBefore, $alteredMatch);
 | ||
| 					$alteredMatch = str_replace(array('")', '\')'), ')', $alteredMatch);
 | ||
| 				}
 | ||
| 
 | ||
| 				// Finally, apply the changes
 | ||
| 				$cssContent = str_replace($fullUrlMatch, $alteredMatch, $cssContent);
 | ||
| 
 | ||
| 				// Bug fix
 | ||
| 				$cssContent = str_replace(
 | ||
| 					array($appendBefore . '"' . $appendBefore, $appendBefore . "'" . $appendBefore),
 | ||
| 					$appendBefore,
 | ||
| 					$cssContent
 | ||
| 				);
 | ||
| 
 | ||
| 				// Bug Fix 2
 | ||
| 				$cssContent = str_replace($appendBefore . 'http', 'http', $cssContent);
 | ||
| 				$cssContent = str_replace($appendBefore . '//', '//', $cssContent);
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return $cssContent;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Next: Alter the HTML source by updating the original link URLs with the just cached ones
 | ||
| 	 *
 | ||
| 	 * @param $htmlSource
 | ||
| 	 *
 | ||
| 	 * @return mixed
 | ||
| 	 */
 | ||
| 	public static function updateHtmlSourceOriginalToOptimizedCss($htmlSource)
 | ||
| 	{
 | ||
| 		$parseSiteUrlPath = (string)parse_url(site_url(), PHP_URL_PATH);
 | ||
| 
 | ||
| 		$siteUrlNoProtocol = str_replace(array('http://', 'https://'), '//', site_url());
 | ||
| 
 | ||
| 		$cssOptimizeList = ObjectCache::wpacu_cache_get('wpacu_css_optimize_list') ?: array();
 | ||
| 		$allEnqueuedCleanSources = ObjectCache::wpacu_cache_get('wpacu_css_enqueued_hrefs') ?: array();
 | ||
| 
 | ||
| 		$allEnqueuedCleanSourcesIncludingTheirRelPaths = array();
 | ||
| 
 | ||
| 		foreach ($allEnqueuedCleanSources as $allEnqueuedCleanSource) {
 | ||
| 			$allEnqueuedCleanSourcesIncludingTheirRelPaths[] = $allEnqueuedCleanSource;
 | ||
| 
 | ||
| 			if (strpos($allEnqueuedCleanSource, 'http://') === 0 || strpos($allEnqueuedCleanSource, 'https://') === 0) {
 | ||
| 				$allEnqueuedCleanSourcesIncludingTheirRelPaths[] = str_replace(array('http://', 'https://'), '//', $allEnqueuedCleanSource);
 | ||
| 
 | ||
| 				// e.g. www.mysite.com/blog/
 | ||
| 				if ($parseSiteUrlPath !== '/' && strlen($parseSiteUrlPath) > 1) {
 | ||
| 					$allEnqueuedCleanSourcesIncludingTheirRelPaths[] = $parseSiteUrlPath . str_replace(site_url(), '', $allEnqueuedCleanSource);
 | ||
| 				}
 | ||
| 
 | ||
| 				// e.g. www.mysite.com/
 | ||
| 				if ($parseSiteUrlPath === '/' || ! $parseSiteUrlPath) {
 | ||
| 					$allEnqueuedCleanSourcesIncludingTheirRelPaths[] = str_replace(site_url(), '', $allEnqueuedCleanSource);
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		$cdnUrls = OptimizeCommon::getAnyCdnUrls();
 | ||
| 		$cdnUrlForCss = isset($cdnUrls['css']) ? $cdnUrls['css'] : false;
 | ||
| 
 | ||
| 		// Grabs both LINK "stylesheet" and those with as="style" which is for preloaded LINK tags
 | ||
| 		preg_match_all('#<link[^>]*(stylesheet|(as(\s+|)=(\s+|)(|"|\')style(|"|\')))[^>]*>#Umi', OptimizeCommon::cleanerHtmlSource( $htmlSource, array( 'for_fetching_link_tags' ) ), $matchesSourcesFromTags, PREG_SET_ORDER);
 | ||
| 
 | ||
| 		if (empty($matchesSourcesFromTags)) {
 | ||
| 			return $htmlSource;
 | ||
| 		}
 | ||
| 
 | ||
| 		$cssOptimizeListHardcoded = $linkTagsToUpdate = array();
 | ||
| 
 | ||
| 		foreach ($matchesSourcesFromTags as $matches) {
 | ||
| 			$linkSourceTag = $matches[0];
 | ||
| 
 | ||
| 			if ($linkSourceTag === '' || strip_tags($linkSourceTag) !== '') {
 | ||
| 				// Hmm? Not a valid tag... Skip it...
 | ||
| 				continue;
 | ||
| 			}
 | ||
| 
 | ||
| 			// Check if the CSS has any 'data-wpacu-skip' attribute; if it does, do not alter it
 | ||
| 			if (preg_match('#data-wpacu-skip([=>/ ])#i', $linkSourceTag)) {
 | ||
| 				continue;
 | ||
| 			}
 | ||
| 
 | ||
| 			$cleanLinkHrefFromTagArray = OptimizeCommon::getLocalCleanSourceFromTag($linkSourceTag);
 | ||
| 
 | ||
| 			// Skip external links, no point in carrying on
 | ||
| 			if (! $cleanLinkHrefFromTagArray || ! is_array($cleanLinkHrefFromTagArray)) {
 | ||
| 				continue;
 | ||
| 			}
 | ||
| 
 | ||
| 			// Is it a local CSS? Check if it's hardcoded (not enqueued the WordPress way)
 | ||
| 			$cleanLinkHrefFromTag = $cleanLinkHrefFromTagArray['source'];
 | ||
| 			$afterQuestionMark = $cleanLinkHrefFromTagArray['after_question_mark'];
 | ||
| 
 | ||
| 			$isHardcodedDetected = false;
 | ||
| 
 | ||
| 			if (! in_array($cleanLinkHrefFromTag, $allEnqueuedCleanSourcesIncludingTheirRelPaths)) {
 | ||
| 				// Not in the final enqueued list? Most likely hardcoded (not added via wp_enqueue_scripts())
 | ||
| 				// Emulate the object value (as the enqueued styles)
 | ||
| 				$generatedHandle = md5($cleanLinkHrefFromTag);
 | ||
| 
 | ||
| 				$value = (object)array(
 | ||
| 					'handle' => $generatedHandle,
 | ||
| 					'src'    => $cleanLinkHrefFromTag,
 | ||
| 					'ver'    => md5($afterQuestionMark)
 | ||
| 				);
 | ||
| 
 | ||
| 				$optimizeValues = self::maybeOptimizeIt($value);
 | ||
| 				ObjectCache::wpacu_cache_set('wpacu_maybe_optimize_it_css_'.$generatedHandle, $optimizeValues);
 | ||
| 
 | ||
| 				if (! empty($optimizeValues)) {
 | ||
| 					$isHardcodedDetected = true;
 | ||
| 					$cssOptimizeListHardcoded[] = $optimizeValues;
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			if ( ! $isHardcodedDetected ) {
 | ||
| 				$listToParse = $cssOptimizeList;
 | ||
| 			} else {
 | ||
| 				$listToParse = $cssOptimizeListHardcoded;
 | ||
| 			}
 | ||
| 
 | ||
| 			if (empty($listToParse)) {
 | ||
| 				continue;
 | ||
| 			}
 | ||
| 
 | ||
| 			foreach ($listToParse as $listValues) {
 | ||
| 				// Index 0: Source URL (relative)
 | ||
| 				// Index 1: New Optimized URL (relative)
 | ||
| 				// Index 2: Source URL (as it is)
 | ||
| 
 | ||
| 				// if the relative path from the WP root does not match the value of the source from the tag, do not continue
 | ||
| 				// e.g. '/wp-content/plugins/my-plugin/script.js' has to be inside '<script src="/wp-content/plugins/my-plugin/script.js?ver=1.1"></script>'
 | ||
| 				if (strpos($cleanLinkHrefFromTag, $listValues[0]) === false) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				// The contents of the CSS file has been changed and thus, we will replace the source path from the original tag with the cached (e.g. minified) one
 | ||
| 
 | ||
| 				// If the minified files are deleted (e.g. /wp-content/cache/ is cleared)
 | ||
| 				// do not replace the CSS file path to avoid breaking the website
 | ||
| 				$localPathOptimizedFile = rtrim(Misc::getWpRootDirPath(), '/') . $listValues[1];
 | ||
| 
 | ||
| 				if (! is_file($localPathOptimizedFile)) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				// Make sure the source URL gets updated even if it starts with // (some plugins/theme strip the protocol when enqueuing assets)
 | ||
| 				// If the first value fails to be replaced, the next one will be attempted for replacement
 | ||
| 				// the order of the elements in the array is very important
 | ||
| 				$sourceUrlList = array(
 | ||
| 					site_url() . $listValues[0], // with protocol
 | ||
| 					$siteUrlNoProtocol . $listValues[0], // without protocol
 | ||
| 				);
 | ||
| 
 | ||
| 				if ($parseSiteUrlPath && (strpos($listValues[0], $parseSiteUrlPath) === 0 || strpos($cleanLinkHrefFromTag, $parseSiteUrlPath) === 0)) {
 | ||
| 					$sourceUrlList[] = $cleanLinkHrefFromTag;
 | ||
| 				}
 | ||
| 
 | ||
| 				if ($parseSiteUrlPath && (strpos($cleanLinkHrefFromTag, $parseSiteUrlPath) === 0 && strpos($cleanLinkHrefFromTag, $listValues[0]) !== false)) {
 | ||
| 					$sourceUrlList[] = str_replace('//', '/', $parseSiteUrlPath.'/'.$listValues[0]);
 | ||
| 				}
 | ||
| 				elseif ( $cleanLinkHrefFromTag === $listValues[0] ) {
 | ||
| 					$sourceUrlList[] = $listValues[0];
 | ||
| 				}
 | ||
| 
 | ||
| 				if ($cdnUrlForCss) {
 | ||
| 					// Does it have a CDN?
 | ||
| 					$sourceUrlList[] = OptimizeCommon::cdnToUrlFormat($cdnUrlForCss, 'rel') . $listValues[0];
 | ||
| 				}
 | ||
| 
 | ||
| 				// Any rel tag? You never know
 | ||
| 				// e.g. <link src="/wp-content/themes/my-theme/style.css"></script>
 | ||
| 				if ( (strpos($listValues[2], '/') === 0 && strpos($listValues[2], '//') !== 0)
 | ||
| 					|| (strpos($listValues[2], '/') !== 0 &&
 | ||
| 					    strpos($listValues[2], '//') !== 0 &&
 | ||
| 					    stripos($listValues[2], 'http://') !== 0 &&
 | ||
| 					    stripos($listValues[2], 'https://') !== 0) ) {
 | ||
| 					$sourceUrlList[] = $listValues[2];
 | ||
| 
 | ||
| 					}
 | ||
| 
 | ||
| 				if ( $cleanLinkHrefFromTag === $listValues[0] ) {
 | ||
| 					$sourceUrlList[] = $cleanLinkHrefFromTag;
 | ||
| 				}
 | ||
| 
 | ||
| 				// If no CDN is set, it will return site_url() as a prefix
 | ||
| 				$optimizeUrl = OptimizeCommon::cdnToUrlFormat($cdnUrlForCss, 'raw') . $listValues[1]; // string
 | ||
| 
 | ||
| 				if ($linkSourceTag !== str_replace($sourceUrlList, $optimizeUrl, $linkSourceTag)) {
 | ||
| 					// Extra measure: Check the file size which should be 4 bytes, but add some margin error in case some environments will report less
 | ||
| 					$isEmptyOptimizedFile = (strpos($localPathOptimizedFile, '-wpacu-empty-file.css') !== false && filesize($localPathOptimizedFile) < 10);
 | ||
| 
 | ||
| 					// Strip it as its content (after optimization, for instance) is empty; no point in having extra HTTP requests
 | ||
| 					if ($isEmptyOptimizedFile) {
 | ||
| 						// Note: As for September 3, 2020, the inline CSS associated with the handle is no longer removed if the main CSS file is empty
 | ||
| 						// There could be cases when the main CSS file is empty (e.g. theme's styling), but the inline STYLE tag associated with it has syntax that is needed
 | ||
| 
 | ||
| 						$htmlSource       = str_replace($linkSourceTag, '', $htmlSource);
 | ||
| 
 | ||
| 						} else {
 | ||
| 						// Do the replacement
 | ||
| 						$newLinkSourceTag = self::updateOriginalToOptimizedTag( $linkSourceTag, $sourceUrlList, $optimizeUrl );
 | ||
| 						$linkTagsToUpdate[$linkSourceTag] = $newLinkSourceTag;
 | ||
| 						}
 | ||
| 
 | ||
| 					break; // there was a match, stop here
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return strtr($htmlSource, $linkTagsToUpdate);
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $linkSourceTag string
 | ||
| 	 * @param $sourceUrlList array
 | ||
| 	 * @param $optimizeUrl string
 | ||
| 	 *
 | ||
| 	 * @return array|string|string[]|null
 | ||
| 	 */
 | ||
| 	public static function updateOriginalToOptimizedTag($linkSourceTag, $sourceUrlList, $optimizeUrl)
 | ||
| 	{
 | ||
| 		if (is_array($sourceUrlList) && ! empty($sourceUrlList)) {
 | ||
| 			foreach ($sourceUrlList as $sourceUrl) {
 | ||
| 				$newLinkSourceTag = str_replace($sourceUrl, $optimizeUrl, $linkSourceTag);
 | ||
| 
 | ||
| 				if ($newLinkSourceTag !== $linkSourceTag) {
 | ||
| 					break;
 | ||
| 				}
 | ||
| 			}
 | ||
| 		} else {
 | ||
| 			$newLinkSourceTag = str_replace( $sourceUrlList, $optimizeUrl, $linkSourceTag );
 | ||
| 		}
 | ||
| 
 | ||
| 		// Needed in case it's added to the Combine CSS exceptions list
 | ||
| 		if (CombineCss::proceedWithCssCombine()) {
 | ||
| 			$sourceUrlRel = is_array($sourceUrlList) ? OptimizeCommon::getSourceRelPath($sourceUrlList[0]) : OptimizeCommon::getSourceRelPath($sourceUrlList);
 | ||
| 			$newLinkSourceTag = str_ireplace('<link ', '<link data-wpacu-link-rel-href-before="'.$sourceUrlRel.'" ', $newLinkSourceTag);
 | ||
| 		}
 | ||
| 
 | ||
| 		$hrefValue = Misc::getValueFromTag($newLinkSourceTag);
 | ||
| 
 | ||
| 		// No space from the matching and ? should be there
 | ||
| 		if ($hrefValue && ( strpos( $hrefValue, ' ' ) === false )) {
 | ||
| 			if ( strpos( $hrefValue, '?' ) !== false ) {
 | ||
| 				// Strip things like ?ver=
 | ||
| 				list( , $toStrip ) = explode( '?', $hrefValue );
 | ||
| 				$toStrip            = '?' . trim( $toStrip );
 | ||
| 				$newLinkSourceTag = str_replace( $toStrip, '', $newLinkSourceTag );
 | ||
| 			}
 | ||
| 
 | ||
| 			if ( strpos( $hrefValue, '&ver' ) !== false ) {
 | ||
| 				// Replace any .js&ver with .js
 | ||
| 				$toStrip = strrchr($hrefValue, '&ver');
 | ||
| 				$newLinkSourceTag = str_replace( $toStrip, '', $newLinkSourceTag );
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		global $wp_version;
 | ||
| 
 | ||
| 		$newLinkSourceTag = str_replace('.css&ver='.$wp_version, '.css', $newLinkSourceTag);
 | ||
| 		$newLinkSourceTag = str_replace('.css&ver=', '.css', $newLinkSourceTag);
 | ||
| 
 | ||
| 		return preg_replace('!\s+!', ' ', $newLinkSourceTag); // replace multiple spaces with only one space
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function isInlineCssEnabled()
 | ||
| 	{
 | ||
| 		$isEnabledInSettingsWithListOrAuto = (Main::instance()->settings['inline_css_files'] &&
 | ||
|             (trim(Main::instance()->settings['inline_css_files_list']) !== '' || self::isAutoInlineEnabled()));
 | ||
| 
 | ||
| 		if (! $isEnabledInSettingsWithListOrAuto) {
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Deactivate it for debugging purposes via query string /?wpacu_no_inline_js
 | ||
| 		if ( isset($_GET['wpacu_no_inline_css']) ) {
 | ||
| 			return false;
 | ||
| 		}
 | ||
| 
 | ||
| 		// Finally, return true
 | ||
| 		return true;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * From LINK to STYLE tag: it processes the contents of the LINK stylesheet and replaces the tag with a STYLE tag having the content inlined
 | ||
| 	 *
 | ||
| 	 * @param $htmlSource
 | ||
| 	 *
 | ||
| 	 * @return mixed
 | ||
| 	 */
 | ||
| 	public static function doInline($htmlSource)
 | ||
| 	{
 | ||
| 		$allPatterns = self::getAllInlineChosenPatterns();
 | ||
| 
 | ||
| 		// Skip any LINK tags within conditional comments (e.g. Internet Explorer ones)
 | ||
| 		preg_match_all(
 | ||
| 			'#<link[^>]*stylesheet[^>]*>#Umsi',
 | ||
| 			OptimizeCommon::cleanerHtmlSource( $htmlSource, array( 'strip_content_between_conditional_comments', 'for_fetching_link_tags' ) ),
 | ||
| 			$matchesSourcesFromTags,
 | ||
| 			PREG_SET_ORDER
 | ||
| 		);
 | ||
| 
 | ||
| 		// In case automatic inlining is used
 | ||
| 		$belowSizeInput = (int)Main::instance()->settings['inline_css_files_below_size_input'];
 | ||
| 
 | ||
| 		if ($belowSizeInput === 0) {
 | ||
| 			$belowSizeInput = 1; // needs to have a minimum value
 | ||
| 		}
 | ||
| 
 | ||
| 		if (! empty($matchesSourcesFromTags)) {
 | ||
| 			$cdnUrls = OptimizeCommon::getAnyCdnUrls();
 | ||
| 			$cdnUrlForCss = isset($cdnUrls['css']) ? trim($cdnUrls['css']) : false;
 | ||
| 
 | ||
| 			foreach ($matchesSourcesFromTags as $matchList) {
 | ||
| 				$matchedTag = $matchList[0];
 | ||
| 
 | ||
| 				if ( stripos( $matchedTag, '<link' ) !== 0 ) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				// Do not inline the admin bar SCRIPT file, saving resources as it's shown for the logged-in user only
 | ||
| 				if (strpos($matchedTag, '/wp-includes/css/admin-bar') !== false) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				// They were preloaded for a reason, leave them
 | ||
| 				if (strpos($matchedTag, 'data-wpacu-preload-it-async=') !== false || strpos($matchedTag, 'data-wpacu-to-be-preloaded-basic=') !== false) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				if (strip_tags($matchedTag) !== '') {
 | ||
| 					continue; // something is funny, don't mess with the HTML alteration, leave it as it was
 | ||
| 				}
 | ||
| 
 | ||
| 				$chosenInlineCssMatches = false;
 | ||
| 
 | ||
| 				// Condition #1: Only chosen (via textarea) CSS get inlined
 | ||
| 				if ( false !== strpos( $matchedTag, ' wpacu-to-be-inlined' ) ) {
 | ||
| 					$chosenInlineCssMatches = true;
 | ||
| 				} elseif ( ! empty( $allPatterns ) ) {
 | ||
| 					// Fallback, in case "wpacu-to-be-inlined" was not already added to the tag
 | ||
| 					foreach ($allPatterns as $patternToCheck) {
 | ||
| 						if (preg_match('#'.$patternToCheck.'#si', $matchedTag) || strpos($matchedTag, $patternToCheck) !== false) {
 | ||
| 							$chosenInlineCssMatches = true;
 | ||
| 							break;
 | ||
| 						}
 | ||
| 					}
 | ||
| 				}
 | ||
| 
 | ||
| 				// Is auto inline disabled and the chosen CSS does not match? Continue to the next LINK tag
 | ||
| 				if (! $chosenInlineCssMatches && ! self::isAutoInlineEnabled()) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				$linkHrefOriginal = Misc::getValueFromTag($matchedTag);
 | ||
| 				$localAssetPath = OptimizeCommon::getLocalAssetPath($linkHrefOriginal, 'css');
 | ||
| 
 | ||
| 				if (! $localAssetPath) {
 | ||
| 					continue; // Not on the same domain
 | ||
| 				}
 | ||
| 
 | ||
| 				// Condition #2: Auto inline is enabled and there's no match for any entry in the textarea
 | ||
| 				if (! $chosenInlineCssMatches && self::isAutoInlineEnabled()) {
 | ||
| 					$fileSizeKb = number_format(filesize($localAssetPath) / 1024, 2);
 | ||
| 
 | ||
| 					// If it's not smaller than the value from the input, do not continue with the inlining
 | ||
| 					if ($fileSizeKb >= $belowSizeInput) {
 | ||
| 						continue;
 | ||
| 					}
 | ||
| 				}
 | ||
| 
 | ||
| 				// Is there a media attribute? Make sure to add it to the STYLE tag
 | ||
| 				$mediaAttrValue = Misc::getValueFromTag($matchedTag, 'media');
 | ||
| 
 | ||
| 				$mediaAttr = ($mediaAttrValue && $mediaAttrValue !== 'all') ? 'media=\''.$mediaAttrValue.'\'' : '';
 | ||
| 
 | ||
| 				$appendBeforeAnyRelPath = $cdnUrlForCss ? OptimizeCommon::cdnToUrlFormat($cdnUrlForCss, 'raw') : '';
 | ||
| 
 | ||
| 				$cssContent = self::maybeFixCssContent(
 | ||
| 					FileSystem::fileGetContents($localAssetPath, 'combine_css_imports'), // CSS content
 | ||
| 					$appendBeforeAnyRelPath . OptimizeCommon::getPathToAssetDir($linkHrefOriginal) . '/'
 | ||
| 				);
 | ||
| 
 | ||
| 				// The CSS file is read from its original plugin/theme/cache location
 | ||
| 				// If minify was enabled, then it's already minified, no point in re-minify it to save resources
 | ||
| 				// Changing paths (relative) to fonts, images, etc. are relevant in this case
 | ||
| 				$cssContent = self::maybeAlterContentForCssFile($cssContent, false);
 | ||
| 
 | ||
| 				if ($cssContent && $cssContent !== '/**/') {
 | ||
| 					$htmlSource = str_replace(
 | ||
| 						$matchedTag,
 | ||
| 						'<style '.Misc::getStyleTypeAttribute().' '.$mediaAttr.' data-wpacu-inline-css-file=\'1\'>'."\n".$cssContent."\n".'</style>',
 | ||
| 						$htmlSource
 | ||
| 					);
 | ||
| 				} else {
 | ||
| 					// After CSS alteration (e.g. minify), there's no content left, most likely the CSS file contained only comments, elements without any syntax or empty spaces
 | ||
| 					// Strip the tag completely as there's no reason to print an empty SCRIPT tag to further add to the total DOM elements
 | ||
| 					$htmlSource = str_replace($matchedTag, '', $htmlSource);
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return $htmlSource;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * This applies to both inline and static JS files contents
 | ||
| 	 *
 | ||
| 	 * @param $cssContent
 | ||
| 	 * @param bool $doCssMinify (false by default as it could be already minified or non-minify type)
 | ||
| 	 * @param array $extraParams
 | ||
| 	 *
 | ||
| 	 * @return mixed|string|string[]|null
 | ||
| 	 */
 | ||
| 	public static function maybeAlterContentForCssFile($cssContent, $doCssMinify = false, $extraParams = array())
 | ||
| 	{
 | ||
| 		if (! trim($cssContent)) {
 | ||
| 			return $cssContent;
 | ||
| 		}
 | ||
| 
 | ||
| 		/* [START] Change CSS Content */
 | ||
| 		// Move any @imports to top; This also strips any @imports to Google Fonts if the option is chosen
 | ||
| 		$cssContent = self::importsUpdate( $cssContent );
 | ||
| 
 | ||
| 		if ( $doCssMinify ) {
 | ||
| 			$cssContent = MinifyCss::applyMinification( $cssContent, $doCssMinify );
 | ||
| 		}
 | ||
| 
 | ||
| 		if ( Main::instance()->settings['google_fonts_remove'] ) {
 | ||
| 			$cssContent = FontsGoogleRemove::cleanFontFaceReferences( $cssContent );
 | ||
| 		}
 | ||
| 
 | ||
| 		// Does it have a source map? Strip it
 | ||
| 		if (strpos($cssContent, '/*# sourceMappingURL=') !== false) {
 | ||
| 			$cssContent = OptimizeCommon::stripSourceMap($cssContent, 'css');
 | ||
| 		}
 | ||
| 		/* [END] Change CSS Content */
 | ||
| 
 | ||
| 		return $cssContent;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $cssContent
 | ||
| 	 * @param bool $doCssMinify
 | ||
| 	 * @param array $extraParams
 | ||
| 	 *
 | ||
| 	 * @return mixed|string
 | ||
| 	 */
 | ||
| 	public static function maybeAlterContentForInlineStyleTag($cssContent, $doCssMinify = false, $extraParams = array())
 | ||
| 	{
 | ||
| 		if (! trim($cssContent)) {
 | ||
| 			return $cssContent;
 | ||
| 		}
 | ||
| 
 | ||
| 		$useCacheForInlineStyle = true;
 | ||
| 
 | ||
| 		if (mb_strlen($cssContent) > 500000) { // Bigger then ~500KB? Skip alteration
 | ||
| 			return $cssContent;
 | ||
| 		}
 | ||
| 
 | ||
| 		if (mb_strlen($cssContent) < 40000) { // Smaller than ~40KB? Do not cache it
 | ||
| 			$useCacheForInlineStyle = false;
 | ||
| 		}
 | ||
| 
 | ||
| 		// For debugging purposes
 | ||
| 		if (isset($_GET['wpacu_no_cache']) || (defined('WPACU_NO_CACHE') && WPACU_NO_CACHE === true)) { $useCacheForInlineStyle = false; }
 | ||
| 
 | ||
| 		if ($useCacheForInlineStyle) {
 | ||
| 			// Anything in the cache? Take it from there and don't spend resources with the minification
 | ||
| 			// (which in some environments uses the CPU, depending on the complexity of the JavaScript code) and any other alteration
 | ||
| 			$cssContentBeforeHash = sha1( $cssContent );
 | ||
| 
 | ||
| 			$pathToInlineCssOptimizedItem = WP_CONTENT_DIR . self::getRelPathCssCacheDir() . '/item/inline/' . $cssContentBeforeHash . '.css';
 | ||
| 
 | ||
| 			// Check if the file exists before moving forward
 | ||
| 			if ( is_file( $pathToInlineCssOptimizedItem ) ) {
 | ||
| 				$cachedCssFileExpiresIn = OptimizeCommon::$cachedAssetFileExpiresIn;
 | ||
| 
 | ||
| 				if ( filemtime( $pathToInlineCssOptimizedItem ) < ( time() - 1 * $cachedCssFileExpiresIn ) ) {
 | ||
| 					// Has the caching period expired? Remove the file as a new one has to be generated
 | ||
| 					@unlink( $pathToInlineCssOptimizedItem );
 | ||
| 				} else {
 | ||
| 					// Not expired / Return its content from the cache in a faster way
 | ||
| 					$inlineCssStorageItemJsonContent = trim( FileSystem::fileGetContents( $pathToInlineCssOptimizedItem ) );
 | ||
| 
 | ||
| 					if ( $inlineCssStorageItemJsonContent !== '' ) {
 | ||
| 						return $inlineCssStorageItemJsonContent;
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		/* [START] Change CSS Content */
 | ||
| 		if ( $doCssMinify && in_array('just_minify', $extraParams) ) {
 | ||
| 			$cssContent = MinifyCss::applyMinification( $cssContent, $useCacheForInlineStyle );
 | ||
| 		} else {
 | ||
| 			// Move any @imports to top; This also strips any @imports to Google Fonts if the option is chosen
 | ||
| 			$cssContent = self::importsUpdate( $cssContent );
 | ||
| 
 | ||
| 			if ( $doCssMinify ) {
 | ||
| 				$cssContent = MinifyCss::applyMinification( $cssContent, $useCacheForInlineStyle );
 | ||
| 			}
 | ||
| 
 | ||
| 			if ( Main::instance()->settings['google_fonts_remove'] ) {
 | ||
| 				$cssContent = FontsGoogleRemove::cleanFontFaceReferences( $cssContent );
 | ||
| 			}
 | ||
| 		}
 | ||
| 		/* [END] Change CSS Content */
 | ||
| 
 | ||
| 		if ($useCacheForInlineStyle && isset($pathToInlineCssOptimizedItem)) {
 | ||
| 			// Store the optimized content to the cached CSS file which would be read quicker
 | ||
| 			FileSystem::filePutContents( $pathToInlineCssOptimizedItem, $cssContent );
 | ||
| 		}
 | ||
| 
 | ||
| 		return $cssContent;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function isAutoInlineEnabled()
 | ||
| 	{
 | ||
| 		return Main::instance()->settings['inline_css_files'] &&
 | ||
| 		       Main::instance()->settings['inline_css_files_below_size'] &&
 | ||
| 		       (int)Main::instance()->settings['inline_css_files_below_size_input'] > 0;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Source: https://www.minifier.org/ | https://github.com/matthiasmullie/minify
 | ||
| 	 *
 | ||
| 	 * @param $content
 | ||
| 	 *
 | ||
| 	 * @return string
 | ||
| 	 */
 | ||
| 	public static function importsUpdate($content)
 | ||
| 	{
 | ||
| 		if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) {
 | ||
| 			// Remove from content (they will be appended to the top if they qualify)
 | ||
| 			foreach ($matches[0] as $import) {
 | ||
| 				$content = str_replace($import, '', $content);
 | ||
| 			}
 | ||
| 
 | ||
| 			// Strip any @imports to Google Fonts if it's the case
 | ||
| 			$importsAddToTop = Main::instance()->settings['google_fonts_remove'] ? FontsGoogleRemove::stripGoogleApisImport($matches[2]) : $matches[2];
 | ||
| 
 | ||
| 			// Add to top if there are any imports left
 | ||
| 			if (! empty($importsAddToTop)) {
 | ||
| 				$content = implode(';', $importsAddToTop) . ';' . trim($content, ';');
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return $content;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * e.g. if a style is unloaded, strip any LINK tag that preloads that style (e.g. added by other plugins)
 | ||
| 	 *
 | ||
| 	 * @param $htmlSource
 | ||
| 	 *
 | ||
| 	 * @return array|mixed|string|string[]
 | ||
| 	 */
 | ||
| 	public static function stripAnyReferencesForUnloadedStyles($htmlSource)
 | ||
| 	{
 | ||
| 		// Gather all HREFs of the unloaded styles (if any)
 | ||
| 		$unloadedStyleRelHrefs = array();
 | ||
| 
 | ||
| 		if ( isset( Main::instance()->allUnloadedAssets['styles'] ) && ! empty( Main::instance()->allUnloadedAssets['styles'] ) ) {
 | ||
| 			foreach ( array_unique( Main::instance()->allUnloadedAssets['styles'] ) as $styleHandle ) {
 | ||
| 				if ( ! (isset(Main::instance()->wpAllStyles['registered'][ $styleHandle ]->src) && Main::instance()->wpAllStyles['registered'][ $styleHandle ]->src) ) {
 | ||
| 					continue; // does not have a "src" (e.g. inline CSS)
 | ||
| 				}
 | ||
| 				$unloadedStyleRelHrefs[] = OptimizeCommon::getSourceRelPath( Main::instance()->wpAllStyles['registered'][ $styleHandle ]->src );
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		if ( ! empty($unloadedStyleRelHrefs) ) {
 | ||
| 			$htmlSource = OptimizeCommon::matchAndReplaceLinkTags($htmlSource, array('as' => 'style', 'unloaded_assets_rel_sources' => $unloadedStyleRelHrefs));
 | ||
| 		}
 | ||
| 
 | ||
| 		return $htmlSource;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param string $returnType
 | ||
| 	 *
 | ||
| 	 * @return array|bool
 | ||
| 	 */
 | ||
| 	public static function isOptimizeCssEnabledByOtherParty($returnType = 'list')
 | ||
| 	{
 | ||
| 		$pluginsToCheck = array(
 | ||
| 			'autoptimize/autoptimize.php'            => 'Autoptimize',
 | ||
| 			'wp-rocket/wp-rocket.php'                => 'WP Rocket',
 | ||
| 			'wp-fastest-cache/wpFastestCache.php'    => 'WP Fastest Cache',
 | ||
| 			'w3-total-cache/w3-total-cache.php'      => 'W3 Total Cache',
 | ||
| 			'sg-cachepress/sg-cachepress.php'        => 'SG Optimizer',
 | ||
| 			'fast-velocity-minify/fvm.php'           => 'Fast Velocity Minify',
 | ||
| 			'litespeed-cache/litespeed-cache.php'    => 'LiteSpeed Cache',
 | ||
| 			'swift-performance-lite/performance.php' => 'Swift Performance Lite',
 | ||
| 			'breeze/breeze.php'                      => 'Breeze – WordPress Cache Plugin'
 | ||
| 		);
 | ||
| 
 | ||
| 		$cssOptimizeEnabledIn = array();
 | ||
| 
 | ||
| 		foreach ($pluginsToCheck as $plugin => $pluginTitle) {
 | ||
| 			// "Autoptimize" check
 | ||
| 			if ($plugin === 'autoptimize/autoptimize.php' && Misc::isPluginActive($plugin) && get_option('autoptimize_css')) {
 | ||
| 				$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 
 | ||
| 				if ($returnType === 'if_enabled') { return true; }
 | ||
| 			}
 | ||
| 
 | ||
| 			// "WP Rocket" check
 | ||
| 			if ($plugin === 'wp-rocket/wp-rocket.php' && Misc::isPluginActive($plugin)) {
 | ||
| 				if (function_exists('get_rocket_option')) {
 | ||
| 					$wpRocketMinifyCss = trim(get_rocket_option('minify_css')) ?: false;
 | ||
| 					$wpRocketMinifyConcatenateCss = trim(get_rocket_option('minify_concatenate_css')) ?: false;
 | ||
| 				} else {
 | ||
| 					$wpRocketSettings  = get_option('wp_rocket_settings');
 | ||
| 					$wpRocketMinifyCss = isset($wpRocketSettings['minify_css']) && trim($wpRocketSettings['minify_css']);
 | ||
| 					$wpRocketMinifyConcatenateCss = isset($wpRocketSettings['minify_concatenate_css']) && trim($wpRocketSettings['minify_concatenate_css']);
 | ||
| 				}
 | ||
| 
 | ||
| 				if ($wpRocketMinifyCss || $wpRocketMinifyConcatenateCss) {
 | ||
| 					$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 
 | ||
| 					if ($returnType === 'if_enabled') { return true; }
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			// "WP Fastest Cache" check
 | ||
| 			if ($plugin === 'wp-fastest-cache/wpFastestCache.php' && Misc::isPluginActive($plugin)) {
 | ||
| 				$wpfcOptionsJson = get_option('WpFastestCache');
 | ||
| 				$wpfcOptions = @json_decode($wpfcOptionsJson, ARRAY_A);
 | ||
| 
 | ||
| 				if (isset($wpfcOptions['wpFastestCacheMinifyCss']) || isset($wpfcOptions['wpFastestCacheCombineCss'])) {
 | ||
| 					$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 
 | ||
| 					if ($returnType === 'if_enabled') { return true; }
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			// "W3 Total Cache" check
 | ||
| 			if ($plugin === 'w3-total-cache/w3-total-cache.php' && Misc::isPluginActive($plugin)) {
 | ||
| 				$w3tcConfigMaster = Misc::getW3tcMasterConfig();
 | ||
| 				$w3tcEnableCss = (int)trim(Misc::extractBetween($w3tcConfigMaster, '"minify.css.enable":', ','), '" ');
 | ||
| 
 | ||
| 				if ($w3tcEnableCss === 1) {
 | ||
| 					$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 
 | ||
| 					if ($returnType === 'if_enabled') { return true; }
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			// "SG Optimizer" check
 | ||
| 			if ($plugin === 'sg-cachepress/sg-cachepress.php' && Misc::isPluginActive($plugin)) {
 | ||
| 				if (class_exists('\SiteGround_Optimizer\Options\Options')
 | ||
| 				    && method_exists('\SiteGround_Optimizer\Options\Options', 'is_enabled')
 | ||
| 				    && @\SiteGround_Optimizer\Options\Options::is_enabled('siteground_optimizer_combine_css')) {
 | ||
| 					$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 					if ($returnType === 'if_enabled') { return true; }
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			// "Fast Velocity Minify" check
 | ||
| 			if ($plugin === 'fast-velocity-minify/fvm.php' && Misc::isPluginActive($plugin)) {
 | ||
| 				// It's enough if it's active due to its configuration
 | ||
| 				$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 
 | ||
| 				if ($returnType === 'if_enabled') { return true; }
 | ||
| 			}
 | ||
| 
 | ||
| 			// "LiteSpeed Cache" check
 | ||
| 			if ($plugin === 'litespeed-cache/litespeed-cache.php' && Misc::isPluginActive($plugin) && ($liteSpeedCacheConf = apply_filters('litespeed_cache_get_options', get_option('litespeed-cache-conf')))) {
 | ||
| 				if ( (isset($liteSpeedCacheConf['css_minify']) && $liteSpeedCacheConf['css_minify'])
 | ||
| 				     || (isset($liteSpeedCacheConf['css_combine']) && $liteSpeedCacheConf['css_combine']) ) {
 | ||
| 					$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 
 | ||
| 					if ($returnType === 'if_enabled') { return true; }
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			// "Swift Performance Lite" check
 | ||
| 			if ($plugin === 'swift-performance-lite/performance.php' && Misc::isPluginActive($plugin)
 | ||
| 			    && class_exists('Swift_Performance_Lite') && method_exists('Swift_Performance_Lite', 'check_option')) {
 | ||
| 				if ( @\Swift_Performance_Lite::check_option('merge-styles', 1) ) {
 | ||
| 					$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 				}
 | ||
| 
 | ||
| 				if ($returnType === 'if_enabled') { return true; }
 | ||
| 			}
 | ||
| 
 | ||
| 			// "Breeze – WordPress Cache Plugin"
 | ||
| 			if ($plugin === 'breeze/breeze.php' && Misc::isPluginActive($plugin)) {
 | ||
| 				$breezeBasicSettings    = get_option('breeze_basic_settings');
 | ||
| 				$breezeAdvancedSettings = get_option('breeze_advanced_settings');
 | ||
| 
 | ||
| 				if (isset($breezeBasicSettings['breeze-minify-css'], $breezeAdvancedSettings['breeze-group-css'])
 | ||
| 				    && $breezeBasicSettings['breeze-minify-css'] && $breezeAdvancedSettings['breeze-group-css']) {
 | ||
| 					$cssOptimizeEnabledIn[] = $pluginTitle;
 | ||
| 
 | ||
| 					if ($returnType === 'if_enabled') { return true; }
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($returnType === 'if_enabled') { return false; }
 | ||
| 
 | ||
| 		return $cssOptimizeEnabledIn;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function isWpRocketOptimizeCssDeliveryEnabled()
 | ||
| 	{
 | ||
| 		if (Misc::isPluginActive('wp-rocket/wp-rocket.php')) {
 | ||
| 			if (function_exists('get_rocket_option')) {
 | ||
| 				$wpRocketAsyncCss = trim(get_rocket_option('async_css')) ?: false;
 | ||
| 			} else {
 | ||
| 				$wpRocketSettings  = get_option('wp_rocket_settings');
 | ||
| 				$wpRocketAsyncCss = isset($wpRocketSettings['async_css']) && trim($wpRocketSettings['async_css']);
 | ||
| 			}
 | ||
| 
 | ||
| 			return $wpRocketAsyncCss;
 | ||
| 		}
 | ||
| 
 | ||
| 		return false;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function wpfcMinifyCssEnabledOnly()
 | ||
| 	{
 | ||
| 		if (Misc::isPluginActive('wp-fastest-cache/wpFastestCache.php')) {
 | ||
| 			$wpfcOptionsJson = get_option('WpFastestCache');
 | ||
| 			$wpfcOptions     = @json_decode($wpfcOptionsJson, ARRAY_A);
 | ||
| 
 | ||
| 			// "Minify CSS" is enabled, "Combine CSS" is disabled
 | ||
| 			return isset($wpfcOptions['wpFastestCacheMinifyCss']) && ! isset($wpfcOptions['wpFastestCacheCombineCss']);
 | ||
| 		}
 | ||
| 
 | ||
| 		return false;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @return bool
 | ||
| 	 */
 | ||
| 	public static function isWorthCheckingForOptimization()
 | ||
| 	{
 | ||
| 		// At least one of these options have to be enabled
 | ||
| 		// Otherwise, we will not perform specific useless actions and save resources
 | ||
| 		return MinifyCss::isMinifyCssEnabled() ||
 | ||
| 		       Main::instance()->settings['google_fonts_display'] ||
 | ||
| 		       Main::instance()->settings['google_fonts_remove'];
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $htmlSource
 | ||
| 	 *
 | ||
| 	 * @return mixed
 | ||
| 	 */
 | ||
| 	public static function ignoreDependencyRuleAndKeepChildrenLoaded($htmlSource)
 | ||
| 	{
 | ||
| 		$ignoreChild = Main::instance()->getIgnoreChildren();
 | ||
| 
 | ||
| 		if (isset($ignoreChild['styles']) && ! empty($ignoreChild['styles'])) {
 | ||
| 			foreach (array_keys($ignoreChild['styles']) as $styleHandle) {
 | ||
| 				// Always load the Dashicons if the top admin bar (toolbar) is shown
 | ||
| 				if ($styleHandle === 'dashicons' && is_admin_bar_showing()) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				if (isset(Main::instance()->wpAllStyles['registered'][$styleHandle]->src, Main::instance()->ignoreChildren['styles'][$styleHandle.'_has_unload_rule']) && Main::instance()->wpAllStyles['registered'][$styleHandle]->src && Main::instance()->ignoreChildren['styles'][$styleHandle.'_has_unload_rule']) {
 | ||
| 					if ($scriptExtraAfterHtml = self::generateInlineAssocHtmlForHandle($styleHandle)) {
 | ||
| 						$htmlSource = str_replace($scriptExtraAfterHtml, '', $htmlSource);
 | ||
| 					}
 | ||
| 
 | ||
| 					$listWithMatches   = array();
 | ||
| 					$listWithMatches[] = 'data-wpacu-style-handle=[\'"]'.$styleHandle.'[\'"]';
 | ||
| 
 | ||
| 					if ($styleSrc = Main::instance()->wpAllStyles['registered'][$styleHandle]->src) {
 | ||
| 						$listWithMatches[] = OptimizeCommon::getSourceRelPath($styleSrc);
 | ||
| 					}
 | ||
| 
 | ||
| 					$htmlSource = CleanUp::cleanLinkTagFromHtmlSource($listWithMatches, $htmlSource);
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return $htmlSource;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $styleTagOrHandle
 | ||
| 	 * @param $wpacuRegisteredStyles
 | ||
| 	 * @param $from
 | ||
| 	 * @param string $return ("value": CSS Inline Content / "html": CSS Inline Content surrounded by tags)
 | ||
| 	 *
 | ||
| 	 * @return array
 | ||
| 	 */
 | ||
| 	public static function getInlineAssociatedWithLinkHandle($styleTagOrHandle, $wpacuRegisteredStyles, $from = 'tag', $return = 'value')
 | ||
| 	{
 | ||
| 		$styleExtraAfter = '';
 | ||
| 
 | ||
| 		if ($from === 'tag') {
 | ||
| 			preg_match_all('#data-wpacu-style-handle=([\'])' . '(.*)' . '(\1)#Usmi', $styleTagOrHandle, $outputMatches);
 | ||
| 			$styleHandle = (isset($outputMatches[2][0]) && $outputMatches[2][0]) ? trim($outputMatches[2][0], '"\'') : '';
 | ||
| 		} else {
 | ||
| 			$styleHandle = $styleTagOrHandle;
 | ||
| 		}
 | ||
| 
 | ||
| 		if ($return === 'value' && $styleHandle && isset($wpacuRegisteredStyles[$styleHandle]->extra)) {
 | ||
| 			$styleExtraArray = $wpacuRegisteredStyles[$styleHandle]->extra;
 | ||
| 
 | ||
| 			if (isset($styleExtraArray['after']) && ! empty($styleExtraArray['after'])) {
 | ||
| 				$styleExtraAfter .= "<style id='".$styleHandle."-inline-css' ".Misc::getStyleTypeAttribute().">\n";
 | ||
| 
 | ||
| 				foreach ($styleExtraArray['after'] as $afterData) {
 | ||
| 					if (! is_bool($afterData)) {
 | ||
| 						$styleExtraAfter .= $afterData."\n";
 | ||
| 					}
 | ||
| 				}
 | ||
| 
 | ||
| 				$styleExtraAfter .= '</style>';
 | ||
| 			}
 | ||
| 
 | ||
| 			return array('after' => $styleExtraAfter);
 | ||
| 		}
 | ||
| 
 | ||
| 		if ( $return === 'html' && $styleHandle ) {
 | ||
| 			// 'after' is the only one for inline CSS; there's no 'data' or 'before' like in the inline JS
 | ||
| 			return array('after' => self::generateInlineAssocHtmlForHandle($styleHandle));
 | ||
| 		}
 | ||
| 
 | ||
| 		return array('after' => array());
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $handle
 | ||
| 	 * @param $inlineStyleContent
 | ||
| 	 *
 | ||
| 	 * @return string
 | ||
| 	 */
 | ||
| 	public static function generateInlineAssocHtmlForHandle($handle, $inlineStyleContent = '')
 | ||
| 	{
 | ||
| 		global $wp_styles;
 | ||
| 
 | ||
| 		if ( ! $inlineStyleContent ) {
 | ||
| 			$inlineStyleContent = $wp_styles->print_inline_style( $handle, false );
 | ||
| 		}
 | ||
| 
 | ||
| 		$output = '';
 | ||
| 
 | ||
| 		if ( $inlineStyleContent ) {
 | ||
| 			$output = sprintf(
 | ||
| 				"<style id='%s-inline-css'%s>\n%s\n</style>",
 | ||
| 				esc_attr( $handle ),
 | ||
| 				Misc::getStyleTypeAttribute(),
 | ||
| 				$inlineStyleContent
 | ||
| 			);
 | ||
| 		}
 | ||
| 
 | ||
| 		return $output;
 | ||
| 	}
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * @param $htmlSource
 | ||
| 	 *
 | ||
| 	 * @return array|string|string[]
 | ||
| 	 */
 | ||
| 	public function appendNoScriptCertainLinkTags($htmlSource)
 | ||
| 	{
 | ||
| 		preg_match_all('#<link[^>]*(data-wpacu-preload-it-async)[^>]*(>)#Umi', $htmlSource, $matchesSourcesFromTags, PREG_SET_ORDER);
 | ||
| 
 | ||
| 		$noScripts = '';
 | ||
| 
 | ||
| 		if (! empty($matchesSourcesFromTags)) {
 | ||
| 			foreach ($matchesSourcesFromTags as $matchedValues) {
 | ||
| 				$matchedTag = $matchedValues[0];
 | ||
| 
 | ||
| 				$mediaAttrValue = Misc::getValueFromTag($matchedTag, 'media');
 | ||
| 				$hrefAttrValue  = Misc::getValueFromTag($matchedTag);
 | ||
| 
 | ||
| 				$noScripts .= '<noscript><link rel="stylesheet" href="'.$hrefAttrValue.'" media="'.$mediaAttrValue.'" /></noscript>'."\n";
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return str_replace(self::MOVE_NOSCRIPT_TO_BODY_FOR_CERTAIN_LINK_TAGS, $noScripts, $htmlSource);
 | ||
| 	}
 | ||
| 
 | ||
| 	}
 |