wp_back/wp-content/plugins/wp-asset-clean-up/classes/OptimiseAssets/OptimizeJs.php
2024-05-20 15:37:46 +03:00

1372 lines
50 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace WpAssetCleanUp\OptimiseAssets;
use WpAssetCleanUp\FileSystem;
use WpAssetCleanUp\CleanUp;
use WpAssetCleanUp\Main;
use WpAssetCleanUp\MetaBoxes;
use WpAssetCleanUp\Misc;
use WpAssetCleanUp\ObjectCache;
use WpAssetCleanUp\Plugin;
use WpAssetCleanUp\Preloads;
/**
* Class OptimizeJs
* @package WpAssetCleanUp
*/
class OptimizeJs
{
/**
*
*/
public function init()
{
add_action( 'wp_print_footer_scripts', static function() {
/* [wpacu_timing] */ Misc::scriptExecTimer( 'prepare_optimize_files_js' ); /* [/wpacu_timing] */
self::prepareOptimizeList();
/* [wpacu_timing] */ Misc::scriptExecTimer( 'prepare_optimize_files_js', 'end' ); /* [/wpacu_timing] */
}, PHP_INT_MAX );
}
/**
*
*/
public static function prepareOptimizeList()
{
// Are both Minify and Cache Dynamic JS disabled? No point in continuing and using extra resources as there is nothing to change
if ( ! self::isWorthCheckingForOptimization() || Plugin::preventAnyFrontendOptimization() ) {
return;
}
global $wp_scripts;
$jsOptimizeList = array();
$wpScriptsDone = isset($wp_scripts->done) && is_array($wp_scripts->done) ? $wp_scripts->done : array();
$wpScriptsQueue = isset($wp_scripts->queue) && is_array($wp_scripts->queue) ? $wp_scripts->queue : array();
$wpScriptsList = array_unique(array_merge($wpScriptsDone, $wpScriptsQueue));
// Collect all enqueued clean (no query strings) HREFs to later compare them against any hardcoded JS
$allEnqueuedCleanScriptSrcs = array();
// [Start] Collect for caching
if ( ! empty($wpScriptsList) ) {
$isMinifyJsFilesEnabled = MinifyJs::isMinifyJsEnabled() && in_array(Main::instance()->settings['minify_loaded_js_for'], array('src', 'all', ''));
foreach ( $wpScriptsList as $index => $scriptHandle ) {
if ( isset( Main::instance()->wpAllScripts['registered'][ $scriptHandle ]->src ) && ( $src = Main::instance()->wpAllScripts['registered'][ $scriptHandle ]->src ) ) {
$localAssetPath = OptimizeCommon::getLocalAssetPath( $src, 'js' );
if ( ! $localAssetPath || ! is_file( $localAssetPath ) ) {
continue; // not a local file
}
ob_start();
$wp_scripts->do_item( $scriptHandle );
$scriptSourceTag = trim( ob_get_clean() );
// Check if the JS has any 'data-wpacu-skip' attribute; if it does, do not alter it
if ( preg_match( '#data-wpacu-skip([=>/ ])#i', $scriptSourceTag ) ) {
unset( $wpScriptsList[ $index ] );
continue;
}
$cleanScriptSrcFromTagArray = OptimizeCommon::getLocalCleanSourceFromTag( $scriptSourceTag );
if ( isset( $cleanScriptSrcFromTagArray['source'] ) && $cleanScriptSrcFromTagArray['source'] ) {
$allEnqueuedCleanScriptSrcs[] = $cleanScriptSrcFromTagArray['source'];
}
$optimizeValues = self::maybeOptimizeIt(
Main::instance()->wpAllScripts['registered'][ $scriptHandle ],
array( 'local_asset_path' => $localAssetPath, 'is_minify_js_enabled' => $isMinifyJsFilesEnabled )
);
ObjectCache::wpacu_cache_set( 'wpacu_maybe_optimize_it_js_' . $scriptHandle, $optimizeValues );
if ( ! empty( $optimizeValues ) ) {
$jsOptimizeList[] = $optimizeValues;
}
}
}
}
ObjectCache::wpacu_cache_add('wpacu_js_enqueued_srcs', $allEnqueuedCleanScriptSrcs);
ObjectCache::wpacu_cache_add('wpacu_js_optimize_list', $jsOptimizeList);
// [End] Collect for caching
}
/**
* @param $value
* @param array $fileAlreadyChecked
*
* @return array
*/
public static function maybeOptimizeIt($value, $fileAlreadyChecked = array())
{
if ($optimizeValues = ObjectCache::wpacu_cache_get('wpacu_maybe_optimize_it_js_'.$value->handle)) {
return $optimizeValues;
}
global $wp_version;
$src = isset($value->src) ? $value->src : false;
if (! $src) {
return array();
}
$doFileMinify = true;
$isMinifyJsFilesEnabled = (isset($fileAlreadyChecked['is_minify_js_enabled']) && $fileAlreadyChecked['is_minify_js_enabled'])
? $fileAlreadyChecked['is_minify_js_enabled']
: MinifyJs::isMinifyJsEnabled() && in_array(Main::instance()->settings['minify_loaded_js_for'], array('src', 'all', ''));
if ( ! $isMinifyJsFilesEnabled || MinifyJs::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;
$isJsFile = 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, 'js' );
$checkCond = $localAssetPath && is_file($localAssetPath);
}
if ($checkCond) {
if ($fileMTime = @filemtime($localAssetPath)) {
$dbVer = $fileMTime;
}
$isJsFile = true;
}
if ($isJsFile) {
// 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_js_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-added to the database with the new version
OptimizeCommon::deleteTransient($transientName);
} else {
$localPathToJsOptimized = str_replace( '//', '/', Misc::getWpRootDirPath() . $savedValuesArray['optimize_uri'] );
// Do not load any minified JS file (from the database transient cache) if it doesn't exist
// It will fallback to the original JS file
if ( isset( $savedValuesArray['source_uri'] ) && is_file( $localPathToJsOptimized ) ) {
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
);
}
}
}
}
// Check if it starts without "/" or a protocol; e.g. "wp-content/theme/script.js"
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;
}
/*
* [START] JS Content Optimization
*/
if (Main::instance()->settings['cache_dynamic_loaded_js'] &&
((strpos($src, '/?') !== false) || strpos($src, '.php?') !== false || Misc::endsWith($src, '.php')) &&
(strpos($src, site_url()) !== false)
) {
$pathToAssetDir = '';
$sourceBeforeOptimization = $value->src;
if (! ($jsContent = DynamicLoadedAssets::getAssetContentFrom('dynamic', $value))) {
return array();
}
} else {
if (! $isJsFile) {
return array();
}
/*
* This is a local .JS file
*/
$pathToAssetDir = OptimizeCommon::getPathToAssetDir($value->src);
$sourceBeforeOptimization = str_replace(Misc::getWpRootDirPath(), '/', $localAssetPath);
$jsContent = FileSystem::fileGetContents($localAssetPath);
}
$hadToBeMinified = false;
$jsContent = trim($jsContent);
// If it stays like this, it means there is content there, even if only comments
$jsContentBecomesEmptyAfterMin = false;
if ( $doFileMinify && $jsContent ) { // only bother to minify it if it has any content, save resources
// Minify this file?
$jsContentBeforeMin = $jsContent;
$jsContentAfterMin = MinifyJs::applyMinification($jsContentBeforeMin);
$jsContent = $jsContentAfterMin;
if ( $jsContentBeforeMin && $jsContentAfterMin === '' ) {
// It had content, but became empty after minification, most likely it had only comments (e.g. a default child theme's style)
$jsContentBecomesEmptyAfterMin = true;
} else {
$jsContentCompare = md5(trim( $jsContentBeforeMin, '; ' ));
$jsContentCompareWith = md5(trim( $jsContentAfterMin, '; ' ));
if ( $jsContentCompare !== $jsContentCompareWith ) {
$hadToBeMinified = true;
}
}
}
if ( $jsContentBecomesEmptyAfterMin || $jsContent === '' ) {
$jsContent = '/**/';
} else {
$jsContentArray = self::maybeAlterContentForJsFile( $jsContent, false );
$jsContent = $jsContentArray['content']; // resulting content after alteration
$jsContentAfterAlterToCompare = $jsContentArray['content_after_alter_to_compare'];
if ( $isJsFile && ( ! $hadToBeMinified ) ) {
$jsContentCompare = md5(trim( $jsContent, '; ' ));
$jsContentCompareWith = md5(trim( $jsContentAfterAlterToCompare, '; ' ));
if ( $jsContentCompare === $jsContentCompareWith ) {
// 1: The file was not minified
// 2: It doesn't need any alteration (e.g. no Google Fonts to strip from its content)
// No need to copy it in to the cache (save disk space)
return array();
}
}
// Change the necessary relative paths before the file is copied to the caching directory (e.g. /wp-content/cache/asset-cleanup/)
$jsContent = self::maybeDoJsFixes( $jsContent, $pathToAssetDir . '/' );
}
/*
* [END] JS Content Optimization
*/
if (isset($jsContentBeforeMin) && $jsContent === '/**/' && strpos($jsContentBeforeMin, '/*@cc_on') !== false && strpos($jsContentBeforeMin, '@*/') !== false) {
return array(); // Internet Explorer things, leave the file as it is
}
// Relative path to the new file
// Save it to /wp-content/cache/js/{OptimizeCommon::$optimizedSingleFilesDir}/
$fileVer = sha1($jsContent);
$uniqueCachedAssetName = OptimizeCommon::generateUniqueNameForCachedAsset($isJsFile, $localAssetPath, $value->handle, $fileVer);
$newFilePathUri = self::getRelPathJsCacheDir() . OptimizeCommon::$optimizedSingleFilesDir . '/' . $uniqueCachedAssetName;
$newFilePathUri .= '.js';
if ($jsContent === '') {
$jsContent = '/**/';
}
if ($jsContent === '/**/') {
// 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('.js', '-wpacu-empty-file.js', $newFilePathUri);
}
$newLocalPath = WP_CONTENT_DIR . $newFilePathUri; // Ful Local path
$newLocalPathUrl = WP_CONTENT_URL . $newFilePathUri; // Full URL path
if ($jsContent && $jsContent !== '/**/' && apply_filters('wpacu_print_info_comments_in_cached_assets', true)) {
$jsContent = '/*!' . $sourceBeforeOptimization . '*/' . "\n" . $jsContent;
}
$saveFile = FileSystem::filePutContents($newLocalPath, $jsContent);
if (! $saveFile || ! $jsContent) {
// Fallback to the original JS if the optimized version can't be created or updated
return array();
}
$saveValues = array(
'source_uri' => OptimizeCommon::getSourceRelPath($value->src),
'optimize_uri' => OptimizeCommon::getSourceRelPath($newLocalPathUrl),
'ver' => $dbVer
);
// Add / Re-add (with new version) transient
OptimizeCommon::setTransient($transientName, wp_json_encode($saveValues));
return array(
OptimizeCommon::getSourceRelPath($value->src), // Original SRC (Relative path)
OptimizeCommon::getSourceRelPath($newLocalPathUrl), // New SRC (Relative path)
$value->src, // SRC (as it is)
$value->handle
);
}
/**
* This applies to both inline and static JS files contents
*
* @param $jsContent
* @param bool $doJsMinify (false by default as it could be already minified or non-minify type)
*
* @return array
*/
public static function maybeAlterContentForJsFile($jsContent, $doJsMinify = false)
{
if (! trim($jsContent)) { // No Content! Return it as it, no point in doing extra checks
return array('content' => $jsContent);
}
$jsContentBefore = $jsContent;
/* [START] Change JS Content */
if ($doJsMinify) {
$jsContent = MinifyJs::applyMinification($jsContent);
}
if (Main::instance()->settings['google_fonts_remove']) {
$jsContent = FontsGoogleRemove::stripReferencesFromJsCode($jsContent);
} elseif (Main::instance()->settings['google_fonts_display']) {
// Perhaps "display" parameter has to be applied to Google Font Links if they are active
$jsContent = FontsGoogle::alterGoogleFontUrlFromJsContent($jsContent);
}
/* [END] Change JS Content */
// Does it have a source map? Strip it only if any optimization was already applied
// As, otherwise, there's no point in creating a caching file, since there are no changes worth made to the file
if (($jsContentBefore !== $jsContent) && (strpos($jsContent, '// #sourceMappingURL') !== false) && Misc::endsWith(trim($jsContent), '.map')) {
$jsContent = OptimizeCommon::stripSourceMap($jsContent, 'js');
}
$jsContentAfterAlterToCompare = $jsContent; // new possible values
return array('content' => $jsContent , 'content_after_alter_to_compare' => $jsContentAfterAlterToCompare);
}
/**
* @param $jsContent
* @param $doJsMinify
*
* @return false|mixed|string|string[]|null
*/
public static function maybeAlterContentForInlineScriptTag($jsContent, $doJsMinify)
{
if (! trim($jsContent)) { // No Content! Return it as it, no point in doing extra checks
return $jsContent;
}
if (mb_strlen($jsContent) > 500000) { // Bigger then ~500KB? Skip alteration for this inline SCRIPT
return $jsContent;
}
$useCacheForInlineScript = true;
if (mb_strlen($jsContent) < 40000) { // Smaller than ~40KB? Do not cache it
$useCacheForInlineScript = false;
}
// For debugging purposes
if (isset($_GET['wpacu_no_cache']) || (defined('WPACU_NO_CACHE') && WPACU_NO_CACHE === true)) { $useCacheForInlineScript = false; }
if ($useCacheForInlineScript) {
// 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
$jsContentBeforeHash = sha1( $jsContent );
$pathToInlineJsOptimizedItem = WP_CONTENT_DIR . self::getRelPathJsCacheDir() . '/item/inline/' . $jsContentBeforeHash . '.js';
// Check if the file exists before moving forward
if ( is_file( $pathToInlineJsOptimizedItem ) ) {
$cachedJsFileExpiresIn = OptimizeCommon::$cachedAssetFileExpiresIn;
if ( filemtime( $pathToInlineJsOptimizedItem ) < ( time() - 1 * $cachedJsFileExpiresIn ) ) {
// Has the caching period expired? Remove the file as a new one has to be generated
@unlink( $pathToInlineJsOptimizedItem );
} else {
// Not expired / Return its content from the cache in a faster way
$inlineJsStorageItemJsonContent = FileSystem::fileGetContents( $pathToInlineJsOptimizedItem );
if ( $inlineJsStorageItemJsonContent !== '' ) {
return $inlineJsStorageItemJsonContent;
}
}
}
}
/* [START] Change JS Content */
if ($doJsMinify) {
$jsContent = MinifyJs::applyMinification($jsContent);
}
if (Main::instance()->settings['google_fonts_remove']) {
$jsContent = FontsGoogleRemove::stripReferencesFromJsCode($jsContent);
} elseif (Main::instance()->settings['google_fonts_display']) {
// Perhaps "display" parameter has to be applied to Google Font Links if they are active
$jsContent = FontsGoogle::alterGoogleFontUrlFromJsContent($jsContent);
}
/* [END] Change JS Content */
if ( $useCacheForInlineScript && isset($pathToInlineJsOptimizedItem) ) {
// Store the optimized content to the cached JS file which would be read quicker
FileSystem::filePutContents( $pathToInlineJsOptimizedItem, $jsContent );
}
return $jsContent;
}
/**
* @param $htmlSource
*
* @return string
*/
public static function updateHtmlSourceOriginalToOptimizedJs($htmlSource)
{
$parseSiteUrlPath = (string)parse_url(site_url(), PHP_URL_PATH);
$siteUrlNoProtocol = str_replace(array('http://', 'https://'), '//', site_url());
$jsOptimizeList = ObjectCache::wpacu_cache_get('wpacu_js_optimize_list') ?: array();
$allEnqueuedCleanSources = ObjectCache::wpacu_cache_get('wpacu_js_enqueued_srcs') ?: 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();
$cdnUrlForJs = isset($cdnUrls['js']) ? $cdnUrls['js'] : false;
preg_match_all('#(<script[^>]*src(|\s+)=(|\s+)[^>]*>)|(<link[^>]*(as(\s+|)=(\s+|)(|"|\')script(|"|\'))[^>]*>)#Umi', $htmlSource, $matchesSourcesFromTags, PREG_SET_ORDER);
$jsOptimizeListHardcoded = $scriptTagsToUpdate = array();
foreach ($matchesSourcesFromTags as $matches) {
$scriptSourceTag = $matches[0];
if (strip_tags($scriptSourceTag) !== '') {
// Hmm? Not a valid tag... Skip it...
continue;
}
// Check if the JS has any 'data-wpacu-skip' attribute; if it does, do not alter it
if (preg_match('#data-wpacu-skip([=>/ ])#i', $scriptSourceTag)) {
continue;
}
$cleanScriptSrcFromTagArray = OptimizeCommon::getLocalCleanSourceFromTag($scriptSourceTag);
// Skip external links, no point in carrying on
if (! $cleanScriptSrcFromTagArray || ! is_array($cleanScriptSrcFromTagArray)) {
continue;
}
$forAttr = 'src';
// Any preloads for the optimized script?
// e.g. <link rel='preload' as='script' href='...' />
if (strpos($scriptSourceTag, '<link') !== false) {
$forAttr = 'href';
}
// Is it a local JS? Check if it's hardcoded (not enqueued the WordPress way)
$cleanScriptSrcFromTag = $cleanScriptSrcFromTagArray['source'];
$afterQuestionMark = $cleanScriptSrcFromTagArray['after_question_mark'];
$isHardcodedDetected = false;
if (! in_array($cleanScriptSrcFromTag, $allEnqueuedCleanSourcesIncludingTheirRelPaths)) {
// Not in the final enqueued list? Most likely hardcoded (not added via wp_enqueue_scripts())
// Emulate the object value (as the enqueued scripts)
$generatedHandle = md5($cleanScriptSrcFromTag);
$value = (object)array(
'handle' => $generatedHandle,
'src' => $cleanScriptSrcFromTag,
'ver' => md5($afterQuestionMark)
);
$optimizeValues = self::maybeOptimizeIt($value);
ObjectCache::wpacu_cache_set('wpacu_maybe_optimize_it_js_'.$generatedHandle, $optimizeValues);
if (! empty($optimizeValues)) {
$isHardcodedDetected = true;
$jsOptimizeListHardcoded[] = $optimizeValues;
}
}
if ( ! $isHardcodedDetected ) {
$listToParse = $jsOptimizeList;
} else {
$listToParse = $jsOptimizeListHardcoded;
}
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($cleanScriptSrcFromTag, $listValues[0]) === false) {
continue;
}
// The contents of the CSS file has been changed and thus, we will replace the source path from 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 JS 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($cleanScriptSrcFromTag, $parseSiteUrlPath) === 0)) {
$sourceUrlList[] = $cleanScriptSrcFromTag;
}
if ($parseSiteUrlPath && strpos($cleanScriptSrcFromTag, $parseSiteUrlPath) === 0 && strpos($cleanScriptSrcFromTag, $listValues[0]) !== false) {
$sourceUrlList[] = str_replace('//', '/', $parseSiteUrlPath.'/'.$listValues[0]);
}
elseif ( $cleanScriptSrcFromTag === $listValues[0] ) {
$sourceUrlList[] = $listValues[0];
}
if ($cdnUrlForJs) {
// Does it have a CDN?
$sourceUrlList[] = OptimizeCommon::cdnToUrlFormat($cdnUrlForJs, 'rel') . $listValues[0];
}
// Any rel tag? You never know
// e.g. <script src="/wp-content/themes/my-theme/script.js"></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 no CDN is set, it will return site_url() as a prefix
$optimizeUrl = OptimizeCommon::cdnToUrlFormat($cdnUrlForJs, 'raw') . $listValues[1]; // string
if ($scriptSourceTag !== str_ireplace($sourceUrlList, $optimizeUrl, $scriptSourceTag)) {
// 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.js') !== false && filesize($localPathOptimizedFile) < 10);
if ($isEmptyOptimizedFile) {
// Strip it as its content (after optimization, for instance) is empty; no point in having extra HTTP requests
if ($forAttr === 'src') {
$scriptTagsToUpdate[ $scriptSourceTag . '</script>' ] = '';
} else {
$scriptTagsToUpdate[ $scriptSourceTag ] = ''; // LINK (e.g. preload of a JS file)
}
// Note: As for September 3, 2020, the inline JS associated with the handle is no longer removed if the main JS file is empty
// There could be cases when the main JS file is empty, but the inline JS tag associated with it has code that is needed
} else {
$newScriptSourceTag = self::updateOriginalToOptimizedTag( $scriptSourceTag, $sourceUrlList, $optimizeUrl, $forAttr );
$scriptTagsToUpdate[$scriptSourceTag] = $newScriptSourceTag;
}
break;
}
}
}
return strtr($htmlSource, $scriptTagsToUpdate);
}
/**
* @param $scriptSourceTag string
* @param $sourceUrlList array
* @param $optimizeUrl string
* @param string $forAttr ('src' (default), or 'href' if it's preloaded)
*
* @return string
*/
public static function updateOriginalToOptimizedTag($scriptSourceTag, $sourceUrlList, $optimizeUrl, $forAttr = 'src')
{
if (is_array($sourceUrlList) && ! empty($sourceUrlList)) {
foreach ($sourceUrlList as $sourceUrl) {
$newScriptSourceTag = str_replace($sourceUrl, $optimizeUrl, $scriptSourceTag);
if ($newScriptSourceTag !== $scriptSourceTag) {
break;
}
}
} else {
$newScriptSourceTag = str_replace( $sourceUrlList, $optimizeUrl, $scriptSourceTag );
}
$tagToCheck = ($forAttr === 'src') ? 'script' : 'link';
$sourceUrlRel = is_array($sourceUrlList) ? OptimizeCommon::getSourceRelPath($sourceUrlList[0]) : OptimizeCommon::getSourceRelPath($sourceUrlList);
$newScriptSourceTag = str_ireplace('<'.$tagToCheck.' ', '<'.$tagToCheck.' data-wpacu-script-rel-src-before="'.$sourceUrlRel.'" ', $newScriptSourceTag);
$sourceValue = Misc::getValueFromTag($scriptSourceTag);
// No space from the matching and ? should be there
if ($sourceValue && ( strpos( $sourceValue, ' ' ) === false )) {
if ( strpos( $sourceValue, '?' ) !== false ) {
// Strip things like ?ver=
list( , $toStrip ) = explode( '?', $sourceValue );
$toStrip = '?' . trim( $toStrip );
$newScriptSourceTag = str_replace( $toStrip, '', $newScriptSourceTag );
}
if ( strpos( $sourceValue, '&#038;ver' ) !== false ) {
// Replace any .js&#038;ver with .js
$toStrip = strrchr($sourceValue, '&#038;ver');
$newScriptSourceTag = str_replace( $toStrip, '', $newScriptSourceTag );
}
}
global $wp_version;
$newScriptSourceTag = str_replace('.js&#038;ver='.$wp_version, '.js', $newScriptSourceTag);
$newScriptSourceTag = str_replace('.js&#038;ver=', '.js', $newScriptSourceTag);
return preg_replace('!\s+!', ' ', $newScriptSourceTag); // replace multiple spaces with only one space
}
/**
* @param $htmlSource
*
* @return mixed|void
*/
public static function alterHtmlSource($htmlSource)
{
// There has to be at least one "<script", otherwise, it could be a feed request or something similar (not page, post, homepage etc.)
if ( stripos($htmlSource, '<script') === false || isset($_GET['wpacu_no_optimize_js']) ) {
return $htmlSource;
}
/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source_for_optimize_js' ); /* [/wpacu_timing] */
/* [wpacu_pro] */$htmlSource = apply_filters('wpacu_pro_maybe_move_jquery_after_body_tag', $htmlSource);/* [/wpacu_pro] */
if (! Main::instance()->preventAssetsSettings()) {
/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_unload_ignore_deps_js'; Misc::scriptExecTimer($wpacuTimingName); /* [/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
$htmlSource = self::ignoreDependencyRuleAndKeepChildrenLoaded($htmlSource);
/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
// Move any jQuery inline SCRIPT that is triggered before jQuery library is called through "jquery-core" handle
if (Main::instance()->settings['move_inline_jquery_after_src_tag']) {
/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_move_inline_jquery_after_src_tag'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
$htmlSource = self::moveInlinejQueryAfterjQuerySrc($htmlSource);
/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
}
}
$htmlSource = self::stripAnyReferencesForUnloadedScripts($htmlSource);
/*
* The JavaScript files only get cached if they are minified or are loaded like /?custom-js=version - /script.php?ver=1 etc.
* #optimizing
* STEP 2: Load optimize-able caching list and replace the original source URLs with the new cached ones
*/
// At least minify or cache dynamic loaded JS has to be enabled to proceed
if (self::isWorthCheckingForOptimization()) {
/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_original_to_optimized_js'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
// 'wpacu_js_optimize_list' caching list is also checked; if it's empty, no optimization is made
$htmlSource = self::updateHtmlSourceOriginalToOptimizedJs($htmlSource);
/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
}
if (! Main::instance()->preventAssetsSettings()) {
/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_preload_js'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
$preloads = Preloads::instance()->getPreloads();
if (isset($preloads['scripts']) && ! empty($preloads['scripts'])) {
$htmlSource = Preloads::appendPreloadsForScriptsToHead($htmlSource);
}
/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
}
/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_combine_js'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
$proceedWithCombineOnThisPage = true;
$isSingularPage = defined('WPACU_CURRENT_PAGE_ID') && WPACU_CURRENT_PAGE_ID > 0 && is_singular();
// If "Do not combine JS 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_js_optimize' refers to avoid the combination of JS files
if ( (isset( $pageOptions['no_js_optimize'] ) && $pageOptions['no_js_optimize'])
|| (isset( $pageOptions['no_assets_settings'] ) && $pageOptions['no_assets_settings']) ) {
$proceedWithCombineOnThisPage = false;
}
}
if ($proceedWithCombineOnThisPage) {
/* [wpacu_timing] */ // Note: Load timing is checked within the method /* [/wpacu_timing] */
$htmlSource = CombineJs::doCombine($htmlSource);
if (defined('WPACU_REAPPLY_PRELOADING_FOR_COMBINED_JS') && WPACU_REAPPLY_PRELOADING_FOR_COMBINED_JS) {
$htmlSource = Preloads::appendPreloadsForScriptsToHead($htmlSource);
}
}
/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
if (self::isWorthCheckingForOptimization() && ! Main::instance()->preventAssetsSettings() && (MinifyJs::isMinifyJsEnabled() && in_array(Main::instance()->settings['minify_loaded_js_for'], array('inline', 'all')))) {
/* [wpacu_timing] */ $wpacuTimingName = 'alter_html_source_for_minify_inline_script_tags'; Misc::scriptExecTimer($wpacuTimingName); /* [/wpacu_timing] */
$htmlSource = MinifyJs::minifyInlineScriptTags($htmlSource);
/* [wpacu_timing] */ Misc::scriptExecTimer($wpacuTimingName, 'end'); /* [/wpacu_timing] */
}
// Final cleanups
$htmlSource = str_replace(Preloads::DEL_SCRIPTS_PRELOADS, '', $htmlSource);
$htmlSource = preg_replace('#(\s+|)(data-wpacu-jquery-core-handle=1|data-wpacu-jquery-migrate-handle=1)(\s+|)#Umi', ' ', $htmlSource);
$htmlSource = preg_replace('#(\s+|)data-wpacu-script-rel-src-before=(["\'])' . '(.*)' . '(\1)(\s+|)#Usmi', ' ', $htmlSource);
$htmlSource = preg_replace('#<script(.*)data-wpacu-script-handle=\'(.*)\'#Umi', '<script \\1', $htmlSource);
$htmlSource = preg_replace('#<script(\s+)src=\'#Umi', '<script src=\'', $htmlSource);
// Clear possible empty SCRIPT tags (e.g. left from associated 'before' and 'after' tags after their content was stripped)
$htmlSource = preg_replace('#<script(\s+)(type=\'text/javascript\'|)(\s+|)></script>#Umi', '', $htmlSource);
/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_optimize_js', 'end'); /* [/wpacu_timing] */
return $htmlSource;
}
/**
* @return string
*/
public static function getRelPathJsCacheDir()
{
return OptimizeCommon::getRelPathPluginCacheDir().'js/'; // keep trailing slash at the end
}
/**
* @param $scriptSrcs
* @param $htmlSource
*
* @return array
*/
public static function getScriptTagsFromSrcs($scriptSrcs, $htmlSource)
{
$scriptTags = array();
foreach ($scriptSrcs as $scriptSrc) {
$scriptSrc = str_replace('{site_url}', '', $scriptSrc);
// Clean it up for the preg_quote() call
if (strpos($scriptSrc, '.js?') !== false) {
list($scriptSrc,) = explode('.js?', $scriptSrc);
$scriptSrc .= '.js';
}
preg_match_all('#<script[^>]*src(|\s+)=(|\s+)[^>]*'. preg_quote($scriptSrc, '/'). '.*(>)(.*|)</script>#Usmi', $htmlSource, $matchesFromSrc, PREG_SET_ORDER);
if (isset($matchesFromSrc[0][0]) && strip_tags($matchesFromSrc[0][0]) === '') {
$scriptTags[] = trim($matchesFromSrc[0][0]);
}
}
return $scriptTags;
}
/**
* @param $strFind
* @param $strReplaceWith
* @param $string
*
* @return mixed
*/
public static function strReplaceOnce($strFind, $strReplaceWith, $string)
{
if ( strpos($string, $strFind) === false ) {
return $string;
}
$occurrence = strpos($string, $strFind);
return substr_replace($string, $strReplaceWith, $occurrence, strlen($strFind));
}
/**
* @param $jsContent
* @param $appendBefore
*
* @return string
*/
public static function maybeDoJsFixes($jsContent, $appendBefore)
{
// Relative URIs for CSS Paths
// For code such as:
// $(this).css("background", "url('../images/image-1.jpg')");
$jsContentPathReps = array(
'url("../' => 'url("'.$appendBefore.'../',
"url('../" => "url('".$appendBefore.'../',
'url(../' => 'url('.$appendBefore.'../',
'url("./' => 'url("'.$appendBefore.'./',
"url('./" => "url('".$appendBefore.'./',
'url(./' => 'url('.$appendBefore.'./'
);
$jsContent = str_replace(array_keys($jsContentPathReps), array_values($jsContentPathReps), $jsContent);
$jsContent = trim($jsContent);
if (substr($jsContent, -1) !== ';') {
$jsContent .= "\n" . ';'; // add semicolon as the last character
}
return $jsContent;
}
/**
* e.g. if a script is unloaded, strip any LINK tag that preloads that script (e.g. added by other plugins)
*
* @param $htmlSource
*
* @return array|mixed|string|string[]
*/
public static function stripAnyReferencesForUnloadedScripts($htmlSource)
{
// Gather all SRCs of the unloaded scripts (if any)
$unloadedScriptRelSrcs = array();
if ( isset( Main::instance()->allUnloadedAssets['scripts'] ) && ! empty( Main::instance()->allUnloadedAssets['scripts'] ) ) {
foreach ( array_unique( Main::instance()->allUnloadedAssets['scripts'] ) as $scriptHandle ) {
if ( ! (isset(Main::instance()->wpAllScripts['registered'][ $scriptHandle ]->src) && Main::instance()->wpAllScripts['registered'][ $scriptHandle ]->src) ) {
continue; // does not have a "src" (e.g. inline JS)
}
$unloadedScriptRelSrcs[] = OptimizeCommon::getSourceRelPath( Main::instance()->wpAllScripts['registered'][ $scriptHandle ]->src );
}
}
if ( ! empty($unloadedScriptRelSrcs) ) {
$htmlSource = OptimizeCommon::matchAndReplaceLinkTags($htmlSource, array('as' => 'script', 'unloaded_assets_rel_sources' => $unloadedScriptRelSrcs));
}
return $htmlSource;
}
/**
* @param $htmlSource
*
* @return false|mixed|string|void
*/
public static function moveInlinejQueryAfterjQuerySrc($htmlSource)
{
if (stripos($htmlSource, '<script') === false || ! Misc::isDOMDocumentOn()) {
return $htmlSource;
}
$domTag = OptimizeCommon::getDomLoadedTag($htmlSource, 'moveInlinejQueryAfterjQuerySrc');
$scriptTagsObj = $domTag->getElementsByTagName( 'script' );
if ($scriptTagsObj === null) {
return $htmlSource;
}
// Does it have the "src" attribute? Skip it as it's not an inline SCRIPT tag
$jQueryPatternsToMatch = array(
'jQuery',
'\$(\s+|)\((\s+|)document(\s+|)\)(\s+|).(\s+|)ready(\s+|)\('
);
$jQueryRegExp = '#' . implode('|', $jQueryPatternsToMatch) . '#si';
$jQueryCoreDel = 'data-wpacu-jquery-core-handle=';
$jQueryMigrateDel = 'data-wpacu-jquery-migrate-handle=';
if (strpos($htmlSource, $jQueryMigrateDel) !== false) {
$collectUntil = $jQueryMigrateDel;
} elseif (strpos($htmlSource, $jQueryCoreDel) !== false) {
$collectUntil = $jQueryCoreDel;
} else {
return $htmlSource; // No jQuery or jQuery Migrate? Just return the HTML source
}
$inlineBeforejQuerySrc = array();
foreach ($scriptTagsObj as $scriptTagObj) {
$tagContents = $scriptTagObj->nodeValue;
if ( strpos( Misc::getOuterHTML( $scriptTagObj ), $collectUntil) !== false) {
break;
}
if ($tagContents !== '' && preg_match($jQueryRegExp, $tagContents)) {
preg_match('#<script[^>]*>'.preg_quote($tagContents, '/').'</script>#si', $htmlSource, $matchesExact);
$exactMatchTag = isset($matchesExact[0]) ? $matchesExact[0] : '';
// Replace the first match only in rare cases there are multiple SCRIPT tags with the same code
if ($exactMatchTag && ($pos = strpos($htmlSource, $exactMatchTag)) !== false) {
$inlineBeforejQuerySrc[] = $exactMatchTag;
$htmlSource = substr_replace($htmlSource, '', $pos, strlen($exactMatchTag));
}
}
}
preg_match('#<script* '.$collectUntil.'*[^>]*>(.*?)</script>#si', $htmlSource, $matches);
if (! empty($inlineBeforejQuerySrc) && $collectUntil && isset($matches[0])) {
$htmlSource = preg_replace('#<script* '.$collectUntil.'*[^>]*>(.*?)</script>#si', $matches[0]."\n".implode("\n", $inlineBeforejQuerySrc), $htmlSource);
}
return $htmlSource;
}
/**
* @param string $returnType
* 'list' - will return the list of plugins that have JS optimization enabled
* 'if_enabled' - will stop when it finds the first one (any order) and return true
* @return array|bool
*/
public static function isOptimizeJsEnabledByOtherParty($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'
);
$jsOptimizeEnabledIn = array();
foreach ($pluginsToCheck as $plugin => $pluginTitle) {
// "Autoptimize" check
if ($plugin === 'autoptimize/autoptimize.php' && Misc::isPluginActive($plugin) && get_option('autoptimize_js')) {
$jsOptimizeEnabledIn[] = $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')) {
$wpRocketMinifyJs = get_rocket_option('minify_js');
$wpRocketMinifyConcatenateJs = get_rocket_option('minify_concatenate_js');
} else {
$wpRocketSettings = get_option('wp_rocket_settings');
$wpRocketMinifyJs = isset($wpRocketSettings['minify_js']) ? $wpRocketSettings['minify_js'] : false;
$wpRocketMinifyConcatenateJs = isset($wpRocketSettings['minify_concatenate_js']) ? $wpRocketSettings['minify_concatenate_js'] : false;
}
if ($wpRocketMinifyJs || $wpRocketMinifyConcatenateJs) {
$jsOptimizeEnabledIn[] = $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['wpFastestCacheMinifyJs']) || isset($wpfcOptions['wpFastestCacheCombineJs'])) {
$jsOptimizeEnabledIn[] = $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();
$w3tcEnableJs = (int)trim(Misc::extractBetween($w3tcConfigMaster, '"minify.js.enable":', ','), '" ');
if ($w3tcEnableJs === 1) {
$jsOptimizeEnabledIn[] = $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')) {
if (@\SiteGround_Optimizer\Options\Options::is_enabled( 'siteground_optimizer_optimize_javascript')) {
$jsOptimizeEnabledIn[] = $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
$jsOptimizeEnabledIn[] = $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['js_minify']) && $liteSpeedCacheConf['js_minify'])
|| (isset($liteSpeedCacheConf['js_combine']) && $liteSpeedCacheConf['js_combine']) ) {
$jsOptimizeEnabledIn[] = $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-scripts', 1) ) {
$jsOptimizeEnabledIn[] = $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-js'], $breezeAdvancedSettings['breeze-group-js'])
&& $breezeBasicSettings['breeze-minify-js'] && $breezeAdvancedSettings['breeze-group-js']) {
$jsOptimizeEnabledIn[] = $pluginTitle;
if ($returnType === 'if_enabled') { return true; }
}
}
}
if ($returnType === 'if_enabled') { return false; }
return $jsOptimizeEnabledIn;
}
/**
* @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 MinifyJs::isMinifyJsEnabled() ||
Main::instance()->settings['cache_dynamic_loaded_js'] ||
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['scripts']) && ! empty($ignoreChild['scripts'])) {
foreach (array_keys($ignoreChild['scripts']) as $scriptHandle) {
if (isset(Main::instance()->wpAllScripts['registered'][$scriptHandle]->src, Main::instance()->ignoreChildren['scripts'][$scriptHandle.'_has_unload_rule']) && ($scriptSrc = Main::instance()->wpAllScripts['registered'][$scriptHandle]->src) && Main::instance()->ignoreChildren['scripts'][$scriptHandle.'_has_unload_rule']) {
$toReplaceTagList = array();
// If the handle has any inline JavaScript associated with it (before or after the tag), make sure it's stripped as well
if ($translationsContent = self::generateInlineAssocHtmlForHandle($scriptHandle, 'translations')) {
$toReplaceTagList[] = $translationsContent;
}
if ($cDataContent = self::generateInlineAssocHtmlForHandle($scriptHandle, 'data')) {
$toReplaceTagList[] = $cDataContent;
}
if ($beforeContent = self::generateInlineAssocHtmlForHandle($scriptHandle, 'before')) {
$toReplaceTagList[] = $beforeContent;
}
$toReplaceTagList[] = self::getScriptTagFromHandle(array('data-wpacu-script-handle=[\'"]' . $scriptHandle . '[\'"]'), $htmlSource);
if ($afterContent = self::generateInlineAssocHtmlForHandle($scriptHandle, 'after')) {
$toReplaceTagList[] = $afterContent;
}
$htmlSource = str_replace($toReplaceTagList, '', $htmlSource);
// Extra, in case the previous replacement didn't go through
$listWithMatches = array();
$listWithMatches[] = 'data-wpacu-script-handle=[\'"]'.$scriptHandle.'[\'"]';
$listWithMatches[] = OptimizeCommon::getSourceRelPath($scriptSrc);
$htmlSource = CleanUp::cleanScriptTagFromHtmlSource($listWithMatches, $htmlSource);
}
}
}
return $htmlSource;
}
/**
* This would fetch the content of the SCRIPT tag (data, before & after)
*
* @param $scriptTagOrHandle
* @param $wpacuRegisteredScripts
* @param string $from
* @param string $return ("value": JS Inline Content / "html": JS Inline Content surrounded by tags)
*
* @return array
*/
public static function getInlineAssociatedWithScriptHandle($scriptTagOrHandle, $wpacuRegisteredScripts, $from = 'tag', $return = 'value')
{
if ($from === 'tag') {
preg_match_all('#data-wpacu-script-handle=([\'])' . '(.*)' . '(\1)#Usmi', $scriptTagOrHandle, $outputMatches);
$scriptHandle = (isset($outputMatches[2][0]) && $outputMatches[2][0]) ? trim($outputMatches[2][0], '"\'') : '';
} else { // 'handle'
$scriptHandle = $scriptTagOrHandle;
}
if ( $return === 'value' && $scriptHandle ) {
$scriptExtraCdata = $scriptExtraBefore = $scriptExtraAfter = '';
if (isset($wpacuRegisteredScripts[$scriptHandle]->extra)) {
$scriptExtraArray = $wpacuRegisteredScripts[ $scriptHandle ]->extra;
if ( isset( $scriptExtraArray['data'] ) && $scriptExtraArray['data'] ) {
$scriptExtraCdata .= $scriptExtraArray['data'] . "\n";
}
if ( isset( $scriptExtraArray['before'] ) && ! empty( $scriptExtraArray['before'] ) ) {
foreach ( $scriptExtraArray['before'] as $beforeData ) {
if ( ! is_bool( $beforeData ) ) {
$scriptExtraBefore .= $beforeData . "\n";
}
}
}
if ( isset( $scriptExtraArray['after'] ) && ! empty( $scriptExtraArray['after'] ) ) {
foreach ( $scriptExtraArray['after'] as $afterData ) {
if ( ! is_bool( $afterData ) ) {
$scriptExtraAfter .= $afterData . "\n";
}
}
}
}
$scriptTranslations = '';
if (method_exists('wp_scripts', 'print_translations')) {
$scriptTranslations = wp_scripts()->print_translations( $scriptHandle, false );
}
return array(
'translations' => trim($scriptTranslations),
'data' => trim($scriptExtraCdata),
'before' => trim($scriptExtraBefore),
'after' => trim($scriptExtraAfter)
);
}
if ( $return === 'html' && $scriptHandle ) {
return array(
'translations' => self::generateInlineAssocHtmlForHandle($scriptHandle, 'translations'),
'data' => self::generateInlineAssocHtmlForHandle($scriptHandle, 'data'),
'before' => self::generateInlineAssocHtmlForHandle($scriptHandle, 'before'),
'after' => self::generateInlineAssocHtmlForHandle($scriptHandle, 'after')
);
}
return array('translations' => '', 'data' => '', 'before' => '', 'after' => '');
}
/**
* @param string $handle
* @param string $position
* @param string $inlineScriptContent
*
* @return string
*/
public static function generateInlineAssocHtmlForHandle($handle, $position, $inlineScriptContent = '')
{
global $wp_scripts;
$output = '';
if ($position === 'translations') {
$translations = false;
if (method_exists('wp_scripts', 'print_translations')) {
$translations = $wp_scripts->print_translations( $handle, false );
}
if ( $translations ) {
$output = sprintf( "<script%s id='%s-js-translations'>\n%s\n</script>\n", Misc::getScriptTypeAttribute(), esc_attr( $handle ), $translations );
}
}
if ( $position === 'data' ) {
if ( ! $inlineScriptContent ) {
$inlineScriptContent = $wp_scripts->get_data( $handle, 'data' );
if ( ! $inlineScriptContent ) {
return '';
}
}
$output .= sprintf("<script%s id='%s-js-extra'>\n", Misc::getScriptTypeAttribute(), esc_attr($handle));
// CDATA is not needed for HTML 5.
if ( Misc::getScriptTypeAttribute() ) {
$output .= "/* <![CDATA[ */\n";
}
$output .= $inlineScriptContent."\n";
if ( Misc::getScriptTypeAttribute() ) {
$output .= "/* ]]> */\n";
}
$output .= '</script>';
}
if ( $position === 'before' || $position === 'after' ) {
if ( ! $inlineScriptContent ) {
if (method_exists($wp_scripts, 'get_inline_script_data')) {
// WordPress >= 6.3
$inlineScriptContent = $wp_scripts->get_inline_script_data( $handle, $position );
} else {
// WordPress < 6.3
$inlineScriptContent = $wp_scripts->print_inline_script( $handle, $position, false );
}
if ( ! $inlineScriptContent ) {
$output = '';
}
}
if ( $inlineScriptContent ) {
if (function_exists('wp_get_inline_script_tag')) {
// WordPress >= 5.7.0
$id = "{$handle}-js-{$position}";
$output = wp_get_inline_script_tag( $inlineScriptContent, compact( 'id' ) );
} else {
// WordPress < 5.7.0
$output = sprintf( "<script%s id='%s-js-%s'>\n%s\n</script>\n", Misc::getScriptTypeAttribute(), $handle, $position, $inlineScriptContent );
}
}
}
return $output;
}
/**
* @param $listWithPatterns
* @param $htmlSource
*
* @return string
*/
public static function getScriptTagFromHandle($listWithPatterns, $htmlSource)
{
if (empty($listWithPatterns)) {
return '';
}
if (! is_array($listWithPatterns)) {
$listWithPatterns = array($listWithPatterns);
}
preg_match_all(
'#<script[^>]*('.implode('|', $listWithPatterns).')[^>].*(>)#Usmi',
$htmlSource,
$matchesSourcesFromTags
);
if (empty($matchesSourcesFromTags)) {
return '';
}
if (isset($matchesSourcesFromTags[0]) && ! empty($matchesSourcesFromTags[0])) {
foreach ($matchesSourcesFromTags[0] as $matchesFromTag) {
if (stripos($matchesFromTag, ' src=') !== false && strip_tags($matchesFromTag) === '') {
return $matchesFromTag.'</script>';
}
}
}
return '';
}
}