921 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			921 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace WpAssetCleanUp\OptimiseAssets;
 | |
| 
 | |
| use WpAssetCleanUp\Main;
 | |
| use WpAssetCleanUp\Menu;
 | |
| use WpAssetCleanUp\FileSystem;
 | |
| use WpAssetCleanUp\Misc;
 | |
| use WpAssetCleanUp\ObjectCache;
 | |
| 
 | |
| /**
 | |
|  * Class CombineJs
 | |
|  * @package WpAssetCleanUp\OptimiseAssets
 | |
|  */
 | |
| class CombineJs
 | |
| {
 | |
| 	/**
 | |
| 	 * @var string
 | |
| 	 */
 | |
| 	public static $jsonStorageFile = 'js-combined{maybe-extra-info}.json';
 | |
| 
 | |
| 	/**
 | |
| 	 * @param $htmlSource
 | |
| 	 *
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	public static function doCombine($htmlSource)
 | |
| 	{
 | |
| 		if ( ! Misc::isDOMDocumentOn() ) {
 | |
| 			return $htmlSource;
 | |
| 		}
 | |
| 
 | |
| 		if ( ! self::proceedWithJsCombine() ) {
 | |
| 			return $htmlSource;
 | |
| 		}
 | |
| 
 | |
| 		global $wp_scripts;
 | |
| 		$wpacuRegisteredScripts = $wp_scripts->registered;
 | |
| 
 | |
| 		$combineLevel = 2;
 | |
| 
 | |
| 		$isDeferAppliedOnBodyCombineGroupNo = false;
 | |
| 
 | |
| 		// $uriToFinalJsFile will always be relative ONLY within WP_CONTENT_DIR . self::getRelPathJsCacheDir()
 | |
| 		// which is usually "wp-content/cache/asset-cleanup/js/"
 | |
| 
 | |
| 		// "true" would make it avoid checking the cache and always use the DOM Parser / RegExp
 | |
| 		// for DEV purposes ONLY as it uses more resources
 | |
| 		$finalCacheList = array();
 | |
| 		$skipCache = false;
 | |
| 
 | |
| 		if (isset($_GET['wpacu_no_cache']) || (defined('WPACU_NO_CACHE') && WPACU_NO_CACHE === true)) {
 | |
| 			$skipCache = true;
 | |
| 		}
 | |
| 
 | |
| 		if (! $skipCache) {
 | |
| 			// Speed up processing by getting the already existing final CSS file URI
 | |
| 			// This will avoid parsing the HTML DOM and determine the combined URI paths for all the CSS files
 | |
| 			$finalCacheList = OptimizeCommon::getAssetCachedData( self::$jsonStorageFile, OptimizeJs::getRelPathJsCacheDir(), 'js' );
 | |
| 		}
 | |
| 
 | |
| 		if ( $skipCache || empty($finalCacheList) ) {
 | |
| 			/*
 | |
| 			 * NO CACHING TRANSIENT; Parse the DOM
 | |
| 			*/
 | |
| 			// Nothing in the database records or the retrieved cached file does not exist?
 | |
| 			OptimizeCommon::clearAssetCachedData(self::$jsonStorageFile);
 | |
| 
 | |
| 			$combinableList = array();
 | |
| 
 | |
| 			$jQueryMigrateInBody = false;
 | |
| 			$jQueryLibInBodyCount = 0;
 | |
| 
 | |
| 			$minifyJsInlineTagsIsNotEnabled = ! (MinifyJs::isMinifyJsEnabled() && in_array(Main::instance()->settings['minify_loaded_js_for'], array('inline', 'all')));
 | |
| 
 | |
| 			if ($minifyJsInlineTagsIsNotEnabled) {
 | |
| 				$domTag = Misc::initDOMDocument();
 | |
| 
 | |
| 				// Strip irrelevant tags to boost the speed of the parser (e.g. NOSCRIPT / SCRIPT(inline) / STYLE)
 | |
| 				// Sometimes, inline CODE can be too large, and it takes extra time for loadHTML() to parse
 | |
| 				$htmlSourceAlt = preg_replace( '@<script(| (type=(\'|"|)text/(javascript|template|html)(\'|"|)))>.*?</script>@si', '', $htmlSource );
 | |
| 				$htmlSourceAlt = preg_replace( '@<(style|noscript)[^>]*?>.*?</\\1>@si', '', $htmlSourceAlt );
 | |
| 				$htmlSourceAlt = preg_replace( '#<link([^<>]+)/?>#iU', '', $htmlSourceAlt );
 | |
| 
 | |
| 				if (Main::instance()->isFrontendEditView) {
 | |
| 					$htmlSourceAlt = preg_replace( '@<form action="#wpacu_wrap_assets" method="post">.*?</form>@si', '', $htmlSourceAlt );
 | |
| 				}
 | |
| 
 | |
| 				if ($htmlSourceAlt === '') {
 | |
| 					$htmlSourceAlt = $htmlSource;
 | |
| 				}
 | |
| 
 | |
| 				$domTag->loadHTML( $htmlSourceAlt );
 | |
| 			} else {
 | |
| 				$domTag = OptimizeCommon::getDomLoadedTag($htmlSource, 'combineJs');
 | |
| 			}
 | |
| 
 | |
| 			// Only keep combinable JS files
 | |
| 			foreach ( array( 'head', 'body' ) as $docLocationScript ) {
 | |
| 				$groupIndex = 1;
 | |
| 
 | |
| 				$docLocationElements = $domTag->getElementsByTagName($docLocationScript)->item(0);
 | |
| 				if ($docLocationElements === null) { continue; }
 | |
| 
 | |
| 				// High accuracy (e.g. it ignores tags inside HTML comments, conditional or not)
 | |
| 				$scriptTags = $docLocationElements->getElementsByTagName('script');
 | |
| 				if ($scriptTags === null) { continue; }
 | |
| 
 | |
| 				if ($docLocationScript && Main::instance()->settings['combine_loaded_js_defer_body']) {
 | |
| 					ObjectCache::wpacu_cache_set('wpacu_html_dom_body_tag_for_js', $docLocationElements);
 | |
| 				}
 | |
| 
 | |
| 				foreach ($scriptTags as $tagObject) {
 | |
| 					$scriptAttributes = array();
 | |
| 
 | |
| 					if ( isset($tagObject->attributes) && ! empty($tagObject->attributes) ) {
 | |
| 						foreach ( $tagObject->attributes as $attrObj ) {
 | |
| 							$scriptAttributes[ $attrObj->nodeName ] = trim( $attrObj->nodeValue );
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					$scriptNotCombinable = false; // default (usually, most of the SCRIPT tags can be optimized)
 | |
| 
 | |
| 					// Check if the CSS file has any 'data-wpacu-skip' attribute; if it does, do not alter it
 | |
| 					if (isset($scriptAttributes['data-wpacu-skip'])) {
 | |
| 						$scriptNotCombinable = true;
 | |
| 					}
 | |
| 
 | |
| 					$handleToCheck = isset($scriptAttributes['data-wpacu-script-handle']) ? $scriptAttributes['data-wpacu-script-handle'] : ''; // Maybe: JS Inline (Before, After)
 | |
| 					$hasSrc = isset($scriptAttributes['src']) && trim($scriptAttributes['src']); // No valid SRC attribute? It's not combinable (e.g. an inline tag)
 | |
| 					$isPluginScript = isset($scriptAttributes['data-wpacu-plugin-script']); // Only of the user is logged-in (skip it as it belongs to the Asset CleanUp (Pro) plugin)
 | |
| 
 | |
| 					if (! $scriptNotCombinable && (! $hasSrc || $isPluginScript)) {
 | |
| 						// Inline tag? Skip it in the BODY
 | |
| 						if ($docLocationScript === 'body') {
 | |
| 							continue;
 | |
| 						}
 | |
| 
 | |
| 						// Because of jQuery, we will not have the list of all inline scripts and then the combined files as it is in BODY
 | |
| 						if ($docLocationScript === 'head') {
 | |
| 							if ($handleToCheck === '' && isset($scriptAttributes['id'])) {
 | |
| 								$replaceToGetHandle = '';
 | |
| 								if (strpos($scriptAttributes['id'], '-js-extra') !== false)        { $replaceToGetHandle = '-js-extra';        }
 | |
| 								if (strpos($scriptAttributes['id'], '-js-before') !== false)       { $replaceToGetHandle = '-js-before';       }
 | |
| 								if (strpos($scriptAttributes['id'], '-js-after') !== false)        { $replaceToGetHandle = '-js-after';        }
 | |
| 								if (strpos($scriptAttributes['id'], '-js-translations') !== false) { $replaceToGetHandle = '-js-translations'; }
 | |
| 
 | |
| 								if ($replaceToGetHandle) {
 | |
| 									$handleToCheck = str_replace( $replaceToGetHandle, '', $scriptAttributes['id'] ); // Maybe: JS Inline (Data)
 | |
| 								}
 | |
| 								}
 | |
| 
 | |
| 							// Once an inline SCRIPT (with few exceptions below), except the ones associated with an enqueued script tag (with "src") is stumbled upon, a new combined group in the HEAD tag will be formed
 | |
| 							if ($handleToCheck && OptimizeCommon::appendInlineCodeToCombineAssetType('js')) {
 | |
| 								$getInlineAssociatedWithHandle = OptimizeJs::getInlineAssociatedWithScriptHandle($handleToCheck, $wpacuRegisteredScripts, 'handle');
 | |
| 
 | |
| 								if ( ($getInlineAssociatedWithHandle['data'] || $getInlineAssociatedWithHandle['before'] || $getInlineAssociatedWithHandle['after'])
 | |
| 								     || in_array(trim($tagObject->nodeValue), array($getInlineAssociatedWithHandle['data'], $getInlineAssociatedWithHandle['before'], $getInlineAssociatedWithHandle['after']))
 | |
| 								     || (strpos(trim($tagObject->nodeValue), '/* <![CDATA[ */') === 0 && Misc::endsWith(trim($tagObject->nodeValue), '/* ]]> */')) ) {
 | |
| 
 | |
| 									// It's associated with the enqueued scripts, or it's a (standalone) CDATA inline tag added via wp_localize_script()
 | |
| 									// Skip it instead and if the CDATA is not standalone (e.g. not associated with any script tag), the loop will "stay" in the same combined group
 | |
| 									continue;
 | |
| 								}
 | |
| 							}
 | |
| 
 | |
| 							$scriptNotCombinable = true;
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					$isInGroupType = 'standard';
 | |
| 					$isJQueryLib = $isJQueryMigrate = false;
 | |
| 
 | |
| 					// Has SRC and $isPluginScript is set to false OR it does not have "data-wpacu-skip" attribute
 | |
| 					if (! $scriptNotCombinable) {
 | |
| 						$src = (string)$scriptAttributes['src'];
 | |
| 
 | |
| 						if (self::skipCombine($src, $handleToCheck)) {
 | |
| 							$scriptNotCombinable = true;
 | |
| 						}
 | |
| 
 | |
| 						// Avoid any errors when code like the following one is used:
 | |
| 						// wp.i18n.setLocaleData( localeData, domain );
 | |
| 						// Because the inline JS is not appended to the combined JS, /wp-includes/js/dist/i18n.(min).js has to be called earlier (outside the combined JS file)
 | |
| 						if ( ! OptimizeCommon::appendInlineCodeToCombineAssetType('js') && (strpos($src, '/wp-includes/js/dist/i18n.') !== false) ) {
 | |
| 							$scriptNotCombinable = true;
 | |
| 						}
 | |
| 
 | |
| 						if (isset($scriptAttributes['data-wpacu-to-be-preloaded-basic']) && $scriptAttributes['data-wpacu-to-be-preloaded-basic']) {
 | |
| 							$scriptNotCombinable = true;
 | |
| 						}
 | |
| 
 | |
| 						// Was it optimized and has the URL updated? Check the Source URL
 | |
| 						if (! $scriptNotCombinable && isset($scriptAttributes['data-wpacu-script-rel-src-before']) && $scriptAttributes['data-wpacu-script-rel-src-before'] && self::skipCombine($scriptAttributes['data-wpacu-script-rel-src-before'], $handleToCheck)) {
 | |
| 							$scriptNotCombinable = true;
 | |
| 						}
 | |
| 
 | |
| 						$isJQueryLib     = isset($scriptAttributes['data-wpacu-jquery-core-handle']);
 | |
| 						$isJQueryMigrate = isset($scriptAttributes['data-wpacu-jquery-migrate-handle']);
 | |
| 
 | |
| 						if (isset($scriptAttributes['async'], $scriptAttributes['defer'])) { // Has both "async" and "defer"
 | |
| 							$isInGroupType = 'async_defer';
 | |
| 						} elseif (isset($scriptAttributes['async'])) { // Has only "async"
 | |
| 							$isInGroupType = 'async';
 | |
| 						} elseif (isset($scriptAttributes['defer'])) { // Has only "defer"
 | |
| 							// Does it have "defer" attribute, it's combinable (all checks were already done), loads in the BODY tag and "combine_loaded_js_defer_body" is ON? Keep it to the combination list
 | |
| 							$isCombinableWithBodyDefer = (! $scriptNotCombinable && $docLocationScript === 'body' && Main::instance()->settings['combine_loaded_js_defer_body']);
 | |
| 
 | |
| 							if (! $isCombinableWithBodyDefer) {
 | |
| 								$isInGroupType = 'defer'; // Otherwise, add it to the "defer" group type
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					if ( ! $scriptNotCombinable ) {
 | |
| 						// It also checks the domain name to make sure no external scripts would be added to the list
 | |
| 						if ( $localAssetPath = OptimizeCommon::getLocalAssetPath( $src, 'js' ) ) {
 | |
| 							$scriptExtra = array();
 | |
| 
 | |
| 							if ( isset( $scriptAttributes['data-wpacu-script-handle'], $wpacuRegisteredScripts[ $scriptAttributes['data-wpacu-script-handle'] ]->extra ) && OptimizeCommon::appendInlineCodeToCombineAssetType('js') ) {
 | |
| 								$scriptExtra = $wpacuRegisteredScripts[ $scriptAttributes['data-wpacu-script-handle'] ]->extra;
 | |
| 
 | |
| 								$anyScriptTranslations = method_exists('wp_scripts', 'print_translations')
 | |
| 									? wp_scripts()->print_translations( $scriptAttributes['data-wpacu-script-handle'], false )
 | |
| 									: false;
 | |
| 
 | |
| 								if ( $anyScriptTranslations ) {
 | |
| 									$scriptExtra['translations'] = $anyScriptTranslations;
 | |
| 								}
 | |
| 							}
 | |
| 
 | |
| 							// Standard (could be multiple groups per $docLocationScript), Async & Defer, Async, Defer
 | |
| 							$groupByType = ($isInGroupType === 'standard') ? $groupIndex : $isInGroupType;
 | |
| 
 | |
| 							if ($docLocationScript === 'body') {
 | |
| 								if ($isJQueryLib || strpos($localAssetPath, '/wp-includes/js/jquery/jquery.js') !== false) {
 | |
| 									$jQueryLibInBodyCount++;
 | |
| 								}
 | |
| 
 | |
| 								if ($isJQueryMigrate || strpos($localAssetPath, '/wp-includes/js/jquery/jquery-migrate') !== false) {
 | |
| 									$jQueryLibInBodyCount++;
 | |
| 									$jQueryMigrateInBody = true;
 | |
| 								}
 | |
| 							}
 | |
| 
 | |
| 							$combinableList[$docLocationScript][$groupByType][] = array(
 | |
| 								'src'   => $src,
 | |
| 								'local' => $localAssetPath,
 | |
| 								'info'  => array(
 | |
| 									'is_jquery'         => $isJQueryLib,
 | |
| 									'is_jquery_migrate' => $isJQueryMigrate
 | |
| 								),
 | |
| 								'extra' => $scriptExtra
 | |
| 							);
 | |
| 
 | |
| 							if ($docLocationScript === 'body' && $jQueryLibInBodyCount === 2) {
 | |
| 								$jQueryLibInBodyCount = 0; // reset it
 | |
| 								$groupIndex ++; // a new JS group will be created if jQuery & jQuery Migrate are combined in the BODY
 | |
| 								continue;
 | |
| 							}
 | |
| 						}
 | |
| 					} else {
 | |
| 						$groupIndex ++; // a new JS group will be created (applies to "standard" ones only)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Could be pages such as maintenance mode with no external JavaScript files
 | |
| 			if (empty($combinableList)) {
 | |
| 				return $htmlSource;
 | |
| 			}
 | |
| 
 | |
| 			$finalCacheList = array();
 | |
| 
 | |
| 			foreach ($combinableList as $docLocationScript => $combinableListGroups) {
 | |
| 				$groupNo = 1;
 | |
| 
 | |
| 				foreach ($combinableListGroups as $groupType => $groupFiles) {
 | |
| 					// Any groups having one file? Then it's not really a group and the file should load on its own
 | |
| 					// Could be one extra file besides the jQuery & jQuery Migrate group or the only JS file called within the HEAD
 | |
| 					if (count($groupFiles) < 2) {
 | |
| 						continue;
 | |
| 					}
 | |
| 
 | |
| 					$localAssetsPaths = $groupScriptSrcs = array();
 | |
| 					$localAssetsExtra = array();
 | |
| 					$jQueryIsIncludedInGroup = false;
 | |
| 
 | |
| 					foreach ($groupFiles as $groupFileData) {
 | |
| 						if ($groupFileData['info']['is_jquery'] || strpos($groupFileData['local'], '/wp-includes/js/jquery/jquery.js') !== false) {
 | |
| 							$jQueryIsIncludedInGroup = true;
 | |
| 
 | |
| 							// Is jQuery in the BODY without jQuery Migrate loaded?
 | |
| 							// Isolate it as it needs to be the first to load in case there are inline scripts calling it before the combined group(s)
 | |
| 							if ($docLocationScript === 'body' && ! $jQueryMigrateInBody) {
 | |
| 								continue;
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						$src                    = $groupFileData['src'];
 | |
| 						$groupScriptSrcs[]      = $src;
 | |
| 						$localAssetsPaths[$src] = $groupFileData['local'];
 | |
| 						$localAssetsExtra[$src] = $groupFileData['extra'];
 | |
| 					}
 | |
| 
 | |
| 					$maybeDoJsCombine = self::maybeDoJsCombine(
 | |
| 						$localAssetsPaths,
 | |
| 						$localAssetsExtra,
 | |
| 						$docLocationScript
 | |
| 					);
 | |
| 
 | |
| 					// Local path to combined CSS file
 | |
| 					$localFinalJsFile = $maybeDoJsCombine['local_final_js_file'];
 | |
| 
 | |
| 					// URI (e.g. /wp-content/cache/asset-cleanup/[file-name-here.js]) to the combined JS file
 | |
| 					$uriToFinalJsFile = $maybeDoJsCombine['uri_final_js_file'];
 | |
| 
 | |
| 					if (! is_file($localFinalJsFile)) {
 | |
| 						return $htmlSource; // something is not right as the file wasn't created, we will return the original HTML source
 | |
| 					}
 | |
| 
 | |
| 					$groupScriptSrcsFilter = array_map(static function($src) {
 | |
| 						$src = str_replace(site_url(), '', $src);
 | |
| 						// Starts with // (protocol is missing) - the replacement above wasn't made
 | |
| 						if (strpos($src, '//') === 0) {
 | |
| 							$siteUrlNoProtocol = str_replace(array('http:', 'https:'), '', site_url());
 | |
| 							return str_replace($siteUrlNoProtocol, '', $src);
 | |
| 						}
 | |
| 						return $src;
 | |
| 					}, $groupScriptSrcs);
 | |
| 
 | |
| 					$finalCacheList[$docLocationScript][$groupNo] = array(
 | |
| 						'uri_to_final_js_file' => $uriToFinalJsFile,
 | |
| 						'script_srcs'          => $groupScriptSrcsFilter
 | |
| 					);
 | |
| 
 | |
| 					if (in_array($groupType, array('async_defer', 'async', 'defer'))) {
 | |
| 						if ($groupType === 'async_defer') {
 | |
| 							$finalCacheList[$docLocationScript][$groupNo]['extra_attributes'][] = 'async';
 | |
| 							$finalCacheList[$docLocationScript][$groupNo]['extra_attributes'][] = 'defer';
 | |
| 						} else {
 | |
| 							$finalCacheList[$docLocationScript][$groupNo]['extra_attributes'][] = $groupType;
 | |
| 						}
 | |
| 					}
 | |
| 
 | |
| 					// Apply 'defer="defer"' to combined JS files from the BODY tag (if enabled), except the combined jQuery & jQuery Migrate Group
 | |
| 					if ($docLocationScript === 'body' && ! $jQueryIsIncludedInGroup && Main::instance()->settings['combine_loaded_js_defer_body']) {
 | |
| 						if ($isDeferAppliedOnBodyCombineGroupNo === false) {
 | |
| 							// Only record the first one
 | |
| 							$isDeferAppliedOnBodyCombineGroupNo = $groupNo;
 | |
| 						}
 | |
| 
 | |
| 						$finalCacheList[$docLocationScript][$groupNo]['extra_attributes'][] = 'defer';
 | |
| 					}
 | |
| 
 | |
| 					$groupNo ++;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			OptimizeCommon::setAssetCachedData(self::$jsonStorageFile, OptimizeJs::getRelPathJsCacheDir(), wp_json_encode($finalCacheList));
 | |
| 		}
 | |
| 
 | |
| 		if (! empty($finalCacheList)) {
 | |
| 			$cdnUrls = OptimizeCommon::getAnyCdnUrls();
 | |
| 			$cdnUrlForJs = isset($cdnUrls['js']) ? $cdnUrls['js'] : false;
 | |
| 
 | |
| 			foreach ( $finalCacheList as $docLocationScript => $cachedGroupsList ) {
 | |
| 				foreach ($cachedGroupsList as $groupNo => $cachedValues) {
 | |
| 					$htmlSourceBeforeGroupReplacement = $htmlSource;
 | |
| 
 | |
| 					$uriToFinalJsFile = $cachedValues['uri_to_final_js_file'];
 | |
| 					$filesSources = $cachedValues['script_srcs'];
 | |
| 
 | |
| 					// Basic Combining (1) -> replace "first" tag with the final combination tag (there would be most likely multiple groups)
 | |
| 					// Enhanced Combining (2) -> replace "last" tag with the final combination tag (most likely one group)
 | |
| 					$indexReplacement = ($combineLevel === 2) ? (count($filesSources) - 1) : 0;
 | |
| 
 | |
| 					$finalTagUrl = OptimizeCommon::filterWpContentUrl($cdnUrlForJs) . OptimizeJs::getRelPathJsCacheDir() . $uriToFinalJsFile;
 | |
| 
 | |
| 					$finalJsTagAttrsOutput = '';
 | |
| 					$extraAttrs = array();
 | |
| 
 | |
| 					if (isset($cachedValues['extra_attributes']) && ! empty($cachedValues['extra_attributes'])) {
 | |
| 						$extraAttrs = $cachedValues['extra_attributes'];
 | |
| 						foreach ($extraAttrs as $finalJsTagAttr) {
 | |
| 							$finalJsTagAttrsOutput .= ' '.$finalJsTagAttr.'=\''.$finalJsTagAttr.'\' ';
 | |
| 						}
 | |
| 						$finalJsTagAttrsOutput = trim($finalJsTagAttrsOutput);
 | |
| 					}
 | |
| 
 | |
| 					// No async or defer? Add the preloading for the combined JS from the BODY
 | |
| 					if ( ! $finalJsTagAttrsOutput && $docLocationScript === 'body' ) {
 | |
| 						$finalJsTagAttrsOutput = ' data-wpacu-to-be-preloaded-basic=\'1\' ';
 | |
| 						if ( ! defined('WPACU_REAPPLY_PRELOADING_FOR_COMBINED_JS') ) { define('WPACU_REAPPLY_PRELOADING_FOR_COMBINED_JS', true); }
 | |
| 					}
 | |
| 
 | |
| 					// e.g. For developers that might want to add custom attributes such as data-cfasync="false"
 | |
| 					$finalJsTag = apply_filters(
 | |
| 						'wpacu_combined_js_tag',
 | |
| 						'<script '.$finalJsTagAttrsOutput.' '.Misc::getScriptTypeAttribute().' id=\'wpacu-combined-js-'.$docLocationScript.'-group-'.$groupNo.'\' src=\''.$finalTagUrl.'\'></script>',
 | |
| 						array(
 | |
| 							'attrs'        => $extraAttrs,
 | |
| 							'doc_location' => $docLocationScript,
 | |
| 							'group_no'     => $groupNo,
 | |
| 							'src'          => $finalTagUrl
 | |
| 						)
 | |
| 					);
 | |
| 
 | |
| 					// Reference: https://stackoverflow.com/questions/2368539/php-replacing-multiple-spaces-with-a-single-space
 | |
| 					$finalJsTag = preg_replace('!\s+!', ' ', $finalJsTag);
 | |
| 
 | |
| 					$scriptTagsStrippedNo = 0;
 | |
| 
 | |
| 					$scriptTags = OptimizeJs::getScriptTagsFromSrcs($filesSources, $htmlSource);
 | |
| 
 | |
| 					foreach ($scriptTags as $groupScriptTagIndex => $scriptTag) {
 | |
| 						$replaceWith = ($groupScriptTagIndex === $indexReplacement) ? $finalJsTag : '';
 | |
| 						$htmlSourceBeforeTagReplacement = $htmlSource;
 | |
| 
 | |
| 						// 1) Strip any inline code associated with the tag
 | |
| 						// 2) Finally, strip the actual tag
 | |
| 						$htmlSource = self::stripTagAndAnyInlineAssocCode( $scriptTag, $wpacuRegisteredScripts, $replaceWith, $htmlSource );
 | |
| 
 | |
| 						if ($htmlSource !== $htmlSourceBeforeTagReplacement) {
 | |
| 							$scriptTagsStrippedNo ++;
 | |
| 						}
 | |
| 						}
 | |
| 
 | |
| 					// At least two tags have to be stripped from the group to consider doing the group replacement
 | |
| 					// If the tags weren't replaced it's likely there were changes to their structure after they were cached for the group merging
 | |
| 					if (count($filesSources) !== $scriptTagsStrippedNo) {
 | |
| 						$htmlSource = $htmlSourceBeforeGroupReplacement;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Only relevant if "Defer loading JavaScript combined files from <body>" in "Settings" - "Combine CSS & JS Files" - "Combine loaded JS (JavaScript) into fewer files"
 | |
| 		// and there is at least one combined deferred tag
 | |
| 
 | |
| 		if (isset($finalCacheList['body']) && (! empty($finalCacheList['body'])) && Main::instance()->settings['combine_loaded_js_defer_body']) {
 | |
| 			// CACHE RE-BUILT
 | |
| 			if ($isDeferAppliedOnBodyCombineGroupNo > 0 && $domTag = ObjectCache::wpacu_cache_get('wpacu_html_dom_body_tag_for_js')) {
 | |
| 				$strPart = "id='wpacu-combined-js-body-group-".$isDeferAppliedOnBodyCombineGroupNo."' ";
 | |
| 
 | |
| 				if (strpos($htmlSource, $strPart) === false) {
 | |
| 					return $htmlSource; // something is funny, do not continue
 | |
| 				}
 | |
| 
 | |
| 				list(,$htmlAfterFirstCombinedDeferScript) = explode($strPart, $htmlSource);
 | |
| 				$htmlAfterFirstCombinedDeferScriptMaybeChanged = $htmlAfterFirstCombinedDeferScript;
 | |
| 				$scriptTags = $domTag->getElementsByTagName('script');
 | |
| 			} else {
 | |
| 				// FROM THE CACHE
 | |
| 				foreach ($finalCacheList['body'] as $bodyCombineGroupNo => $values) {
 | |
| 					if (isset($values['extra_attributes']) && in_array('defer', $values['extra_attributes'])) {
 | |
| 						$isDeferAppliedOnBodyCombineGroupNo = $bodyCombineGroupNo;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if (! $isDeferAppliedOnBodyCombineGroupNo) {
 | |
| 					// Not applicable to any combined group
 | |
| 					return $htmlSource;
 | |
| 				}
 | |
| 
 | |
| 				$strPart = 'id=\'wpacu-combined-js-body-group-'.$isDeferAppliedOnBodyCombineGroupNo.'\'';
 | |
| 
 | |
| 				$htmlAfterFirstCombinedDeferScriptMaybeChanged = false;
 | |
| 
 | |
| 				if (strpos($htmlSource, $strPart) !== false) {
 | |
| 					list( , $htmlAfterFirstCombinedDeferScript ) = explode( $strPart, $htmlSource );
 | |
| 					$htmlAfterFirstCombinedDeferScriptMaybeChanged = $htmlAfterFirstCombinedDeferScript;
 | |
| 				}
 | |
| 
 | |
| 				// It means to combine took place for any reason (e.g. only one JS file loaded in the HEAD and one in the BODY)
 | |
| 				if (! isset($htmlAfterFirstCombinedDeferScript)) {
 | |
| 					return $htmlSource;
 | |
| 				}
 | |
| 
 | |
| 				$domTag = Misc::initDOMDocument();
 | |
| 
 | |
| 				// Strip irrelevant tags to boost the speed of the parser (e.g. NOSCRIPT / SCRIPT(inline) / STYLE)
 | |
| 				// Sometimes, inline CODE can be too large, and it takes extra time for loadHTML() to parse
 | |
| 				$htmlSourceAlt = preg_replace( '@<script(| type=\'text/javascript\'| type="text/javascript")>.*?</script>@si', '', $htmlAfterFirstCombinedDeferScript );
 | |
| 				$htmlSourceAlt = preg_replace( '@<(style|noscript)[^>]*?>.*?</\\1>@si', '', $htmlSourceAlt );
 | |
| 				$htmlSourceAlt = preg_replace( '#<link([^<>]+)/?>#iU', '', $htmlSourceAlt );
 | |
| 
 | |
| 				if (Main::instance()->isFrontendEditView) {
 | |
| 					$htmlSourceAlt = preg_replace( '@<form action="#wpacu_wrap_assets" method="post">.*?</form>@si', '', $htmlSourceAlt );
 | |
| 				}
 | |
| 
 | |
| 				// No other SCRIPT left, stop here in this case
 | |
| 				if (strpos($htmlSourceAlt, '<script') === false) {
 | |
| 					return $htmlSource;
 | |
| 				}
 | |
| 
 | |
| 				$domTag->loadHTML( $htmlSourceAlt );
 | |
| 				$scriptTags = $domTag->getElementsByTagName('script');
 | |
| 			}
 | |
| 
 | |
| 			if ( $scriptTags === null ) {
 | |
| 				return $htmlSource;
 | |
| 			}
 | |
| 
 | |
| 			foreach ($scriptTags as $tagObject) {
 | |
| 				if (empty($tagObject->attributes)) { continue; }
 | |
| 
 | |
| 				$scriptAttributes = array();
 | |
| 
 | |
| 				foreach ( $tagObject->attributes as $attrObj ) {
 | |
| 					$scriptAttributes[ $attrObj->nodeName ] = trim( $attrObj->nodeValue );
 | |
| 				}
 | |
| 
 | |
| 				// No "src" attribute? Skip it (most likely an inline script tag)
 | |
| 				if (! (isset($scriptAttributes['src']) && $scriptAttributes['src'])) {
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				// Skip it as "defer" is already set
 | |
| 				if (isset($scriptAttributes['defer'])) {
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				// Has "src" attribute and "defer" is not applied? Add it
 | |
| 				if ($htmlAfterFirstCombinedDeferScriptMaybeChanged !== false) {
 | |
| 					$htmlAfterFirstCombinedDeferScriptMaybeChanged = trim( preg_replace(
 | |
| 						'#\ssrc(\s+|)=(\s+|)(|"|\'|\s+)(' . preg_quote( $scriptAttributes['src'], '/' ) . ')(\3)#si',
 | |
| 						' src=\3\4\3 defer=\'defer\'',
 | |
| 						$htmlAfterFirstCombinedDeferScriptMaybeChanged
 | |
| 					) );
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if ($htmlAfterFirstCombinedDeferScriptMaybeChanged && $htmlAfterFirstCombinedDeferScriptMaybeChanged !== $htmlAfterFirstCombinedDeferScript) {
 | |
| 				$htmlSource = str_replace($htmlAfterFirstCombinedDeferScript, $htmlAfterFirstCombinedDeferScriptMaybeChanged, $htmlSource);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		libxml_clear_errors();
 | |
| 
 | |
| 		// Finally, return the HTML source
 | |
| 		return $htmlSource;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param $src
 | |
| 	 *
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	public static function skipCombine($src, $handle = '')
 | |
| 	{
 | |
| 		// In case the handle was appended
 | |
| 		if ($handle !== '' && in_array($handle, Main::instance()->skipAssets['scripts'])) {
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		$regExps = array(
 | |
| 			'#/wp-content/bs-booster-cache/#'
 | |
| 		);
 | |
| 
 | |
| 		if (Main::instance()->settings['combine_loaded_js_exceptions'] !== '') {
 | |
| 			$loadedJsExceptionsPatterns = trim(Main::instance()->settings['combine_loaded_js_exceptions']);
 | |
| 
 | |
| 			if (strpos($loadedJsExceptionsPatterns, "\n")) {
 | |
| 				// Multiple values (one per line)
 | |
| 				foreach (explode("\n", $loadedJsExceptionsPatterns) as $loadedJsExceptionsPattern) {
 | |
| 					$regExps[] = '#'.trim($loadedJsExceptionsPattern).'#';
 | |
| 				}
 | |
| 			} else {
 | |
| 				// Only one value?
 | |
| 				$regExps[] = '#'.trim($loadedJsExceptionsPatterns).'#';
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// No exceptions set? Do not skip combination
 | |
| 		if (empty($regExps)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		foreach ($regExps as $regExp) {
 | |
| 			$regExp = Misc::purifyRegexValue($regExp);
 | |
| 
 | |
| 			if ( @preg_match( $regExp, $src ) || ( strpos($src, $regExp) !== false ) ) {
 | |
| 				// Skip combination
 | |
| 				return true;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param $localAssetsPaths
 | |
| 	 * @param $localAssetsExtra
 | |
| 	 * @param $docLocationScript
 | |
| 	 *
 | |
| 	 * @return array
 | |
| 	 */
 | |
| 	public static function maybeDoJsCombine($localAssetsPaths, $localAssetsExtra, $docLocationScript)
 | |
| 	{
 | |
| 		// Only combine if $shaOneCombinedUriPaths.js does not exist
 | |
| 		// If "?ver" value changes on any of the assets or the asset list changes in any way
 | |
| 		// then $shaOneCombinedUriPaths will change too and a new JS file will be generated and loaded
 | |
| 
 | |
| 		// Change $assetsContents as paths to fonts and images that are relative (e.g. ../, ../../) have to be updated
 | |
| 		$uriToFinalJsFile = $localFinalJsFile = $finalJsContents = '';
 | |
| 
 | |
| 		foreach ($localAssetsPaths as $assetHref => $localAssetsPath) {
 | |
| 			if ($jsContent = trim(FileSystem::fileGetContents($localAssetsPath))) {
 | |
| 				// Does it have a source map? Strip it
 | |
| 				if (strpos($jsContent, '//# sourceMappingURL=') !== false) {
 | |
| 					$jsContent = OptimizeCommon::stripSourceMap($jsContent, 'js');
 | |
| 				}
 | |
| 
 | |
| 				$pathToAssetDir = OptimizeCommon::getPathToAssetDir($assetHref);
 | |
| 
 | |
| 				$contentToAddToCombinedFile = '';
 | |
| 
 | |
| 				if (apply_filters('wpacu_print_info_comments_in_cached_assets', true)) {
 | |
| 					$contentToAddToCombinedFile = '/*!' . str_replace( Misc::getWpRootDirPath(), '/', $localAssetsPath ) . "*/\n";
 | |
| 				}
 | |
| 
 | |
| 				// This includes the extra from 'data' (CDATA added via wp_localize_script()) & 'before' as they are both printed BEFORE the SCRIPT tag
 | |
| 				$contentToAddToCombinedFile .= self::maybeWrapBetweenTryCatch(self::appendToCombineJs('translations', $localAssetsExtra, $assetHref, $pathToAssetDir), $assetHref);
 | |
| 				$contentToAddToCombinedFile .= self::maybeWrapBetweenTryCatch(self::appendToCombineJs('before', $localAssetsExtra, $assetHref, $pathToAssetDir), $assetHref);
 | |
| 				$contentToAddToCombinedFile .= self::maybeWrapBetweenTryCatch(OptimizeJs::maybeDoJsFixes($jsContent, $pathToAssetDir . '/'), $assetHref) . "\n";
 | |
| 				// This includes the inline 'after' the SCRIPT tag
 | |
| 				$contentToAddToCombinedFile .= self::maybeWrapBetweenTryCatch(self::appendToCombineJs('after', $localAssetsExtra, $assetHref, $pathToAssetDir), $assetHref);
 | |
| 
 | |
| 				$finalJsContents .= $contentToAddToCombinedFile;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if ($finalJsContents !== '') {
 | |
| 			$finalJsContents = trim($finalJsContents);
 | |
| 			$shaOneForCombinedJs = sha1($finalJsContents);
 | |
| 
 | |
| 			$uriToFinalJsFile = $docLocationScript . '-' . $shaOneForCombinedJs . '.js';
 | |
| 			$localFinalJsFile  = WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir() . $uriToFinalJsFile;
 | |
| 
 | |
| 			if (! is_file($localFinalJsFile)) {
 | |
| 				FileSystem::filePutContents( $localFinalJsFile, $finalJsContents );
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return array(
 | |
| 			'uri_final_js_file'   => $uriToFinalJsFile,
 | |
| 			'local_final_js_file' => $localFinalJsFile
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param $addItLocation
 | |
| 	 * @param $localAssetsExtra
 | |
| 	 * @param $assetHref
 | |
| 	 * @param $pathToAssetDir
 | |
| 	 *
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	public static function appendToCombineJs($addItLocation, $localAssetsExtra, $assetHref, $pathToAssetDir)
 | |
| 	{
 | |
| 		$extraContentToAppend = '';
 | |
| 		$doJsMinifyInline = MinifyJs::isMinifyJsEnabled() && in_array(Main::instance()->settings['minify_loaded_js_for'], array('inline', 'all'));
 | |
| 
 | |
| 		if ($addItLocation === 'before') {
 | |
| 			// [Before JS Content]
 | |
| 			if (isset($localAssetsExtra[$assetHref]['data']) && ($dataValue = $localAssetsExtra[$assetHref]['data'])) {
 | |
| 				$extraContentToAppend = '';
 | |
| 				if (self::isInlineJsCombineable($dataValue) && trim($dataValue) !== '') {
 | |
| 					$cData = $doJsMinifyInline ? MinifyJs::applyMinification( $dataValue ) : $dataValue;
 | |
| 					$cData = OptimizeJs::maybeDoJsFixes( $cData, $pathToAssetDir . '/' );
 | |
| 					$extraContentToAppend .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [inline: cdata] */' : '';
 | |
| 					$extraContentToAppend .= $cData;
 | |
| 					$extraContentToAppend .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [/inline: cdata] */' : '';
 | |
| 					$extraContentToAppend .= "\n";
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (isset($localAssetsExtra[$assetHref]['before']) && ! empty($localAssetsExtra[$assetHref]['before'])) {
 | |
| 				$inlineBeforeJsData = '';
 | |
| 
 | |
| 				foreach ($localAssetsExtra[$assetHref]['before'] as $beforeData) {
 | |
| 					if (! is_bool($beforeData) && self::isInlineJsCombineable($beforeData)) {
 | |
| 						$inlineBeforeJsData .= $beforeData . "\n";
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if (trim($inlineBeforeJsData)) {
 | |
| 					$inlineBeforeJsData = OptimizeJs::maybeAlterContentForInlineScriptTag( $inlineBeforeJsData, $doJsMinifyInline );
 | |
| 					$inlineBeforeJsData = OptimizeJs::maybeDoJsFixes( $inlineBeforeJsData, $pathToAssetDir . '/' );
 | |
| 					$extraContentToAppend .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [inline: before] */' : '';
 | |
| 					$extraContentToAppend .= $inlineBeforeJsData;
 | |
| 					$extraContentToAppend .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [/inline: before] */' : '';
 | |
| 					$extraContentToAppend .= "\n";
 | |
| 				}
 | |
| 			}
 | |
| 			// [/Before JS Content]
 | |
| 		} elseif ($addItLocation === 'after') {
 | |
| 			// [After JS Content]
 | |
| 			if (isset($localAssetsExtra[$assetHref]['after']) && ! empty($localAssetsExtra[$assetHref]['after'])) {
 | |
| 				$inlineAfterJsData = '';
 | |
| 
 | |
| 				foreach ($localAssetsExtra[$assetHref]['after'] as $afterData) {
 | |
| 					if (! is_bool($afterData) && self::isInlineJsCombineable($afterData)) {
 | |
| 						$inlineAfterJsData .= $afterData."\n";
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if ( trim($inlineAfterJsData) ) {
 | |
| 					$inlineAfterJsData = OptimizeJs::maybeAlterContentForInlineScriptTag( $inlineAfterJsData, $doJsMinifyInline );
 | |
| 					$inlineAfterJsData = OptimizeJs::maybeDoJsFixes( $inlineAfterJsData, $pathToAssetDir . '/' );
 | |
| 					$extraContentToAppend .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [inline: after] */' : '';
 | |
| 					$extraContentToAppend .= $inlineAfterJsData;
 | |
| 					$extraContentToAppend .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [/inline: after] */' : '';
 | |
| 					$extraContentToAppend .= "\n";
 | |
| 				}
 | |
| 			}
 | |
| 			// [/After JS Content]
 | |
| 		} elseif ($addItLocation === 'translations' && isset($localAssetsExtra[$assetHref]['translations']) && $localAssetsExtra[$assetHref]['translations']) {
 | |
| 			$inlineAfterJsData = OptimizeJs::maybeAlterContentForInlineScriptTag( $localAssetsExtra[$assetHref]['translations'], $doJsMinifyInline );
 | |
| 			$inlineAfterJsData = OptimizeJs::maybeDoJsFixes( $inlineAfterJsData, $pathToAssetDir . '/' );
 | |
| 			$extraContentToAppend .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [inline: translations] */' : '';
 | |
| 			$extraContentToAppend .= $inlineAfterJsData;
 | |
| 			$extraContentToAppend .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [/inline: translations] */' : '';
 | |
| 			$extraContentToAppend .= "\n";
 | |
| 		}
 | |
| 
 | |
| 		return $extraContentToAppend;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param $jsCode
 | |
| 	 * @param $sourceUrl
 | |
| 	 *
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	public static function maybeWrapBetweenTryCatch($jsCode, $sourceUrl)
 | |
| 	{
 | |
| 		if ($jsCode && Main::instance()->settings['combine_loaded_js_try_catch']) {
 | |
| 			return <<<JS
 | |
| try {
 | |
| 	{$jsCode}
 | |
| } catch (err) {
 | |
| 	console.log("Asset CleanUp - There is a JavaScript error related to the following source: {$sourceUrl} - Error: " + err.message); 
 | |
| }
 | |
| JS;
 | |
| 		}
 | |
| 
 | |
| 		return $jsCode;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param $scriptTag
 | |
| 	 * @param $wpacuRegisteredScripts
 | |
| 	 * @param $replaceWith
 | |
| 	 * @param $htmlSource
 | |
| 	 *
 | |
| 	 * @return mixed
 | |
| 	 */
 | |
| 	public static function stripTagAndAnyInlineAssocCode($scriptTag, $wpacuRegisteredScripts, $replaceWith, $htmlSource)
 | |
| 	{
 | |
| 		if (OptimizeCommon::appendInlineCodeToCombineAssetType('js')) {
 | |
| 			$scriptExtrasValue      = OptimizeJs::getInlineAssociatedWithScriptHandle($scriptTag, $wpacuRegisteredScripts, 'tag', 'value');
 | |
| 
 | |
| 			$scriptExtraTranslationsValue  = (isset($scriptExtrasValue['translations']) && $scriptExtrasValue['translations']) ? $scriptExtrasValue['translations']   : '';
 | |
| 			$scriptExtraCdataValue         = (isset($scriptExtrasValue['data'])   && $scriptExtrasValue['data'])   ? $scriptExtrasValue['data']   : '';
 | |
| 			$scriptExtraBeforeValue        = (isset($scriptExtrasValue['before']) && $scriptExtrasValue['before']) ? $scriptExtrasValue['before'] : '';
 | |
| 			$scriptExtraAfterValue         = (isset($scriptExtrasValue['after'])  && $scriptExtrasValue['after'])  ? $scriptExtrasValue['after']  : '';
 | |
| 
 | |
| 			$scriptExtrasHtml       = OptimizeJs::getInlineAssociatedWithScriptHandle($scriptTag, $wpacuRegisteredScripts, 'tag', 'html');
 | |
| 			preg_match_all('#data-wpacu-script-handle=([\'])' . '(.*)' . '(\1)#Usmi', $scriptTag, $outputMatches);
 | |
| 			$scriptHandle = (isset($outputMatches[2][0]) && $outputMatches[2][0]) ? trim($outputMatches[2][0], '"\'') : '';
 | |
| 
 | |
| 			$scriptExtraTranslationsHtml = (isset($scriptExtrasHtml['translations']) && $scriptExtrasHtml['translations']) ? $scriptExtrasHtml['translations'] : '';
 | |
| 			$scriptExtraCdataHtml  = (isset($scriptExtrasHtml['data'])   && $scriptExtrasHtml['data'])   ? $scriptExtrasHtml['data']   : '';
 | |
| 			$scriptExtraBeforeHtml = (isset($scriptExtrasHtml['before']) && $scriptExtrasHtml['before']) ? $scriptExtrasHtml['before'] : '';
 | |
| 			$scriptExtraAfterHtml  = (isset($scriptExtrasHtml['after'])  && $scriptExtrasHtml['after'])  ? $scriptExtrasHtml['after']  : '';
 | |
| 
 | |
| 			if ($scriptExtraTranslationsValue || $scriptExtraCdataValue || $scriptExtraBeforeValue || $scriptExtraAfterValue) {
 | |
| 				if ( $scriptExtraCdataValue && self::isInlineJsCombineable($scriptExtraCdataValue) ) {
 | |
| 					$htmlSource = str_replace($scriptExtraCdataHtml, '', $htmlSource );
 | |
| 				}
 | |
| 
 | |
| 				if ($scriptExtraTranslationsValue) {
 | |
| 					$repsBefore = array(
 | |
| 						$scriptExtraTranslationsHtml => '',
 | |
| 						str_replace( '<script ', '<script data-wpacu-script-handle=\'' . $scriptHandle . '\' ', $scriptExtraTranslationsHtml ) => '',
 | |
| 						'>'."\n".$scriptExtraTranslationsValue."\n".'</script>' => '></script>',
 | |
| 						$scriptExtraTranslationsValue."\n" => ''
 | |
| 					);
 | |
| 					$htmlSource = str_replace(array_keys($repsBefore), array_values($repsBefore), $htmlSource );
 | |
| 				}
 | |
| 
 | |
| 				if ($scriptExtraBeforeValue && self::isInlineJsCombineable($scriptExtraBeforeValue)) {
 | |
| 					$repsBefore = array(
 | |
| 						$scriptExtraBeforeHtml => '',
 | |
| 						str_replace( '<script ', '<script data-wpacu-script-handle=\'' . $scriptHandle . '\' ', $scriptExtraBeforeHtml ) => '',
 | |
| 						'>'."\n".$scriptExtraBeforeValue."\n".'</script>' => '></script>',
 | |
| 						$scriptExtraBeforeValue."\n" => ''
 | |
| 					);
 | |
| 					$htmlSource = str_replace(array_keys($repsBefore), array_values($repsBefore), $htmlSource );
 | |
| 				}
 | |
| 
 | |
| 				if ($scriptExtraAfterValue && self::isInlineJsCombineable($scriptExtraAfterValue)) {
 | |
| 					$repsBefore = array(
 | |
| 						$scriptExtraAfterHtml => '',
 | |
| 						str_replace( '<script ', '<script data-wpacu-script-handle=\'' . $scriptHandle . '\' ', $scriptExtraAfterHtml ) => '',
 | |
| 						'>'."\n".$scriptExtraAfterValue."\n".'</script>' => '></script>',
 | |
| 						$scriptExtraAfterValue."\n" => ''
 | |
| 					);
 | |
| 					$htmlSource = str_replace(array_keys($repsBefore), array_values($repsBefore), $htmlSource);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Finally, strip/replace the tag
 | |
| 		return str_replace( array($scriptTag."\n", $scriptTag), $replaceWith, $htmlSource );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * This is to prevent certain inline JS to be appended to the combined JS files in order to avoid lots of disk space (sometimes a few GB) of JS combined files
 | |
| 	 *
 | |
| 	 * @param $jsInlineValue
 | |
| 	 *
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	public static function isInlineJsCombineable($jsInlineValue)
 | |
| 	{
 | |
| 		// The common WordPress nonce
 | |
| 		if (strpos($jsInlineValue, 'nonce') !== false) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		// WooCommerce Cart Fragments
 | |
| 		if (strpos($jsInlineValue, 'wc_cart_hash_') !== false && strpos($jsInlineValue, 'cart_hash_key') !== false) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		if (substr(trim($jsInlineValue), 0, 1) === '{' && substr(trim($jsInlineValue), -1, 1) === '}') {
 | |
| 			json_decode($jsInlineValue);
 | |
| 
 | |
| 			if (json_last_error() === JSON_ERROR_NONE) {
 | |
| 				return false; // it's a JSON format (e.g. type="application/json" from "wordpress-popular-posts" plugin)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return true; // default
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	public static function proceedWithJsCombine()
 | |
| 	{
 | |
| 		// not on query string request (debugging purposes)
 | |
| 		if ( isset($_REQUEST['wpacu_no_js_combine']) ) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		// No JS files are combined in the Dashboard
 | |
| 		// Always in the front-end view
 | |
| 		// Do not combine if there's a POST request as there could be assets loading conditionally
 | |
| 		// that might not be needed when the page is accessed without POST, making the final JS file larger
 | |
| 		if (! empty($_POST) || is_admin()) {
 | |
| 			return false; // Do not combine
 | |
| 		}
 | |
| 
 | |
| 		// Only clean request URIs allowed (with few exceptions)
 | |
| 		if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
 | |
| 			// Exceptions
 | |
| 			if (! OptimizeCommon::loadOptimizedAssetsIfQueryStrings()) {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (! OptimizeCommon::doCombineIsRegularPage()) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		$pluginSettings = Main::instance()->settings;
 | |
| 
 | |
| 		if ($pluginSettings['test_mode'] && ! Menu::userCanManageAssets()) {
 | |
| 			return false; // Do not combine anything if "Test Mode" is ON
 | |
| 		}
 | |
| 
 | |
| 		if ($pluginSettings['combine_loaded_js'] === '') {
 | |
| 			return false; // Do not combine
 | |
| 		}
 | |
| 
 | |
| 		if (OptimizeJs::isOptimizeJsEnabledByOtherParty('if_enabled')) {
 | |
| 			return false; // Do not combine (it's already enabled in other plugin)
 | |
| 		}
 | |
| 
 | |
| 		// "Minify HTML" from WP Rocket is sometimes stripping combined SCRIPT tags
 | |
| 		// Better uncombined then missing essential SCRIPT files
 | |
| 		if (Misc::isWpRocketMinifyHtmlEnabled()) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		if ( ($pluginSettings['combine_loaded_js'] === 'for_admin'
 | |
| 		      || $pluginSettings['combine_loaded_js_for_admin_only'] == 1)
 | |
| 		     && Menu::userCanManageAssets() ) {
 | |
| 			return true; // Do combine
 | |
| 		}
 | |
| 		*/
 | |
| 
 | |
| 		// "Apply it only for guest visitors (default)" is set; Do not combine if the user is logged in
 | |
| 		if ( $pluginSettings['combine_loaded_js_for'] === 'guests' && is_user_logged_in() ) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		if ( in_array($pluginSettings['combine_loaded_js'], array('for_all', 1)) ) {
 | |
| 			return true; // Do combine
 | |
| 		}
 | |
| 
 | |
| 		// Finally, return false as none of the checks above matched
 | |
| 		return false;
 | |
| 	}
 | |
| }
 |