first
This commit is contained in:
@ -0,0 +1,627 @@
|
||||
<?php
|
||||
namespace WpAssetCleanUp\OptimiseAssets;
|
||||
|
||||
use WpAssetCleanUp\Main;
|
||||
use WpAssetCleanUp\Menu;
|
||||
use WpAssetCleanUp\FileSystem;
|
||||
use WpAssetCleanUp\Misc;
|
||||
use WpAssetCleanUp\ObjectCache;
|
||||
use WpAssetCleanUp\Preloads;
|
||||
|
||||
/**
|
||||
* Class CombineCss
|
||||
* @package WpAssetCleanUp\OptimiseAssets
|
||||
*/
|
||||
class CombineCss
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $jsonStorageFile = 'css-combined{maybe-extra-info}.json';
|
||||
|
||||
/**
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function doCombine($htmlSource)
|
||||
{
|
||||
if ( ! Misc::isDOMDocumentOn() ) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
if ( ! self::proceedWithCssCombine() ) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
global $wp_styles;
|
||||
$wpacuRegisteredStyles = $wp_styles->registered;
|
||||
|
||||
$storageJsonContents = array();
|
||||
$skipCache = false; // default
|
||||
|
||||
if (isset($_GET['wpacu_no_cache']) || (defined('WPACU_NO_CACHE') && WPACU_NO_CACHE === true)) {
|
||||
$skipCache = true;
|
||||
}
|
||||
|
||||
// If cache is not skipped, read the information from the cache as it's much faster
|
||||
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
|
||||
$storageJsonContents = OptimizeCommon::getAssetCachedData( self::$jsonStorageFile, OptimizeCss::getRelPathCssCacheDir(), 'css' );
|
||||
}
|
||||
|
||||
// $uriToFinalCssFile will always be relative ONLY within WP_CONTENT_DIR . self::getRelPathCssCacheDir()
|
||||
// which is usually "wp-content/cache/asset-cleanup/css/"
|
||||
|
||||
if ( $skipCache || empty($storageJsonContents) ) {
|
||||
$storageJsonContentsToSave = array();
|
||||
|
||||
/*
|
||||
* NO CACHING? Parse the DOM
|
||||
*/
|
||||
// Nothing in the database records or the retrieved cached file does not exist?
|
||||
OptimizeCommon::clearAssetCachedData(self::$jsonStorageFile);
|
||||
|
||||
$storageJsonContents = array();
|
||||
|
||||
$domTag = OptimizeCommon::getDomLoadedTag($htmlSource, 'combineCss');
|
||||
|
||||
foreach (array('head', 'body') as $docLocationTag) {
|
||||
$combinedUriPathsGroup = $localAssetsPathsGroup = $linkHrefsGroup = array();
|
||||
$localAssetsExtraGroup = array();
|
||||
|
||||
$docLocationElements = $domTag->getElementsByTagName($docLocationTag)->item(0);
|
||||
|
||||
if ($docLocationElements === null) { continue; }
|
||||
|
||||
$xpath = new \DOMXpath($domTag);
|
||||
$linkTags = $xpath->query('/html/'.$docLocationTag.'/link[@rel="stylesheet"] | /html/'.$docLocationTag.'/link[@rel="preload"]');
|
||||
if ($linkTags === null) { continue; }
|
||||
|
||||
foreach ($linkTags as $tagObject) {
|
||||
$linkAttributes = array();
|
||||
foreach ($tagObject->attributes as $attrObj) { $linkAttributes[$attrObj->nodeName] = trim($attrObj->nodeValue); }
|
||||
|
||||
// Only rel="stylesheet" (with no rel="preload" associated with it) gets prepared for combining as links with rel="preload" (if any) are never combined into a standard render-blocking CSS file
|
||||
// rel="preload" is there for a reason to make sure the CSS code is made available earlier prior to the one from rel="stylesheet" which is render-blocking
|
||||
if (isset($linkAttributes['rel'], $linkAttributes['href']) && $linkAttributes['href']) {
|
||||
$href = (string) $linkAttributes['href'];
|
||||
|
||||
if (self::skipCombine($linkAttributes['href'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// e.g. for 'admin-bar' (keep it as standalone when critical CSS is used)
|
||||
if (isset($linkAttributes['data-wpacu-skip-preload']) && has_filter('wpacu_critical_css')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the CSS file has any 'data-wpacu-skip' attribute; if it does, do not alter it
|
||||
if (isset($linkAttributes['data-wpacu-skip']) || isset($scriptAttributes['data-wpacu-apply-media-query'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Separate each combined group by the "media" attribute; e.g. we don't want "all" and "print" mixed
|
||||
$mediaValue = (array_key_exists('media', $linkAttributes) && $linkAttributes['media']) ? $linkAttributes['media'] : 'all';
|
||||
|
||||
// Check if there is any rel="preload" (Basic) connected to the rel="stylesheet"
|
||||
// making sure the file is not added to the final CSS combined file
|
||||
if (isset($linkAttributes['data-wpacu-style-handle']) &&
|
||||
$linkAttributes['data-wpacu-style-handle'] &&
|
||||
ObjectCache::wpacu_cache_get($linkAttributes['data-wpacu-style-handle'], 'wpacu_basic_preload_handles')) {
|
||||
$mediaValue = 'wpacu_preload_basic_' . $mediaValue;
|
||||
}
|
||||
|
||||
// Make the right reference for later use
|
||||
if ($linkAttributes['rel'] === 'preload') {
|
||||
if (isset($linkAttributes['data-wpacu-preload-css-basic'])) {
|
||||
$mediaValue = 'wpacu_preload_basic_' . $mediaValue;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Was it optimized and has the URL updated? Check the Source URL to determine if it should be skipped from combining
|
||||
if (isset($linkAttributes['data-wpacu-link-rel-href-before']) && $linkAttributes['data-wpacu-link-rel-href-before'] && self::skipCombine($linkAttributes['data-wpacu-link-rel-href-before'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid combining own plugin's CSS (irrelevant) as it takes extra useless space in the caching directory
|
||||
if (isset($linkAttributes['id']) && $linkAttributes['id'] === WPACU_PLUGIN_ID.'-style-css') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$localAssetPath = OptimizeCommon::getLocalAssetPath($href, 'css');
|
||||
|
||||
// It will skip external stylesheets (from a different domain)
|
||||
if ( $localAssetPath ) {
|
||||
$styleExtra = array();
|
||||
|
||||
if (isset($linkAttributes['data-wpacu-style-handle'], $wpacuRegisteredStyles[$linkAttributes['data-wpacu-style-handle']]->extra) && OptimizeCommon::appendInlineCodeToCombineAssetType('css')) {
|
||||
$styleExtra = $wpacuRegisteredStyles[$linkAttributes['data-wpacu-style-handle']]->extra;
|
||||
}
|
||||
|
||||
$sourceRelPath = OptimizeCommon::getSourceRelPath($href);
|
||||
|
||||
$alreadyAddedSourceRelPath = isset($combinedUriPathsGroup[$mediaValue]) && in_array($sourceRelPath, $combinedUriPathsGroup[$mediaValue]);
|
||||
if (! $alreadyAddedSourceRelPath) {
|
||||
$combinedUriPathsGroup[$mediaValue][] = $sourceRelPath;
|
||||
}
|
||||
|
||||
$localAssetsPathsGroup[$mediaValue][$href] = $localAssetPath;
|
||||
|
||||
$alreadyAddedHref = isset($linkHrefsGroup[$mediaValue]) && in_array($href, $linkHrefsGroup[$mediaValue]);
|
||||
if (! $alreadyAddedHref) {
|
||||
$linkHrefsGroup[$mediaValue][] = $href;
|
||||
}
|
||||
|
||||
$localAssetsExtraGroup[$mediaValue][$href] = $styleExtra;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No Link Tags or only one tag in the combined group? Do not proceed with any combining
|
||||
if ( empty( $combinedUriPathsGroup ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($combinedUriPathsGroup as $mediaValue => $combinedUriPaths) {
|
||||
// There have to be at least two CSS files to create a combined CSS file
|
||||
if (count($combinedUriPaths) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$localAssetsPaths = $localAssetsPathsGroup[$mediaValue];
|
||||
$linkHrefs = $linkHrefsGroup[$mediaValue];
|
||||
$localAssetsExtra = array_filter($localAssetsExtraGroup[$mediaValue]);
|
||||
|
||||
$maybeDoCssCombine = self::maybeDoCssCombine(
|
||||
$localAssetsPaths,
|
||||
$linkHrefs,
|
||||
$localAssetsExtra,
|
||||
$docLocationTag
|
||||
);
|
||||
|
||||
// Local path to combined CSS file
|
||||
$localFinalCssFile = $maybeDoCssCombine['local_final_css_file'];
|
||||
|
||||
// URI (e.g. /wp-content/cache/asset-cleanup/[file-name-here.css]) to the combined CSS file
|
||||
$uriToFinalCssFile = $maybeDoCssCombine['uri_final_css_file'];
|
||||
|
||||
// Any link hrefs removed perhaps if the file wasn't combined?
|
||||
$linkHrefs = $maybeDoCssCombine['link_hrefs'];
|
||||
|
||||
if (is_file($localFinalCssFile)) {
|
||||
$storageJsonContents[$docLocationTag][$mediaValue] = array(
|
||||
'uri_to_final_css_file' => $uriToFinalCssFile,
|
||||
'link_hrefs' => array_map(static function($href) {
|
||||
return str_replace('{site_url}', '', OptimizeCommon::getSourceRelPath($href));
|
||||
}, $linkHrefs)
|
||||
);
|
||||
|
||||
$storageJsonContentsToSave[$docLocationTag][$mediaValue] = array(
|
||||
'uri_to_final_css_file' => $uriToFinalCssFile,
|
||||
'link_hrefs' => array_map(static function($href) {
|
||||
return OptimizeCommon::getSourceRelPath($href);
|
||||
}, $linkHrefs)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libxml_clear_errors();
|
||||
|
||||
OptimizeCommon::setAssetCachedData(
|
||||
self::$jsonStorageFile,
|
||||
OptimizeCss::getRelPathCssCacheDir(),
|
||||
wp_json_encode($storageJsonContentsToSave)
|
||||
);
|
||||
}
|
||||
|
||||
$cdnUrls = OptimizeCommon::getAnyCdnUrls();
|
||||
$cdnUrlForCss = isset($cdnUrls['css']) ? $cdnUrls['css'] : false;
|
||||
|
||||
if ( ! empty($storageJsonContents) ) {
|
||||
foreach ($storageJsonContents as $docLocationTag => $mediaValues) {
|
||||
$groupLocation = 1;
|
||||
|
||||
foreach ($mediaValues as $mediaValue => $storageJsonContentLocation) {
|
||||
if (! isset($storageJsonContentLocation['link_hrefs'][0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Irrelevant to have only one CSS file in a combine CSS group
|
||||
if (count($storageJsonContentLocation['link_hrefs']) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storageJsonContentLocation['link_hrefs'] = array_map(static function($href) {
|
||||
return str_replace('{site_url}', '', $href);
|
||||
}, $storageJsonContentLocation['link_hrefs']);
|
||||
|
||||
$finalTagUrl = OptimizeCommon::filterWpContentUrl($cdnUrlForCss) . OptimizeCss::getRelPathCssCacheDir() . $storageJsonContentLocation['uri_to_final_css_file'];
|
||||
|
||||
$finalCssTagAttrs = array();
|
||||
|
||||
if (strpos($mediaValue, 'wpacu_preload_basic_') === 0) {
|
||||
// Put the right "media" value after cleaning the reference
|
||||
$mediaValueClean = str_replace('wpacu_preload_basic_', '', $mediaValue);
|
||||
|
||||
// Basic Preload
|
||||
$finalCssTag = <<<HTML
|
||||
<link rel='stylesheet' data-wpacu-to-be-preloaded-basic='1' id='wpacu-combined-css-{$docLocationTag}-{$groupLocation}-preload-it-basic' href='{$finalTagUrl}' type='text/css' media='{$mediaValueClean}' />
|
||||
HTML;
|
||||
$finalCssTagRelPreload = <<<HTML
|
||||
<link rel='preload' as='style' data-wpacu-preload-it-basic='1' id='wpacu-combined-css-{$docLocationTag}-{$groupLocation}-preload-it-basic' href='{$finalTagUrl}' type='text/css' media='{$mediaValueClean}' />
|
||||
HTML;
|
||||
|
||||
$finalCssTagAttrs['rel'] = 'preload';
|
||||
$finalCssTagAttrs['media'] = $mediaValueClean;
|
||||
|
||||
$htmlSource = str_replace(Preloads::DEL_STYLES_PRELOADS, $finalCssTagRelPreload."\n" . Preloads::DEL_STYLES_PRELOADS, $htmlSource);
|
||||
} else {
|
||||
// Render-blocking CSS
|
||||
$finalCssTag = <<<HTML
|
||||
<link rel='stylesheet' id='wpacu-combined-css-{$docLocationTag}-{$groupLocation}' href='{$finalTagUrl}' type='text/css' media='{$mediaValue}' />
|
||||
HTML;
|
||||
$finalCssTagAttrs['rel'] = 'stylesheet';
|
||||
$finalCssTagAttrs['media'] = $mediaValue;
|
||||
}
|
||||
|
||||
// In case one (e.g. usually a developer) needs to alter it
|
||||
$finalCssTag = apply_filters(
|
||||
'wpacu_combined_css_tag',
|
||||
$finalCssTag,
|
||||
array(
|
||||
'attrs' => $finalCssTagAttrs,
|
||||
'doc_location' => $docLocationTag,
|
||||
'group_no' => $groupLocation,
|
||||
'href' => $finalTagUrl
|
||||
)
|
||||
);
|
||||
|
||||
// Reference: https://stackoverflow.com/questions/2368539/php-replacing-multiple-spaces-with-a-single-space
|
||||
$finalCssTag = preg_replace('!\s+!', ' ', $finalCssTag);
|
||||
|
||||
$htmlSourceBeforeAnyLinkTagReplacement = $htmlSource;
|
||||
|
||||
// Detect first LINK tag from the <$locationTag> and replace it with the final combined LINK tag
|
||||
$firstLinkTag = OptimizeCss::getFirstLinkTag($storageJsonContentLocation['link_hrefs'][0], $htmlSource);
|
||||
|
||||
if ($firstLinkTag) {
|
||||
// 1) Strip inline code before/after it (if any)
|
||||
// 2) Finally, strip the actual tag
|
||||
$htmlSource = self::stripTagAndAnyInlineAssocCode( $firstLinkTag, $wpacuRegisteredStyles, $finalCssTag, $htmlSource );
|
||||
}
|
||||
|
||||
if ($htmlSource !== $htmlSourceBeforeAnyLinkTagReplacement) {
|
||||
$htmlSource = self::stripJustCombinedLinkTags(
|
||||
$storageJsonContentLocation['link_hrefs'],
|
||||
$wpacuRegisteredStyles,
|
||||
$htmlSource
|
||||
); // Strip the combined files to avoid duplicate code
|
||||
|
||||
// There should be at least two replacements made AND all the tags should have been replaced
|
||||
// Leave no room for errors, otherwise the page could end up with extra files loaded, leading to a slower website
|
||||
if ($htmlSource === 'do_not_combine') {
|
||||
$htmlSource = $htmlSourceBeforeAnyLinkTagReplacement;
|
||||
} else {
|
||||
$groupLocation++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $filesSources
|
||||
* @param $wpacuRegisteredStyles
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function stripJustCombinedLinkTags($filesSources, $wpacuRegisteredStyles, $htmlSource)
|
||||
{
|
||||
preg_match_all('#<link[^>]*(stylesheet|preload)[^>]*(>)#Umi', $htmlSource, $matchesSourcesFromTags, PREG_SET_ORDER);
|
||||
|
||||
$linkTagsStrippedNo = 0;
|
||||
|
||||
foreach ($matchesSourcesFromTags as $matchSourceFromTag) {
|
||||
$matchedSourceFromTag = (isset($matchSourceFromTag[0]) && strip_tags($matchSourceFromTag[0]) === '') ? trim($matchSourceFromTag[0]) : '';
|
||||
|
||||
if (! $matchSourceFromTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The DOMDocument is already checked if it's enabled in doCombine()
|
||||
$domTag = Misc::initDOMDocument();
|
||||
$domTag->loadHTML($matchedSourceFromTag);
|
||||
|
||||
foreach ($domTag->getElementsByTagName('link') as $tagObject) {
|
||||
if (empty($tagObject->attributes)) { continue; }
|
||||
|
||||
foreach ($tagObject->attributes as $tagAttrs) {
|
||||
if ($tagAttrs->nodeName === 'href') {
|
||||
$relNodeValue = trim(OptimizeCommon::getSourceRelPath($tagAttrs->nodeValue));
|
||||
|
||||
if (in_array($relNodeValue, $filesSources)) {
|
||||
$htmlSourceBeforeLinkTagReplacement = $htmlSource;
|
||||
|
||||
// 1) Strip inline code before/after it (if any)
|
||||
// 2) Finally, strip the actual tag
|
||||
$htmlSource = self::stripTagAndAnyInlineAssocCode( $matchedSourceFromTag, $wpacuRegisteredStyles, '', $htmlSource );
|
||||
|
||||
if ($htmlSource !== $htmlSourceBeforeLinkTagReplacement) {
|
||||
$linkTagsStrippedNo++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libxml_clear_errors();
|
||||
}
|
||||
|
||||
// Aren't all the LINK tags stripped? They should be, otherwise, do not proceed with the HTML alteration (no combining will take place)
|
||||
// Minus the already combined tag
|
||||
if (($linkTagsStrippedNo < 2) && (count($filesSources) !== $linkTagsStrippedNo)) {
|
||||
return 'do_not_combine';
|
||||
}
|
||||
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $href
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function skipCombine($href)
|
||||
{
|
||||
$regExps = array(
|
||||
'#/wp-content/bs-booster-cache/#'
|
||||
);
|
||||
|
||||
if (Main::instance()->settings['combine_loaded_css_exceptions'] !== '') {
|
||||
$loadedCssExceptionsPatterns = trim(Main::instance()->settings['combine_loaded_css_exceptions']);
|
||||
|
||||
if (strpos($loadedCssExceptionsPatterns, "\n")) {
|
||||
// Multiple values (one per line)
|
||||
foreach (explode("\n", $loadedCssExceptionsPatterns) as $loadedCssExceptionPattern) {
|
||||
$regExps[] = '#'.trim($loadedCssExceptionPattern).'#';
|
||||
}
|
||||
} else {
|
||||
// Only one value?
|
||||
$regExps[] = '#'.trim($loadedCssExceptionsPatterns).'#';
|
||||
}
|
||||
}
|
||||
|
||||
// No exceptions set? Do not skip combination
|
||||
if (empty($regExps)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($regExps as $regExp) {
|
||||
$regExp = Misc::purifyRegexValue($regExp);
|
||||
|
||||
if ( @preg_match( $regExp, $href ) || ( strpos($href, $regExp) !== false ) ) {
|
||||
// Skip combination
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $localAssetsPaths
|
||||
* @param $linkHrefs
|
||||
* @param $localAssetsExtra
|
||||
* @param $docLocationTag
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function maybeDoCssCombine($localAssetsPaths, $linkHrefs, $localAssetsExtra, $docLocationTag)
|
||||
{
|
||||
// Only combine if $shaOneCombinedUriPaths.css 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 CSS file will be generated and loaded
|
||||
|
||||
// Change $finalCombinedCssContent as paths to fonts and images that are relative (e.g. ../, ../../) have to be updated + other optimization changes
|
||||
$uriToFinalCssFile = $localFinalCssFile = $finalCombinedCssContent = '';
|
||||
|
||||
foreach ($localAssetsPaths as $assetHref => $localAssetsPath) {
|
||||
if ($cssContent = trim(FileSystem::fileGetContents($localAssetsPath, 'combine_css_imports'))) {
|
||||
$pathToAssetDir = OptimizeCommon::getPathToAssetDir($assetHref);
|
||||
|
||||
// Does it have a source map? Strip it
|
||||
if (strpos($cssContent, '/*# sourceMappingURL=') !== false) {
|
||||
$cssContent = OptimizeCommon::stripSourceMap($cssContent, 'css');
|
||||
}
|
||||
|
||||
if (apply_filters('wpacu_print_info_comments_in_cached_assets', true)) {
|
||||
$finalCombinedCssContent .= '/*!' . str_replace( Misc::getWpRootDirPath(), '/', $localAssetsPath ) . "*/\n";
|
||||
}
|
||||
|
||||
$finalCombinedCssContent .= OptimizeCss::maybeFixCssContent($cssContent, $pathToAssetDir . '/') . "\n";
|
||||
|
||||
$finalCombinedCssContent = self::appendToCombineCss($localAssetsExtra, $assetHref, $pathToAssetDir, $finalCombinedCssContent);
|
||||
}
|
||||
}
|
||||
|
||||
// Move any @imports to top; This also strips any @imports to Google Fonts if the option is chosen
|
||||
$finalCombinedCssContent = trim(OptimizeCss::importsUpdate($finalCombinedCssContent));
|
||||
|
||||
if (Main::instance()->settings['google_fonts_remove']) {
|
||||
$finalCombinedCssContent = FontsGoogleRemove::cleanFontFaceReferences($finalCombinedCssContent);
|
||||
}
|
||||
|
||||
$finalCombinedCssContent = apply_filters('wpacu_local_fonts_display_css_output', $finalCombinedCssContent, Main::instance()->settings['local_fonts_display']);
|
||||
|
||||
if ($finalCombinedCssContent) {
|
||||
$finalCombinedCssContent = trim($finalCombinedCssContent);
|
||||
$shaOneForCombinedCss = sha1($finalCombinedCssContent);
|
||||
|
||||
$uriToFinalCssFile = $docLocationTag . '-' .$shaOneForCombinedCss . '.css';
|
||||
$localFinalCssFile = WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() . $uriToFinalCssFile;
|
||||
|
||||
if (! is_file($localFinalCssFile)) {
|
||||
FileSystem::filePutContents($localFinalCssFile, $finalCombinedCssContent);
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'uri_final_css_file' => $uriToFinalCssFile,
|
||||
'local_final_css_file' => $localFinalCssFile,
|
||||
'link_hrefs' => $linkHrefs
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $localAssetsExtra
|
||||
* @param $assetHref
|
||||
* @param $pathToAssetDir
|
||||
* @param $finalAssetsContents
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function appendToCombineCss($localAssetsExtra, $assetHref, $pathToAssetDir, $finalAssetsContents)
|
||||
{
|
||||
if (isset($localAssetsExtra[$assetHref]['after']) && ! empty($localAssetsExtra[$assetHref]['after'])) {
|
||||
$afterCssContent = '';
|
||||
|
||||
foreach ($localAssetsExtra[$assetHref]['after'] as $afterData) {
|
||||
if (! is_bool($afterData)) {
|
||||
$afterCssContent .= $afterData."\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (trim($afterCssContent)) {
|
||||
if (MinifyCss::isMinifyCssEnabled() && in_array(Main::instance()->settings['minify_loaded_css_for'], array('inline', 'all'))) {
|
||||
$afterCssContent = MinifyCss::applyMinification( $afterCssContent );
|
||||
}
|
||||
|
||||
$afterCssContent = OptimizeCss::maybeFixCssContent( $afterCssContent, $pathToAssetDir . '/' );
|
||||
|
||||
$finalAssetsContents .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [inline: after] */' : '';
|
||||
$finalAssetsContents .= $afterCssContent;
|
||||
$finalAssetsContents .= apply_filters('wpacu_print_info_comments_in_cached_assets', true) ? '/* [/inline: after] */' : '';
|
||||
$finalAssetsContents .= "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $finalAssetsContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* The targeted LINK tag (which was enqueued and has a handle) is replaced with $replaceWith
|
||||
* along with any inline content that was added after it via wp_add_inline_style()
|
||||
*
|
||||
* @param $targetedLinkTag
|
||||
* @param $wpacuRegisteredStyles
|
||||
* @param $replaceWith
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function stripTagAndAnyInlineAssocCode($targetedLinkTag, $wpacuRegisteredStyles, $replaceWith, $htmlSource)
|
||||
{
|
||||
if (OptimizeCommon::appendInlineCodeToCombineAssetType('css')) {
|
||||
$scriptExtrasHtml = OptimizeCss::getInlineAssociatedWithLinkHandle($targetedLinkTag, $wpacuRegisteredStyles, 'tag', 'html');
|
||||
$scriptExtraAfterHtml = (isset($scriptExtrasHtml['after']) && $scriptExtrasHtml['after']) ? "\n".$scriptExtrasHtml['after'] : '';
|
||||
|
||||
$htmlSource = str_replace(
|
||||
array(
|
||||
$targetedLinkTag . $scriptExtraAfterHtml,
|
||||
$targetedLinkTag . trim($scriptExtraAfterHtml)
|
||||
),
|
||||
$replaceWith,
|
||||
$htmlSource
|
||||
);
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
array(
|
||||
$targetedLinkTag."\n",
|
||||
$targetedLinkTag
|
||||
),
|
||||
$replaceWith."\n",
|
||||
$htmlSource
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function proceedWithCssCombine()
|
||||
{
|
||||
// Not on query string request (debugging purposes)
|
||||
if ( ! empty($_REQUEST) && array_key_exists('wpacu_no_css_combine', $_REQUEST) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No CSS 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 CSS file larger
|
||||
if (! empty($_POST) || is_admin()) {
|
||||
return false; // Do not combine
|
||||
}
|
||||
|
||||
// Only clean request URIs allowed (with Exceptions)
|
||||
// Exceptions
|
||||
if ((strpos($_SERVER['REQUEST_URI'], '?') !== false) && ! 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 and the user is in guest mode (not logged-in)
|
||||
}
|
||||
|
||||
if ($pluginSettings['combine_loaded_css'] === '') {
|
||||
return false; // Do not combine
|
||||
}
|
||||
|
||||
if (OptimizeCss::isOptimizeCssEnabledByOtherParty('if_enabled')) {
|
||||
return false; // Do not combine (it's already enabled in other plugin)
|
||||
}
|
||||
|
||||
// "Minify HTML" from WP Rocket is sometimes stripping combined LINK tags
|
||||
// Better uncombined then missing essential CSS files
|
||||
if (Misc::isWpRocketMinifyHtmlEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
// The option is no longer used since v1.1.7.3 (Pro) & v1.3.6.4 (Lite)
|
||||
if ( ($pluginSettings['combine_loaded_css'] === 'for_admin'
|
||||
|| $pluginSettings['combine_loaded_css_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_css_for'] === 'guests' && is_user_logged_in() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($pluginSettings['combine_loaded_css'], array('for_all', 1)) ) {
|
||||
return true; // Do combine
|
||||
}
|
||||
|
||||
// Finally, return false as none of the verification above matched
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,514 @@
|
||||
<?php
|
||||
namespace WpAssetCleanUp\OptimiseAssets;
|
||||
|
||||
use MatthiasMullie\Minify\Minify;
|
||||
|
||||
use MatthiasMullie\PathConverter\ConverterInterface;
|
||||
use MatthiasMullie\PathConverter\Converter;
|
||||
|
||||
/**
|
||||
* Combine CSS Imports extended from CSS minifier
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @package Minify
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @author Tijs Verkoyen <minify@verkoyen.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
||||
* @license MIT License
|
||||
*/
|
||||
class CombineCssImports extends Minify
|
||||
{
|
||||
/**
|
||||
* @var int maximum import size in kB
|
||||
*/
|
||||
protected $maxImportSize = 5;
|
||||
|
||||
/**
|
||||
* @var string[] valid import extensions
|
||||
*/
|
||||
protected $importExtensions = array(
|
||||
'gif' => 'data:image/gif',
|
||||
'png' => 'data:image/png',
|
||||
'jpe' => 'data:image/jpeg',
|
||||
'jpg' => 'data:image/jpeg',
|
||||
'jpeg' => 'data:image/jpeg',
|
||||
'svg' => 'data:image/svg+xml',
|
||||
'woff' => 'data:application/x-font-woff',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'xbm' => 'image/x-xbitmap',
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the maximum size if files to be imported.
|
||||
*
|
||||
* Files larger than this size (in kB) will not be imported into the CSS.
|
||||
* Importing files into the CSS as data-uri will save you some connections,
|
||||
* but we should only import relatively small decorative images so that our
|
||||
* CSS file doesn't get too bulky.
|
||||
*
|
||||
* @param int $size Size in kB
|
||||
*/
|
||||
public function setMaxImportSize($size)
|
||||
{
|
||||
$this->maxImportSize = $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of extensions to be imported into the CSS (to save network
|
||||
* connections).
|
||||
* Keys of the array should be the file extensions & respective values
|
||||
* should be the data type.
|
||||
*
|
||||
* @param string[] $extensions Array of file extensions
|
||||
*/
|
||||
public function setImportExtensions(array $extensions)
|
||||
{
|
||||
$this->importExtensions = $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move any import statements to the top.
|
||||
*
|
||||
* @param string $content Nearly finished CSS content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function moveImportsToTop($content)
|
||||
{
|
||||
if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) {
|
||||
// remove from content
|
||||
foreach ($matches[0] as $import) {
|
||||
$content = str_replace($import, '', $content);
|
||||
}
|
||||
|
||||
// add to top
|
||||
$content = implode(';', $matches[2]).';'.trim($content, ';');
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine CSS from import statements.
|
||||
*
|
||||
* @import's will be loaded and their content merged into the original file,
|
||||
* to save HTTP requests.
|
||||
*
|
||||
* @param string $source The file to combine imports for
|
||||
* @param string $content The CSS content to combine imports for
|
||||
* @param string[] $parents Parent paths, for circular reference checks
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
protected function combineImports($source, $content, $parents)
|
||||
{
|
||||
$importRegexes = array(
|
||||
// @import url(xxx)
|
||||
'/
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# open url()
|
||||
url\(
|
||||
|
||||
# (optional) open path enclosure
|
||||
(?P<quotes>["\']?)
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# (optional) close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
# close url()
|
||||
\)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) media statement(s)
|
||||
(?P<media>[^;]*)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) closing semi-colon
|
||||
;?
|
||||
|
||||
/ix',
|
||||
|
||||
// @import 'xxx'
|
||||
'/
|
||||
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) media statement(s)
|
||||
(?P<media>[^;]*)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) closing semi-colon
|
||||
;?
|
||||
|
||||
/ix',
|
||||
);
|
||||
|
||||
// find all relative imports in css
|
||||
$matches = array();
|
||||
foreach ($importRegexes as $importRegex) {
|
||||
if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
|
||||
$matches = array_merge($matches, $regexMatches);
|
||||
}
|
||||
}
|
||||
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop the matches
|
||||
foreach ($matches as $match) {
|
||||
// get the path for the file that will be imported
|
||||
$importPath = dirname($source).'/'.$match['path'];
|
||||
|
||||
// only replace the import with the content if we can grab the
|
||||
// content of the file
|
||||
if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if current file was not imported previously in the same
|
||||
// import chain.
|
||||
if (in_array($importPath, $parents)) {
|
||||
// No need to have and endless loop (the same file imported again and again)
|
||||
$search[] = $match[0];
|
||||
$replace[] = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
// grab referenced file & optimize it (which may include importing
|
||||
// yet other @import statements recursively)
|
||||
$minifier = new static($importPath);
|
||||
$minifier->setMaxImportSize($this->maxImportSize);
|
||||
$minifier->setImportExtensions($this->importExtensions);
|
||||
$importContent = $minifier->execute($source, $parents);
|
||||
|
||||
// check if this is only valid for certain media
|
||||
if (!empty($match['media'])) {
|
||||
$importContent = '@media '.$match['media'].'{'.$importContent.'}';
|
||||
}
|
||||
|
||||
// add to replacement array
|
||||
$search[] = $match[0];
|
||||
$replace[] = $importContent;
|
||||
}
|
||||
|
||||
// replace the import statements
|
||||
return str_replace($search, $replace, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $css
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function alterImportsBetweenComments($css)
|
||||
{
|
||||
// RegEx Source: https://blog.ostermiller.org/finding-comments-in-source-code-using-regular-expressions/
|
||||
preg_match_all('#/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/#', $css, $commentsMatches);
|
||||
|
||||
if (isset($commentsMatches[0]) && ! empty($commentsMatches[0])) {
|
||||
foreach ($commentsMatches[0] as $commentMatch) {
|
||||
if (strpos($commentMatch, '@import') === false) {
|
||||
continue; // the comment needs to have @import
|
||||
}
|
||||
|
||||
$newComment = str_replace('@import', '(wpacu)(at)import', $commentMatch);
|
||||
$css = str_replace($commentMatch, $newComment, $css);
|
||||
}
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import files into the CSS, base64-sized.
|
||||
*
|
||||
* @url(image.jpg) images will be loaded and their content merged into the
|
||||
* original file, to save HTTP requests.
|
||||
*
|
||||
* @param string $source The file to import files for
|
||||
* @param string $content The CSS content to import files for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function importFiles($source, $content)
|
||||
{
|
||||
$regex = '/url\((["\']?)(.+?)\\1\)/i';
|
||||
if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop the matches
|
||||
foreach ($matches as $match) {
|
||||
$extension = substr(strrchr($match[2], '.'), 1);
|
||||
if ($extension && !array_key_exists($extension, $this->importExtensions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the path for the file that will be imported
|
||||
$path = $match[2];
|
||||
$path = dirname($source).'/'.$path;
|
||||
|
||||
// only replace the import with the content if we're able to get
|
||||
// the content of the file, and it's relatively small
|
||||
if ($this->canImportFile($path) && $this->canImportBySize($path)) {
|
||||
// grab content && base64-ize
|
||||
$importContent = $this->load($path);
|
||||
$importContent = base64_encode($importContent);
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
$replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
|
||||
}
|
||||
}
|
||||
|
||||
// replace the import statements
|
||||
$content = str_replace($search, $replace, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform CSS optimizations.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to
|
||||
* @param string[] $parents Parent paths, for circular reference checks
|
||||
*
|
||||
* @return string The minified data
|
||||
*/
|
||||
public function execute($path = null, $parents = array())
|
||||
{
|
||||
$content = '';
|
||||
|
||||
// loop CSS data (raw data and files)
|
||||
foreach ($this->data as $source => $css) {
|
||||
// Some developers might have wrapped @import between comments
|
||||
// No import for those
|
||||
$css = $this->alterImportsBetweenComments($css);
|
||||
|
||||
$source = is_int($source) ? '' : $source;
|
||||
$parents = $source ? array_merge($parents, array($source)) : $parents;
|
||||
$css = $this->combineImports($source, $css, $parents);
|
||||
$css = $this->importFiles($source, $css);
|
||||
|
||||
/*
|
||||
* If we'll save to a new path, we'll have to fix the relative paths
|
||||
* to be relative no longer to the source file, but to the new path.
|
||||
* If we don't write to a file, fall back to same path so no
|
||||
* conversion happens (because we still want it to go through most
|
||||
* of the move code, which also addresses url() & @import syntax...)
|
||||
*/
|
||||
$converter = $this->getPathConverter($source, $path ?: $source);
|
||||
$css = $this->move($converter, $css);
|
||||
|
||||
// combine css
|
||||
$content .= $css;
|
||||
}
|
||||
|
||||
$content = $this->moveImportsToTop($content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moving a css file should update all relative urls.
|
||||
* Relative references (e.g. ../images/image.gif) in a certain css file,
|
||||
* will have to be updated when a file is being saved at another location
|
||||
* (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
|
||||
*
|
||||
* @param ConverterInterface $converter Relative path converter
|
||||
* @param string $content The CSS content to update relative urls for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function move(ConverterInterface $converter, $content)
|
||||
{
|
||||
/*
|
||||
* Relative path references will usually be enclosed by url(). @import
|
||||
* is an exception, where url() is not necessary around the path (but is
|
||||
* allowed).
|
||||
* This *could* be 1 regular expression, where both regular expressions
|
||||
* in this array are on different sides of a |. But we're using named
|
||||
* patterns in both regexes, the same name on both regexes. This is only
|
||||
* possible with a (?J) modifier, but that only works after a fairly
|
||||
* recent PCRE version. That's why I'm doing 2 separate regular
|
||||
* expressions & combining the matches after executing of both.
|
||||
*/
|
||||
$relativeRegexes = array(
|
||||
// url(xxx)
|
||||
'/
|
||||
# open url()
|
||||
url\(
|
||||
|
||||
\s*
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])?
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure
|
||||
(?(quotes)(?P=quotes))
|
||||
|
||||
\s*
|
||||
|
||||
# close url()
|
||||
\)
|
||||
|
||||
/ix',
|
||||
|
||||
// @import "xxx"
|
||||
'/
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# we don\'t have to check for @import url(), because the
|
||||
# condition above will already catch these
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
/ix',
|
||||
);
|
||||
|
||||
// find all relative urls in css
|
||||
$matches = array();
|
||||
foreach ($relativeRegexes as $relativeRegex) {
|
||||
if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
|
||||
$matches = array_merge($matches, $regexMatches);
|
||||
}
|
||||
}
|
||||
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop all urls
|
||||
foreach ($matches as $match) {
|
||||
// determine if it's an url() or an @import match
|
||||
$type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
|
||||
|
||||
$url = $match['path'];
|
||||
if ($this->canImportByPath($url)) {
|
||||
// attempting to interpret GET-params makes no sense, so let's discard them for a while
|
||||
$params = strrchr($url, '?');
|
||||
$url = $params ? substr($url, 0, -strlen($params)) : $url;
|
||||
|
||||
// fix relative url
|
||||
$url = $converter->convert($url);
|
||||
|
||||
// now that the path has been converted, re-apply GET-params
|
||||
$url .= $params;
|
||||
}
|
||||
|
||||
/*
|
||||
* Urls with control characters above 0x7e should be quoted.
|
||||
* According to Mozilla's parser, whitespace is only allowed at the
|
||||
* end of unquoted urls.
|
||||
* Urls with `)` (as could happen with data: uris) should also be
|
||||
* quoted to avoid being confused for the url() closing parentheses.
|
||||
* And urls with a # have also been reported to cause issues.
|
||||
* Urls with quotes inside should also remain escaped.
|
||||
*
|
||||
* @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
|
||||
* @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
|
||||
* @see https://github.com/matthiasmullie/minify/issues/193
|
||||
*/
|
||||
$url = trim($url);
|
||||
if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) {
|
||||
$url = $match['quotes'] . $url . $match['quotes'];
|
||||
}
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
if ($type === 'url') {
|
||||
$replace[] = 'url('.$url.')';
|
||||
} elseif ($type === 'import') {
|
||||
$replace[] = '@import "'.$url.'"';
|
||||
}
|
||||
}
|
||||
|
||||
// replace urls
|
||||
return str_replace($search, $replace, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file is small enough to be imported.
|
||||
*
|
||||
* @param string $path The path to the file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canImportBySize($path)
|
||||
{
|
||||
return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file a file can be imported, going by the path.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canImportByPath($path)
|
||||
{
|
||||
return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a converter to update relative paths to be relative to the new
|
||||
* destination.
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
*
|
||||
* @return ConverterInterface
|
||||
*/
|
||||
protected function getPathConverter($source, $target)
|
||||
{
|
||||
return new Converter($source, $target);
|
||||
}
|
||||
}
|
@ -0,0 +1,920 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace WpAssetCleanUp\OptimiseAssets;
|
||||
|
||||
/**
|
||||
* Class DynamicLoadedAssets
|
||||
* @package WpAssetCleanUp
|
||||
*/
|
||||
class DynamicLoadedAssets
|
||||
{
|
||||
/**
|
||||
* @param $from
|
||||
* @param $value
|
||||
*
|
||||
* @return bool|mixed|string
|
||||
*/
|
||||
public static function getAssetContentFrom($from, $value)
|
||||
{
|
||||
$assetContent = '';
|
||||
|
||||
if ($from === 'simple-custom-css') {
|
||||
/*
|
||||
* Special Case: "Simple Custom CSS" Plugin
|
||||
*
|
||||
* /?sccss=1
|
||||
*
|
||||
* As it is (no minification or optimization), it adds extra load time to the page
|
||||
* as the CSS is read via PHP and all the WP environment is loading
|
||||
*/
|
||||
if (! $assetContent = self::getSimpleCustomCss()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($from === 'dynamic') { // /? .php? etc.
|
||||
if (! OptimizeCommon::isSourceFromSameHost($value->src)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$response = wp_remote_get(
|
||||
$value->src
|
||||
);
|
||||
|
||||
if (wp_remote_retrieve_response_code($response) !== 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $assetContent = wp_remote_retrieve_body($response)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $assetContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Simple Custom CSS" (better retrieval, especially for localhost and password-protected sites)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSimpleCustomCss()
|
||||
{
|
||||
$sccssOptions = get_option('sccss_settings');
|
||||
$sccssRawContent = isset($sccssOptions['sccss-content']) ? $sccssOptions['sccss-content'] : '';
|
||||
$cssContent = wp_kses($sccssRawContent, array('\'', '\"'));
|
||||
$cssContent = str_replace('>', '>', $cssContent);
|
||||
|
||||
return trim($cssContent);
|
||||
}
|
||||
}
|
@ -0,0 +1,764 @@
|
||||
<?php
|
||||
namespace WpAssetCleanUp\OptimiseAssets;
|
||||
|
||||
use WpAssetCleanUp\Main;
|
||||
use WpAssetCleanUp\Misc;
|
||||
use WpAssetCleanUp\Plugin;
|
||||
|
||||
/**
|
||||
* Class FontsGoogle
|
||||
* @package WpAssetCleanUp\OptimiseAssets
|
||||
*/
|
||||
class FontsGoogle
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $containsStr = '//fonts.googleapis.com/';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $matchesStr = '//fonts.googleapis.com/(css|icon)\?';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const NOSCRIPT_WEB_FONT_LOADER = '<span style="display: none;" data-name=wpacu-delimiter content="ASSET CLEANUP NOSCRIPT WEB FONT LOADER"></span>';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const COMBINED_LINK_DEL = '<span style="display: none;" data-name=wpacu-delimiter content="ASSET CLEANUP COMBINED LINK LOCATION"></span>';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if (self::preventAnyChange()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter('wp_resource_hints', array($this, 'resourceHints'), PHP_INT_MAX, 2);
|
||||
|
||||
add_action('wp_head', array($this, 'preloadFontFiles'), 1);
|
||||
add_action('wp_footer', static function() {
|
||||
if ( Plugin::preventAnyFrontendOptimization() || Main::isTestModeActive() || Main::instance()->settings['google_fonts_remove'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo self::NOSCRIPT_WEB_FONT_LOADER;
|
||||
}, PHP_INT_MAX);
|
||||
|
||||
add_filter('wpacu_html_source_after_optimization', static function($htmlSource) {
|
||||
// Is the mark still there and wasn't replaced? Strip it
|
||||
return str_replace(FontsGoogle::NOSCRIPT_WEB_FONT_LOADER, '', $htmlSource);
|
||||
});
|
||||
|
||||
add_action('init', function() {
|
||||
// don't apply any changes if not in the front-end view (e.g. Dashboard view)
|
||||
// or test mode is enabled and a guest user is accessing the page
|
||||
if ( Plugin::preventAnyFrontendOptimization() || Main::isTestModeActive() || Main::instance()->settings['google_fonts_remove'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter('style_loader_src', array($this, 'alterGoogleFontLink'));
|
||||
}, 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $urls
|
||||
* @param $relationType
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function resourceHints($urls, $relationType)
|
||||
{
|
||||
// don't apply any changes if not in the front-end view (e.g. Dashboard view)
|
||||
// or test mode is enabled and a guest user is accessing the page
|
||||
if (is_admin() || Main::isTestModeActive() || Plugin::preventAnyFrontendOptimization()) {
|
||||
return $urls;
|
||||
}
|
||||
|
||||
// Are the Google Fonts removed? Do not add it and strip any existing ones
|
||||
if (! empty($urls) && Main::instance()->settings['google_fonts_remove']) {
|
||||
foreach ($urls as $urlKey => $urlValue) {
|
||||
if (is_string($urlValue) && ((stripos($urlValue, 'fonts.googleapis.com') !== false) || (stripos($urlValue, 'fonts.gstatic.com') !== false))) {
|
||||
unset($urls[$urlKey]);
|
||||
}
|
||||
}
|
||||
|
||||
return $urls; // Finally, return the list after any removals
|
||||
}
|
||||
|
||||
// Google Fonts "preconnect"
|
||||
if ('preconnect' === $relationType
|
||||
&& ! Main::instance()->settings['google_fonts_remove'] // "Remove Google Fonts" has to be turned off
|
||||
&& Main::instance()->settings['google_fonts_preconnect']) { // Needs to be enabled within "Plugin Usage Preferences" in "Settings"
|
||||
$urls[] = array(
|
||||
'href' => 'https://fonts.gstatic.com/',
|
||||
'crossorigin'
|
||||
);
|
||||
}
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function preloadFontFiles()
|
||||
{
|
||||
// don't apply any changes if not in the front-end view (e.g. Dashboard view)
|
||||
// or test mode is enabled and a guest user is accessing the page
|
||||
if ( Plugin::preventAnyFrontendOptimization() || Main::isTestModeActive() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $preloadFontFiles = trim(Main::instance()->settings['google_fonts_preload_files'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$preloadFontFilesArray = array();
|
||||
|
||||
if (strpos($preloadFontFiles, "\n") !== false) {
|
||||
foreach (explode("\n", $preloadFontFiles) as $preloadFontFile) {
|
||||
$preloadFontFile = trim($preloadFontFile);
|
||||
|
||||
if (! $preloadFontFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$preloadFontFilesArray[] = $preloadFontFile;
|
||||
}
|
||||
} else {
|
||||
$preloadFontFilesArray[] = $preloadFontFiles;
|
||||
}
|
||||
|
||||
$preloadFontFilesArray = array_unique($preloadFontFilesArray);
|
||||
|
||||
$preloadFontFilesOutput = '';
|
||||
|
||||
// Finally, go through the list
|
||||
foreach ($preloadFontFilesArray as $preloadFontFile) {
|
||||
$preloadFontFilesOutput .= '<link rel="preload" as="font" href="'.esc_attr($preloadFontFile).'" data-wpacu-preload-font="1" crossorigin>'."\n";
|
||||
}
|
||||
|
||||
echo apply_filters('wpacu_preload_google_font_files_output', $preloadFontFilesOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return false|mixed|string|void
|
||||
*/
|
||||
public static function alterHtmlSource($htmlSource)
|
||||
{
|
||||
// don't apply any changes if not in the front-end view (e.g. Dashboard view)
|
||||
// or test mode is enabled and a guest user is accessing the page
|
||||
// or an AMP page is accessed
|
||||
if ( Plugin::preventAnyFrontendOptimization() || Main::isTestModeActive()) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove Google Fonts? Stop here as optimization is no longer relevant
|
||||
*/
|
||||
if (Main::instance()->settings['google_fonts_remove']) {
|
||||
return FontsGoogleRemove::cleanHtmlSource($htmlSource);
|
||||
}
|
||||
|
||||
/*
|
||||
* Optimize Google Fonts
|
||||
*/
|
||||
if (stripos($htmlSource, self::$containsStr) !== false) {
|
||||
// Cleaner HTML Source
|
||||
$altHtmlSource = preg_replace( '@<(script|style|noscript)[^>]*?>.*?</\\1>@si', '', $htmlSource ); // strip irrelevant tags for the collection
|
||||
$altHtmlSource = preg_replace( '/<!--[^>]*' . preg_quote( self::$containsStr, '/' ) . '.*?-->/', '', $altHtmlSource ); // strip any comments containing the string
|
||||
|
||||
// Get all valid LINKs that have the $string within them
|
||||
preg_match_all( '#<link[^>]*' . self::$matchesStr . '.*(>)#Usmi', $altHtmlSource, $matchesFromLinkTags, PREG_SET_ORDER );
|
||||
|
||||
// Needs to match at least one to carry on with the replacements
|
||||
if ( isset( $matchesFromLinkTags[0] ) && ! empty( $matchesFromLinkTags[0] ) ) {
|
||||
if ( Main::instance()->settings['google_fonts_combine'] ) {
|
||||
/*
|
||||
* "Combine Google Fonts" IS enabled
|
||||
*/
|
||||
$finalCombinableLinks = $preloadedLinks = array();
|
||||
|
||||
foreach ( $matchesFromLinkTags as $linkTagArray ) {
|
||||
$linkTag = $finalLinkTag = trim( trim( $linkTagArray[0], '"\'' ) );
|
||||
|
||||
// Extra checks to make sure it's a valid LINK tag
|
||||
if ( ( strpos( $linkTag, "'" ) !== false && ( substr_count( $linkTag, "'" ) % 2 ) )
|
||||
|| ( strpos( $linkTag, '"' ) !== false && ( substr_count( $linkTag, '"' ) % 2 ) )
|
||||
|| ( trim( strip_tags( $linkTag ) ) !== '' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the CSS has any 'data-wpacu-skip' attribute; if it does, do not continue and leave it as it is (non-combined)
|
||||
if ( preg_match( '#data-wpacu-skip([=>/ ])#i', $linkTag ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$finalLinkHref = $linkHrefOriginal = Misc::getValueFromTag($linkTag);
|
||||
|
||||
// [START] Remove invalid requests with no font family
|
||||
$urlParse = parse_url( str_replace( '&', '&', $linkHrefOriginal ), PHP_URL_QUERY );
|
||||
parse_str( $urlParse, $qStr );
|
||||
|
||||
if ( isset( $qStr['family'] ) && ! $qStr['family'] ) {
|
||||
$htmlSource = str_replace( $linkTag, '', $htmlSource );
|
||||
continue;
|
||||
}
|
||||
// [END] Remove invalid requests with no font family
|
||||
|
||||
// If anything is set apart from '[none set]', proceed
|
||||
if ( Main::instance()->settings['google_fonts_display'] ) {
|
||||
$finalLinkHref = self::alterGoogleFontLink( $linkHrefOriginal );
|
||||
|
||||
if ( $finalLinkHref !== $linkHrefOriginal ) {
|
||||
$finalLinkTag = str_replace( $linkHrefOriginal, $finalLinkHref, $linkTag );
|
||||
|
||||
// Finally, alter the HTML source
|
||||
$htmlSource = str_replace( $linkTag, $finalLinkTag, $htmlSource );
|
||||
}
|
||||
}
|
||||
|
||||
if ( preg_match( '/rel=(["\'])preload(["\'])/i', $finalLinkTag )
|
||||
|| strpos( $finalLinkTag, 'data-wpacu-to-be-preloaded-basic' ) ) {
|
||||
$preloadedLinks[] = $finalLinkHref;
|
||||
}
|
||||
|
||||
$finalCombinableLinks[] = array( 'href' => $finalLinkHref, 'tag' => $finalLinkTag );
|
||||
}
|
||||
|
||||
$preloadedLinks = array_unique( $preloadedLinks );
|
||||
|
||||
// Remove data for preloaded LINKs
|
||||
if ( ! empty( $preloadedLinks ) ) {
|
||||
foreach ( $finalCombinableLinks as $fclIndex => $combinableLinkData ) {
|
||||
if ( in_array( $combinableLinkData['href'], $preloadedLinks ) ) {
|
||||
unset( $finalCombinableLinks[ $fclIndex ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$finalCombinableLinks = array_values( $finalCombinableLinks );
|
||||
|
||||
// Only proceed with the optimization/combine if there's obviously at least 2 combinable URL requests to Google Fonts
|
||||
// OR the loading type is different from render-blocking
|
||||
if ( Main::instance()->settings['google_fonts_combine_type'] || count( $finalCombinableLinks ) > 1 ) {
|
||||
$htmlSource = self::combineGoogleFontLinks( $finalCombinableLinks, $htmlSource );
|
||||
}
|
||||
} elseif (Main::instance()->settings['google_fonts_display']) {
|
||||
/*
|
||||
* "Combine Google Fonts" IS NOT enabled
|
||||
* Go through the links and apply any "font-display"
|
||||
*/
|
||||
foreach ( $matchesFromLinkTags as $linkTagArray ) {
|
||||
$linkTag = trim( trim( $linkTagArray[0], '"\'' ) );
|
||||
|
||||
// Extra checks to make sure it's a valid LINK tag
|
||||
if ( ( strpos( $linkTag, "'" ) !== false && ( substr_count( $linkTag, "'" ) % 2 ) )
|
||||
|| ( strpos( $linkTag, '"' ) !== false && ( substr_count( $linkTag, '"' ) % 2 ) )
|
||||
|| ( trim( strip_tags( $linkTag ) ) !== '' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the CSS has any 'data-wpacu-skip' attribute; if it does, do not continue and leave it as it is (non-altered)
|
||||
if ( preg_match( '#data-wpacu-skip([=>/ ])#i', $linkTag ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$linkHrefOriginal = Misc::getValueFromTag($linkTag);
|
||||
|
||||
// [START] Remove invalid requests with no font family
|
||||
$urlParse = parse_url( str_replace( '&', '&', $linkHrefOriginal ), PHP_URL_QUERY );
|
||||
parse_str( $urlParse, $qStr );
|
||||
|
||||
if ( isset( $qStr['family'] ) && ! $qStr['family'] ) {
|
||||
$htmlSource = str_replace( $linkTag, '', $htmlSource );
|
||||
continue;
|
||||
}
|
||||
// [END] Remove invalid requests with no font family
|
||||
|
||||
// If anything is set apart from '[none set]', proceed
|
||||
$newLinkHref = self::alterGoogleFontLink( $linkHrefOriginal );
|
||||
|
||||
if ( $newLinkHref !== $linkHrefOriginal ) {
|
||||
$finalLinkTag = str_replace( $linkHrefOriginal, $newLinkHref, $linkTag );
|
||||
|
||||
// Finally, alter the HTML source
|
||||
$htmlSource = str_replace( $linkTag, $finalLinkTag, $htmlSource );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "font-display: swap;" if enabled
|
||||
$htmlSource = self::alterGoogleFontUrlFromInlineStyleTags($htmlSource);
|
||||
|
||||
// Clear any traces
|
||||
return str_replace(self::NOSCRIPT_WEB_FONT_LOADER, '', $htmlSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $linkHrefOriginal
|
||||
* @param bool $escHtml
|
||||
* @param $alterFor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function alterGoogleFontLink($linkHrefOriginal, $escHtml = true, $alterFor = 'css')
|
||||
{
|
||||
$isInVar = false; // The link is inside a variable with a JSON format
|
||||
|
||||
// Some special filtering here as some hosting environments (at least staging) behave funny with // inside SCRIPT tags
|
||||
if ($alterFor === 'js') {
|
||||
$containsStrNoSlashes = str_replace('/', '', self::$containsStr);
|
||||
$conditionOne = stripos($linkHrefOriginal, $containsStrNoSlashes) === false;
|
||||
|
||||
if (strpos($linkHrefOriginal, '\/') !== false) {
|
||||
$isInVar = true;
|
||||
}
|
||||
} else { // css (default)
|
||||
$conditionOne = stripos($linkHrefOriginal, self::$containsStr) === false;
|
||||
}
|
||||
|
||||
// Do not continue if it doesn't contain the right string, or it contains 'display=' or it does not contain 'family=' or there is no value set for "font-display"
|
||||
if ($conditionOne ||
|
||||
stripos($linkHrefOriginal, 'display=') !== false ||
|
||||
stripos($linkHrefOriginal, 'family=') === false ||
|
||||
! Main::instance()->settings['google_fonts_display']) {
|
||||
// Return original source
|
||||
return $linkHrefOriginal;
|
||||
}
|
||||
|
||||
$altLinkHref = str_replace('&', '&', $linkHrefOriginal);
|
||||
|
||||
if ($isInVar) {
|
||||
$altLinkHref = str_replace('\/', '/', $altLinkHref);
|
||||
}
|
||||
|
||||
$urlQuery = parse_url($altLinkHref, PHP_URL_QUERY);
|
||||
parse_str($urlQuery, $outputStr);
|
||||
|
||||
// Is there no "display" or there is, but it has an empty value? Append the one we have in the "Settings" - "Google Fonts"
|
||||
if ( ! isset($outputStr['display']) || (isset($outputStr['display']) && $outputStr['display'] === '') ) {
|
||||
$outputStr['display'] = Main::instance()->settings['google_fonts_display'];
|
||||
|
||||
list($linkHrefFirstPart) = explode('?', $linkHrefOriginal);
|
||||
|
||||
// Returned the updated source with the 'display' parameter appended to it
|
||||
$afterQuestionMark = http_build_query($outputStr);
|
||||
|
||||
if ($escHtml) {
|
||||
$afterQuestionMark = esc_attr($afterQuestionMark);
|
||||
}
|
||||
|
||||
return $linkHrefFirstPart . '?' . $afterQuestionMark;
|
||||
}
|
||||
|
||||
// Return original source
|
||||
return $linkHrefOriginal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function alterGoogleFontUrlFromInlineStyleTags($htmlSource)
|
||||
{
|
||||
if (! preg_match('/@import(\s+)url\(/i', $htmlSource)) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
preg_match_all('#<\s*?style\b[^>]*>(.*?)</style\b[^>]*>#s', $htmlSource, $styleMatches, PREG_SET_ORDER);
|
||||
|
||||
if (empty($styleMatches)) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
// Go through each STYLE tag
|
||||
foreach ($styleMatches as $styleInlineArray) {
|
||||
list($styleInlineTag, $styleInlineContent) = $styleInlineArray;
|
||||
|
||||
// Check if the STYLE tag has any 'data-wpacu-skip' attribute; if it does, do not continue
|
||||
if (preg_match('#data-wpacu-skip([=>/ ])#i', $styleInlineTag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is the content relevant?
|
||||
if (! preg_match('/@import(\s+|)(url|\(|\'|")/i', $styleInlineContent)
|
||||
|| stripos($styleInlineContent, 'fonts.googleapis.com') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do any alteration to the URL of the Google Font
|
||||
$newCssOutput = self::alterGoogleFontUrlFromCssContent($styleInlineTag);
|
||||
|
||||
$htmlSource = str_replace($styleInlineTag, $newCssOutput, $htmlSource);
|
||||
}
|
||||
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $cssContent
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function alterGoogleFontUrlFromCssContent($cssContent)
|
||||
{
|
||||
if (stripos($cssContent, 'fonts.googleapis.com') === false || ! Main::instance()->settings['google_fonts_display']) {
|
||||
return $cssContent;
|
||||
}
|
||||
|
||||
$regExps = array('/@import(\s+)url\((.*?)\)(|\s+)\;/i', '/@import(\s+|)(\(|\'|")(.*?)(\'|"|\))\;/i');
|
||||
|
||||
$newCssOutput = $cssContent;
|
||||
|
||||
foreach ($regExps as $regExpIndex => $regExpPattern) {
|
||||
preg_match_all($regExpPattern, $cssContent, $matchesFromInlineCode, PREG_SET_ORDER);
|
||||
|
||||
if (! empty($matchesFromInlineCode)) {
|
||||
foreach ($matchesFromInlineCode as $matchesFromInlineCodeArray) {
|
||||
$cssImportRule = $matchesFromInlineCodeArray[0];
|
||||
|
||||
if ($regExpIndex === 0) {
|
||||
$googleApisUrl = trim($matchesFromInlineCodeArray[2], '"\' ');
|
||||
} else {
|
||||
$googleApisUrl = trim($matchesFromInlineCodeArray[3], '"\' ');
|
||||
}
|
||||
|
||||
// It has to be a Google Fonts API link
|
||||
if (stripos($googleApisUrl, 'fonts.googleapis.com') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newGoogleApisUrl = self::alterGoogleFontLink($googleApisUrl, false);
|
||||
|
||||
if ($newGoogleApisUrl !== $googleApisUrl) {
|
||||
$newCssImportRule = str_replace($googleApisUrl, $newGoogleApisUrl, $cssImportRule);
|
||||
$newCssOutput = str_replace($cssImportRule, $newCssImportRule, $newCssOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $newCssOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $jsContent
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function alterGoogleFontUrlFromJsContent($jsContent)
|
||||
{
|
||||
if (stripos($jsContent, 'fonts.googleapis.com') === false) {
|
||||
return $jsContent;
|
||||
}
|
||||
|
||||
$newJsOutput = $jsContent;
|
||||
|
||||
preg_match_all('#fonts.googleapis.com(.*?)(["\'])#si', $jsContent, $matchesFromJsCode);
|
||||
|
||||
if (isset($matchesFromJsCode[0]) && ! empty($matchesFromJsCode)) {
|
||||
foreach ($matchesFromJsCode[0] as $match) {
|
||||
$matchRule = $match;
|
||||
$googleApisUrl = trim($match, '"\' ');
|
||||
|
||||
$newGoogleApisUrl = self::alterGoogleFontLink($googleApisUrl, false, 'js');
|
||||
if ($newGoogleApisUrl !== $googleApisUrl) {
|
||||
$newJsMatchOutput = str_replace($googleApisUrl, $newGoogleApisUrl, $matchRule);
|
||||
$newJsOutput = str_replace($matchRule, $newJsMatchOutput, $newJsOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for any "WebFontConfig = { google: { families: ['font-one', 'font-two'] } }" patterns
|
||||
if ( stripos( $jsContent, 'WebFontConfig' ) !== false
|
||||
&& preg_match_all( '#WebFontConfig(.*?)google(\s+|):(\s+|){(\s+|)families(\s+|):(?<families>.*?)]#s', $jsContent, $webFontConfigMatches )
|
||||
&& isset( $webFontConfigMatches['families'] ) && ! empty( $webFontConfigMatches['families'] )
|
||||
) {
|
||||
foreach ($webFontConfigMatches['families'] as $webFontConfigKey => $webFontConfigMatch) {
|
||||
$originalWholeMatch = $webFontConfigMatches[0][$webFontConfigKey];
|
||||
$familiesMatchOutput = trim($webFontConfigMatch);
|
||||
|
||||
// NO match or existing "display" parameter was found? Do not continue
|
||||
if (! $familiesMatchOutput || strpos($familiesMatchOutput, 'display=')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Alter the matched string
|
||||
$familiesNewOutput = preg_replace('/([\'"])$/', '&display='.Main::instance()->settings['google_fonts_display'].'\\1', $familiesMatchOutput);
|
||||
$newWebFontConfigOutput = str_replace($familiesMatchOutput, $familiesNewOutput, $originalWholeMatch);
|
||||
|
||||
// Finally, do the replacement
|
||||
$newJsOutput = str_replace($originalWholeMatch, $newWebFontConfigOutput, $newJsOutput);
|
||||
}
|
||||
}
|
||||
|
||||
return $newJsOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $finalLinks
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return false|mixed|string|void
|
||||
*/
|
||||
public static function combineGoogleFontLinks($finalLinks, $htmlSource)
|
||||
{
|
||||
$fontsArray = array();
|
||||
|
||||
foreach ($finalLinks as $finalLinkIndex => $finalLinkData) {
|
||||
$finalLinkHref = $finalLinkData['href'];
|
||||
$finalLinkHref = str_replace('&', '&', $finalLinkHref);
|
||||
|
||||
$queries = parse_url($finalLinkHref, PHP_URL_QUERY);
|
||||
parse_str($queries, $fontQueries);
|
||||
|
||||
if (! array_key_exists('family', $fontQueries) || array_key_exists('text', $fontQueries)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Strip the existing tag, leave a mark where the final combined LINK will be placed
|
||||
$stripTagWith = ($finalLinkIndex === 0) ? self::COMBINED_LINK_DEL : '';
|
||||
$finalLinkTag = $finalLinkData['tag'];
|
||||
|
||||
$htmlSource = str_ireplace(array($finalLinkTag."\n", $finalLinkTag), $stripTagWith, $htmlSource);
|
||||
|
||||
$family = trim($fontQueries['family']);
|
||||
$family = trim($family, '|');
|
||||
|
||||
if (! $family) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($family, '|') !== false) {
|
||||
// More than one family per request?
|
||||
foreach (explode('|', $family) as $familyOne) {
|
||||
if (strpos($familyOne, ':') !== false) {
|
||||
// They have types
|
||||
list ($familyRaw, $familyTypes) = explode(':', $familyOne);
|
||||
$fontsArray['families'][$familyRaw]['types'] = self::buildSortTypesList($familyTypes);
|
||||
} else {
|
||||
// They do not have types
|
||||
$familyRaw = $familyOne;
|
||||
$fontsArray['families'][$familyRaw]['types'] = false;
|
||||
}
|
||||
}
|
||||
} elseif (strpos($family, ':') !== false) {
|
||||
list ($familyRaw, $familyTypes) = explode(':', $family);
|
||||
$fontsArray['families'][$familyRaw]['types'] = self::buildSortTypesList($familyTypes);
|
||||
} else {
|
||||
$familyRaw = $family;
|
||||
$fontsArray['families'][$familyRaw]['types'] = false;
|
||||
}
|
||||
|
||||
if (array_key_exists('subset', $fontQueries)) {
|
||||
// More than one subset per request?
|
||||
if (strpos($fontQueries['subset'], ',') !== false) {
|
||||
$multipleSubsets = explode(',', trim($fontQueries['subset'], ','));
|
||||
|
||||
foreach ($multipleSubsets as $subset) {
|
||||
$fontsArray['subsets'][] = trim($subset);
|
||||
}
|
||||
} else {
|
||||
// Only one subset
|
||||
$fontsArray['subsets'][] = $fontQueries['subset'];
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('effect', $fontQueries)) {
|
||||
// More than one subset per request?
|
||||
if (strpos($fontQueries['effect'], '|') !== false) {
|
||||
$multipleSubsets = explode('|', trim($fontQueries['effect'], '|'));
|
||||
|
||||
foreach ($multipleSubsets as $subset) {
|
||||
$fontsArray['effects'][] = trim($subset);
|
||||
}
|
||||
} else {
|
||||
// Only one subset
|
||||
$fontsArray['effects'][] = $fontQueries['effect'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($fontsArray)) {
|
||||
$finalCombinedParameters = '';
|
||||
ksort($fontsArray['families']);
|
||||
|
||||
// Families
|
||||
foreach ($fontsArray['families'] as $familyRaw => $fontValues) {
|
||||
$finalCombinedParameters .= str_replace(' ', '+', $familyRaw);
|
||||
|
||||
// Any types? e.g. 400, 400italic, bold, etc.
|
||||
if (isset($fontValues['types']) && $fontValues['types'] !== false) {
|
||||
$finalCombinedParameters .= ':' . $fontValues['types'];
|
||||
}
|
||||
|
||||
$finalCombinedParameters .= '|';
|
||||
}
|
||||
|
||||
$finalCombinedParameters = trim($finalCombinedParameters, '|');
|
||||
|
||||
// Subsets
|
||||
if (isset($fontsArray['subsets']) && ! empty($fontsArray['subsets'])) {
|
||||
sort($fontsArray['subsets']);
|
||||
$finalCombinedParameters .= '&subset=' . implode(',', array_unique($fontsArray['subsets']));
|
||||
}
|
||||
|
||||
// Effects
|
||||
if (isset($fontsArray['effects']) && ! empty($fontsArray['effects'])) {
|
||||
sort($fontsArray['effects']);
|
||||
$finalCombinedParameters .= '&effect=' . implode('|', array_unique($fontsArray['effects']));
|
||||
}
|
||||
|
||||
if ($fontDisplay = Main::instance()->settings['google_fonts_display']) {
|
||||
$finalCombinedParameters .= '&display=' . $fontDisplay;
|
||||
}
|
||||
|
||||
$finalCombinedParameters = esc_attr($finalCombinedParameters);
|
||||
|
||||
// This is needed for both render-blocking and async (within NOSCRIPT tag as a fallback)
|
||||
$finalCombinedLink = <<<LINK
|
||||
<link rel='stylesheet' id='wpacu-combined-google-fonts-css' href='https://fonts.googleapis.com/css?family={$finalCombinedParameters}' type='text/css' media='all' />
|
||||
LINK;
|
||||
/*
|
||||
* Loading Type: Render-Blocking (Default)
|
||||
*/
|
||||
if (! Main::instance()->settings['google_fonts_combine_type']) {
|
||||
$finalCombinedLink .= "\n";
|
||||
$htmlSource = str_replace(self::COMBINED_LINK_DEL, apply_filters('wpacu_combined_google_fonts_link_tag', $finalCombinedLink), $htmlSource);
|
||||
}
|
||||
|
||||
/*
|
||||
* Loading Type: Asynchronous via LINK preload with fallback
|
||||
*/
|
||||
if (Main::instance()->settings['google_fonts_combine_type'] === 'async_preload') {
|
||||
$finalPreloadCombinedLink = <<<LINK
|
||||
<link rel='preload' as="style" onload="this.onload=null;this.rel='stylesheet'" data-wpacu-preload-it-async='1' id='wpacu-combined-google-fonts-css-async-preload' href='https://fonts.googleapis.com/css?family={$finalCombinedParameters}' type='text/css' media='all' />
|
||||
LINK;
|
||||
$finalPreloadCombinedLink .= "\n".Misc::preloadAsyncCssFallbackOutput();
|
||||
|
||||
$htmlSource = str_replace(self::COMBINED_LINK_DEL, apply_filters('wpacu_combined_google_fonts_async_preload_link_tag', $finalPreloadCombinedLink), $htmlSource);
|
||||
}
|
||||
|
||||
/*
|
||||
* Loading Type: Asynchronous via Web Font Loader (webfont.js) with fallback
|
||||
*/
|
||||
if (Main::instance()->settings['google_fonts_combine_type'] === 'async') { // Async via Web Font Loader
|
||||
$subSetsStr = '';
|
||||
|
||||
if (isset($fontsArray['subsets']) && ! empty($fontsArray['subsets'])) {
|
||||
sort($fontsArray['subsets']);
|
||||
$subSetsStr = implode(',', array_unique($fontsArray['subsets']));
|
||||
}
|
||||
|
||||
$wfConfigGoogleFamilies = array();
|
||||
|
||||
// Families
|
||||
$iCount = 0;
|
||||
|
||||
foreach ($fontsArray['families'] as $familyRaw => $fontValues) {
|
||||
$wfConfigGoogleFamily = str_replace(' ', '+', $familyRaw);
|
||||
|
||||
// Any types? e.g. 400, 400italic, bold, etc.
|
||||
$hasTypes = false;
|
||||
if (isset($fontValues['types']) && $fontValues['types']) {
|
||||
$wfConfigGoogleFamily .= ':'.$fontValues['types'];
|
||||
$hasTypes = true;
|
||||
}
|
||||
|
||||
if ($subSetsStr) {
|
||||
// If there are types, continue to use the comma delimiter
|
||||
$wfConfigGoogleFamily .= ($hasTypes ? ',' : ':') . $subSetsStr;
|
||||
}
|
||||
|
||||
// Append extra parameters to the last family from the list
|
||||
if ($iCount === count($fontsArray['families']) - 1) {
|
||||
// Effects
|
||||
if (isset($fontsArray['effects']) && ! empty($fontsArray['effects'])) {
|
||||
sort($fontsArray['effects']);
|
||||
$wfConfigGoogleFamily .= '&effect=' . implode('|', array_unique($fontsArray['effects']));
|
||||
}
|
||||
|
||||
if ($fontDisplay = Main::instance()->settings['google_fonts_display']) {
|
||||
$wfConfigGoogleFamily .= '&display=' . $fontDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
$wfConfigGoogleFamilies[] = "'".$wfConfigGoogleFamily."'";
|
||||
|
||||
$iCount++;
|
||||
}
|
||||
|
||||
$wfConfigGoogleFamiliesStr = '['.implode(',', $wfConfigGoogleFamilies).']';
|
||||
|
||||
$finalInlineTagWebFontConfig = '<script id=\'wpacu-google-fonts-async-load\' type=\'text/javascript\'>'."\n".'WebFontConfig={google:{families:'.$wfConfigGoogleFamiliesStr.'}};(function(wpacuD){var wpacuWf=wpacuD.createElement(\'script\'),wpacuS=wpacuD.scripts[0];wpacuWf.src=(\'https:\'===document.location.protocol?\'https\':\'http\')+\'://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js\';wpacuWf.async=!0;wpacuS.parentNode.insertBefore(wpacuWf,wpacuS)})(document);'."\n".'</script>';
|
||||
|
||||
$htmlSource = str_replace(
|
||||
array(
|
||||
self::COMBINED_LINK_DEL,
|
||||
self::NOSCRIPT_WEB_FONT_LOADER
|
||||
),
|
||||
array(
|
||||
apply_filters( 'wpacu_combined_google_fonts_inline_script_tag', $finalInlineTagWebFontConfig ),
|
||||
'<noscript>' . apply_filters( 'wpacu_combined_google_fonts_link_tag', $finalCombinedLink ) . '</noscript>' . "\n"
|
||||
),
|
||||
$htmlSource
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. 300, 400, 400italic, bold, etc.
|
||||
*
|
||||
* @param $types
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function buildSortTypesList($types)
|
||||
{
|
||||
$newTypes = array();
|
||||
|
||||
// More than one type per family?
|
||||
if (strpos($types, ',') !== false) {
|
||||
$multipleTypes = explode(',', trim($types, ','));
|
||||
|
||||
foreach ($multipleTypes as $type) {
|
||||
if (trim($type)) {
|
||||
$newTypes[] = trim($type);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only one type per family
|
||||
$newTypes[] = $types;
|
||||
}
|
||||
|
||||
$newTypes = array_unique($newTypes);
|
||||
|
||||
sort($newTypes);
|
||||
|
||||
return implode(',', $newTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function preventAnyChange()
|
||||
{
|
||||
return defined( 'WPACU_ALLOW_ONLY_UNLOAD_RULES' ) && WPACU_ALLOW_ONLY_UNLOAD_RULES;
|
||||
}
|
||||
}
|
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
namespace WpAssetCleanUp\OptimiseAssets;
|
||||
|
||||
/**
|
||||
* Class FontsGoogle
|
||||
* @package WpAssetCleanUp\OptimiseAssets
|
||||
*/
|
||||
class FontsGoogleRemove
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $stringsToCheck = array(
|
||||
'//fonts.googleapis.com',
|
||||
'//fonts.gstatic.com'
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $possibleWebFontConfigCdnPatterns = array(
|
||||
'//ajax.googleapis.com/ajax/libs/webfont/(.*?)', // Google Apis
|
||||
'//cdnjs.cloudflare.com/ajax/libs/webfont/(.*?)', // Cloudflare
|
||||
'//cdn.jsdelivr.net/npm/webfontloader@(.*?)' // jsDELIVR
|
||||
);
|
||||
|
||||
/**
|
||||
* Called late from OptimizeCss after all other optimizations are done (e.g. minify, combine)
|
||||
*
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function cleanHtmlSource($htmlSource)
|
||||
{
|
||||
$htmlSource = self::cleanLinkTags($htmlSource);
|
||||
$htmlSource = self::cleanFromInlineStyleTags($htmlSource);
|
||||
|
||||
return str_replace(FontsGoogle::NOSCRIPT_WEB_FONT_LOADER, '', $htmlSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function cleanLinkTags($htmlSource)
|
||||
{
|
||||
// Do not continue if there is no single reference to the string we look for in the clean HTML source
|
||||
if (stripos($htmlSource, FontsGoogle::$containsStr) === false) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
// Get all valid LINKs that have the self::$stringsToCheck within them
|
||||
$strContainsArray = array_map(static function($containsStr) {
|
||||
return preg_quote($containsStr, '/');
|
||||
}, self::$stringsToCheck);
|
||||
|
||||
$strContainsFormat = implode('|', $strContainsArray);
|
||||
|
||||
preg_match_all('#<link[^>]*(' . $strContainsFormat . ').*(>)#Usmi', $htmlSource, $matchesFromLinkTags, PREG_SET_ORDER);
|
||||
|
||||
$stripLinksList = array();
|
||||
|
||||
// Needs to match at least one to carry on with the replacements
|
||||
if (isset($matchesFromLinkTags[0]) && ! empty($matchesFromLinkTags[0])) {
|
||||
foreach ($matchesFromLinkTags as $linkTagArray) {
|
||||
$linkTag = trim(trim($linkTagArray[0], '"\''));
|
||||
|
||||
if (strip_tags($linkTag) !== '') {
|
||||
continue; // Something might be funny there, make sure the tag is valid
|
||||
}
|
||||
|
||||
// Check if the Google Fonts CSS has any 'data-wpacu-skip' attribute; if it does, do not remove it
|
||||
if (preg_match('#data-wpacu-skip([=>/ ])#i', $linkTag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stripLinksList[$linkTag] = '';
|
||||
}
|
||||
|
||||
$htmlSource = strtr($htmlSource, $stripLinksList);
|
||||
}
|
||||
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function cleanFromInlineStyleTags($htmlSource)
|
||||
{
|
||||
if (! preg_match('/(;?)(@import (?<url>url\(|\()?(?P<quotes>["\'()]?).+?(?P=quotes)(?(url)\)));?/', $htmlSource)) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
preg_match_all('#<\s*?style\b[^>]*>(.*?)</style\b[^>]*>#s', $htmlSource, $styleMatches, PREG_SET_ORDER);
|
||||
|
||||
if (empty($styleMatches)) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
// Go through each STYLE tag
|
||||
foreach ($styleMatches as $styleInlineArray) {
|
||||
list($styleInlineTag, $styleInlineContent) = $styleInlineArray;
|
||||
|
||||
// Check if the STYLE tag has any 'data-wpacu-skip' attribute; if it does, do not continue
|
||||
if (preg_match('#data-wpacu-skip([=>/ ])#i', $styleInlineTag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newStyleInlineTag = $styleInlineTag;
|
||||
$newStyleInlineContent = $styleInlineContent;
|
||||
|
||||
// Is the content relevant?
|
||||
preg_match_all('/(;?)(@import (?<url>url\(|\()?(?P<quotes>["\'()]?).+?(?P=quotes)(?(url)\)));?/', $styleInlineContent, $matches);
|
||||
|
||||
if (isset($matches[0]) && ! empty($matches[0])) {
|
||||
foreach ($matches[0] as $matchedImport) {
|
||||
$newStyleInlineContent = str_replace($matchedImport, '', $newStyleInlineContent);
|
||||
}
|
||||
|
||||
$newStyleInlineContent = trim($newStyleInlineContent);
|
||||
|
||||
// Is the STYLE tag empty after the @imports are removed? It happens on some websites; strip the tag, no point of having it empty
|
||||
if ($newStyleInlineContent === '') {
|
||||
$htmlSource = str_replace($styleInlineTag, '', $htmlSource);
|
||||
} else {
|
||||
$newStyleInlineTag = str_replace($styleInlineContent, $newStyleInlineContent, $styleInlineTag);
|
||||
$htmlSource = str_replace($styleInlineTag, $newStyleInlineTag, $htmlSource);
|
||||
}
|
||||
}
|
||||
|
||||
$styleTagAfterImportsCleaned = $newStyleInlineTag;
|
||||
$styleTagAfterFontFaceCleaned = trim(self::cleanFontFaceReferences($newStyleInlineContent));
|
||||
$newStyleInlineTag = str_replace($newStyleInlineContent, $styleTagAfterFontFaceCleaned, $newStyleInlineTag);
|
||||
|
||||
$htmlSource = str_replace($styleTagAfterImportsCleaned, $newStyleInlineTag, $htmlSource);
|
||||
}
|
||||
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $importsAddToTop
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function stripGoogleApisImport($importsAddToTop)
|
||||
{
|
||||
// Remove any Google Fonts imports
|
||||
foreach ($importsAddToTop as $importKey => $importToPrepend) {
|
||||
if (stripos($importToPrepend, FontsGoogle::$containsStr) !== false) {
|
||||
unset($importsAddToTop[$importKey]);
|
||||
}
|
||||
}
|
||||
|
||||
return $importsAddToTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* If "Google Font Remove" is active, strip its references from JavaScript code as well
|
||||
*
|
||||
* @param $jsContent
|
||||
*
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
public static function stripReferencesFromJsCode($jsContent)
|
||||
{
|
||||
if (self::preventAnyChange()) {
|
||||
return $jsContent;
|
||||
}
|
||||
|
||||
$webFontConfigReferenceOne = "#src(\s+|)=(\s+|)(?<startDel>'|\")(\s+|)((http:|https:|)(".implode('|', self::$possibleWebFontConfigCdnPatterns).")(\s+|))(?<endDel>'|\")#si";
|
||||
|
||||
if (stripos($jsContent, 'WebFontConfig') !== false
|
||||
&& preg_match('/(WebFontConfig\.|\'|"|)google(\s+|)([\'":=])/i', $jsContent)
|
||||
&& preg_match_all($webFontConfigReferenceOne, $jsContent, $matches) && ! empty($matches)
|
||||
) {
|
||||
foreach ($matches[0] as $matchIndex => $matchRow) {
|
||||
$jsContent = str_replace(
|
||||
$matchRow,
|
||||
'src=' . $matches['startDel'][$matchIndex] . $matches['endDel'][$matchIndex] . ';/* Stripped by ' . WPACU_PLUGIN_TITLE . ' */',
|
||||
$jsContent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$webFontConfigReferenceTwo = '#("|\')((http:|https:|)//fonts.googleapis.com/(.*?))("|\')#si';
|
||||
|
||||
if (preg_match($webFontConfigReferenceTwo, $jsContent)) {
|
||||
$jsContent = preg_replace($webFontConfigReferenceTwo, '\\1\\5', $jsContent);
|
||||
}
|
||||
|
||||
return $jsContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $cssContent
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function cleanFontFaceReferences($cssContent)
|
||||
{
|
||||
if (self::preventAnyChange()) {
|
||||
return $cssContent;
|
||||
}
|
||||
|
||||
preg_match_all('#@font-face(|\s+){(.*?)}#si', $cssContent, $matchesFromCssCode, PREG_SET_ORDER);
|
||||
|
||||
if (! empty($matchesFromCssCode)) {
|
||||
foreach ($matchesFromCssCode as $matches) {
|
||||
$fontFaceSyntax = $matches[0];
|
||||
preg_match_all('/url(\s+|)\((?![\'"]?(?:data):)[\'"]?([^\'")]*)[\'"]?\)/i', $matches[0], $matchesFromUrlSyntax);
|
||||
|
||||
if (! empty($matchesFromUrlSyntax) && stripos(implode('', $matchesFromUrlSyntax[0]), '//fonts.gstatic.com/') !== false) {
|
||||
$cssContent = str_replace($fontFaceSyntax, '', $cssContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cssContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function preventAnyChange()
|
||||
{
|
||||
return defined( 'WPACU_ALLOW_ONLY_UNLOAD_RULES' ) && WPACU_ALLOW_ONLY_UNLOAD_RULES;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
namespace WpAssetCleanUp\OptimiseAssets;
|
||||
|
||||
use WpAssetCleanUp\Main;
|
||||
use WpAssetCleanUp\Plugin;
|
||||
|
||||
/**
|
||||
* Class LocalFonts
|
||||
* @package WpAssetCleanUp\OptimiseAssets
|
||||
*/
|
||||
class FontsLocal
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if (self::preventAnyChange()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action('wp_head', array($this, 'preloadFontFiles'), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function preloadFontFiles()
|
||||
{
|
||||
// AMP page or Test Mode? Do not print anything
|
||||
if ( Plugin::preventAnyFrontendOptimization() || Main::isTestModeActive() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $preloadFontFiles = trim(Main::instance()->settings['local_fonts_preload_files'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$preloadFontFilesArray = array();
|
||||
|
||||
if (strpos($preloadFontFiles, "\n") !== false) {
|
||||
foreach (explode("\n", $preloadFontFiles) as $preloadFontFile) {
|
||||
$preloadFontFile = trim($preloadFontFile);
|
||||
|
||||
if (! $preloadFontFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$preloadFontFilesArray[] = $preloadFontFile;
|
||||
}
|
||||
} else {
|
||||
$preloadFontFilesArray[] = $preloadFontFiles;
|
||||
}
|
||||
|
||||
$preloadFontFilesArray = array_unique($preloadFontFilesArray);
|
||||
|
||||
$preloadFontFilesOutput = '';
|
||||
|
||||
// Finally, go through the list
|
||||
foreach ($preloadFontFilesArray as $preloadFontFile) {
|
||||
$preloadFontFilesOutput .= '<link rel="preload" as="font" href="'.esc_attr($preloadFontFile).'" data-wpacu-preload-font="1" crossorigin>'."\n";
|
||||
}
|
||||
|
||||
echo apply_filters('wpacu_preload_local_font_files_output', $preloadFontFilesOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function preventAnyChange()
|
||||
{
|
||||
if (defined('WPACU_ALLOW_ONLY_UNLOAD_RULES') && WPACU_ALLOW_ONLY_UNLOAD_RULES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
namespace WpAssetCleanUp\OptimiseAssets;
|
||||
|
||||
use WpAssetCleanUp\Main;
|
||||
use WpAssetCleanUp\Menu;
|
||||
use WpAssetCleanUp\MetaBoxes;
|
||||
use WpAssetCleanUp\Misc;
|
||||
|
||||
/**
|
||||
* Class MinifyCss
|
||||
* @package WpAssetCleanUp\OptimiseAssets
|
||||
*/
|
||||
class MinifyCss
|
||||
{
|
||||
/**
|
||||
* @param $cssContent
|
||||
* @param bool $forInlineStyle
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function applyMinification($cssContent, $forInlineStyle = false
|
||||
)
|
||||
{
|
||||
if (class_exists('\MatthiasMullie\Minify\CSS')) {
|
||||
$sha1OriginalContent = sha1($cssContent);
|
||||
$checkForAlreadyMinifiedShaOne = mb_strlen($cssContent) > 40000;
|
||||
|
||||
// Let's check if the content is already minified
|
||||
// Save resources as the minify process can take time if the content is very large
|
||||
// Limit the total number of entries tp 100: if it's more than that, it's likely because there's dynamic JS altering on every page load
|
||||
if ($checkForAlreadyMinifiedShaOne && OptimizeCommon::originalContentIsAlreadyMarkedAsMinified($sha1OriginalContent, 'styles')) {
|
||||
return $cssContent;
|
||||
}
|
||||
|
||||
$cssContentBeforeAnyBugChanges = $cssContent;
|
||||
|
||||
// [CUSTOM BUG FIX]
|
||||
// Encode the special matched content to avoid any wrong minification from the minifier
|
||||
$hasVarWithZeroUnit = false;
|
||||
|
||||
preg_match_all('#--([a-zA-Z0-9_-]+):(\s+)0(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)#', $cssContent, $cssVariablesMatches);
|
||||
|
||||
if (isset($cssVariablesMatches[0]) && ! empty($cssVariablesMatches[0])) {
|
||||
$hasVarWithZeroUnit = true;
|
||||
|
||||
foreach ($cssVariablesMatches[0] as $zeroUnitMatch) {
|
||||
$cssContent = str_replace( $zeroUnitMatch, '[wpacu]' . base64_encode( $zeroUnitMatch ) . '[/wpacu]', $cssContent );
|
||||
}
|
||||
}
|
||||
|
||||
// Fix: If the content is something like "calc(50% - 22px) calc(50% - 22px);" then leave it as it is
|
||||
preg_match_all('#calc(|\s+)\((.*?)(;|})#si', $cssContent, $cssCalcMatches);
|
||||
|
||||
$multipleOrSpecificCalcMatches = array(); // with multiple calc() or with at least one calc() that contains new lines
|
||||
|
||||
if (isset($cssCalcMatches[0]) && ! empty($cssCalcMatches[0])) {
|
||||
foreach ($cssCalcMatches[0] as $cssCalcMatch) {
|
||||
if (substr_count($cssCalcMatch, 'calc') > 1 || strpos($cssCalcMatch, "\n") !== false) {
|
||||
$cssContent = str_replace( $cssCalcMatch, '[wpacu]' . base64_encode( $cssCalcMatch ) . '[/wpacu]', $cssContent );
|
||||
$multipleOrSpecificCalcMatches[] = $cssCalcMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [/CUSTOM BUG FIX]
|
||||
|
||||
$minifier = new \MatthiasMullie\Minify\CSS( $cssContent );
|
||||
|
||||
if ( $forInlineStyle ) {
|
||||
// If the minification is applied for inlined CSS (within STYLE) leave the background URLs unchanged as it sometimes lead to issues
|
||||
$minifier->setImportExtensions( array() );
|
||||
}
|
||||
|
||||
$minifiedContent = trim( $minifier->minify() );
|
||||
|
||||
// [CUSTOM BUG FIX]
|
||||
// Restore the original content
|
||||
if ($hasVarWithZeroUnit) {
|
||||
foreach ( $cssVariablesMatches[0] as $zeroUnitMatch ) {
|
||||
$zeroUnitMatchAlt = str_replace(': 0', ':0', $zeroUnitMatch); // remove the space
|
||||
$minifiedContent = str_replace( '[wpacu]' . base64_encode( $zeroUnitMatch ) . '[/wpacu]', $zeroUnitMatchAlt, $minifiedContent );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty($multipleOrSpecificCalcMatches) ) {
|
||||
foreach ( $multipleOrSpecificCalcMatches as $cssCalcMatch ) {
|
||||
$originalCssCalcMatch = $cssCalcMatch;
|
||||
$cssCalcMatch = preg_replace(array('#calc\(\s+#', '#\s+\);#'), array('calc(', ');'), $originalCssCalcMatch);
|
||||
$cssCalcMatch = str_replace(' ) calc(', ') calc(', $cssCalcMatch);
|
||||
$minifiedContent = str_replace( '[wpacu]' . base64_encode( $originalCssCalcMatch ) . '[/wpacu]', $cssCalcMatch, $minifiedContent );
|
||||
}
|
||||
}
|
||||
// [/CUSTOM BUG FIX]
|
||||
|
||||
// Is there any [wpacu] left? Hmm, the replacement wasn't alright. Make sure to use the original minified version
|
||||
if (strpos($minifiedContent, '[wpacu]') !== false && strpos($minifiedContent, '[/wpacu]') !== false) {
|
||||
$minifier = new \MatthiasMullie\Minify\CSS( $cssContentBeforeAnyBugChanges );
|
||||
|
||||
if ( $forInlineStyle ) {
|
||||
// If the minification is applied for inlined CSS (within STYLE) leave the background URLs unchanged as it sometimes leads to issues
|
||||
$minifier->setImportExtensions( array() );
|
||||
}
|
||||
|
||||
$minifiedContent = trim( $minifier->minify() );
|
||||
}
|
||||
|
||||
if ($checkForAlreadyMinifiedShaOne && $minifiedContent === $cssContent) {
|
||||
// If the resulting content is the same, mark it as minified to avoid the minify process next time
|
||||
OptimizeCommon::originalContentMarkAsAlreadyMinified( $sha1OriginalContent, 'styles' );
|
||||
}
|
||||
|
||||
return $minifiedContent;
|
||||
}
|
||||
|
||||
return $cssContent;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $href
|
||||
* @param string $handle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function skipMinify($href, $handle = '')
|
||||
{
|
||||
// Things like WP Fastest Cache Toolbar CSS shouldn't be minified and take up space on the server
|
||||
if ($handle !== '' && in_array($handle, Main::instance()->skipAssets['styles'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Some of these files (e.g. from Oxygen, WooCommerce) are already minified
|
||||
$regExps = array(
|
||||
'#/wp-content/plugins/wp-asset-clean-up(.*?).min.css#',
|
||||
|
||||
// Formidable Forms
|
||||
'#/wp-content/plugins/formidable/css/formidableforms.css#',
|
||||
|
||||
// Oxygen
|
||||
//'#/wp-content/plugins/oxygen/component-framework/oxygen.css#',
|
||||
|
||||
// WooCommerce
|
||||
'#/wp-content/plugins/woocommerce/assets/css/woocommerce-layout.css#',
|
||||
'#/wp-content/plugins/woocommerce/assets/css/woocommerce.css#',
|
||||
'#/wp-content/plugins/woocommerce/assets/css/woocommerce-smallscreen.css#',
|
||||
'#/wp-content/plugins/woocommerce/assets/css/blocks/style.css#',
|
||||
'#/wp-content/plugins/woocommerce/packages/woocommerce-blocks/build/style.css#',
|
||||
|
||||
// Google Site Kit: the files are already optimized
|
||||
'#/wp-content/plugins/google-site-kit/#',
|
||||
|
||||
// Other libraries from the core that end in .min.css
|
||||
'#/wp-includes/css/(.*?).min.css#',
|
||||
|
||||
// Files within /wp-content/uploads/ or /wp-content/cache/
|
||||
// Could belong to plugins such as "Elementor", "Oxygen" etc.
|
||||
'#/wp-content/uploads/elementor/(.*?).css#',
|
||||
'#/wp-content/uploads/oxygen/css/(.*?)-(.*?).css#',
|
||||
'#/wp-content/cache/(.*?).css#',
|
||||
|
||||
// Already minified, and it also has a random name making the cache folder make bigger
|
||||
'#/wp-content/bs-booster-cache/#',
|
||||
|
||||
);
|
||||
|
||||
$regExps = Misc::replaceRelPluginPath($regExps);
|
||||
|
||||
if (Main::instance()->settings['minify_loaded_css_exceptions'] !== '') {
|
||||
$loadedCssExceptionsPatterns = trim(Main::instance()->settings['minify_loaded_css_exceptions']);
|
||||
|
||||
if (strpos($loadedCssExceptionsPatterns, "\n")) {
|
||||
// Multiple values (one per line)
|
||||
foreach (explode("\n", $loadedCssExceptionsPatterns) as $loadedCssExceptionPattern) {
|
||||
$regExps[] = '#'.trim($loadedCssExceptionPattern).'#';
|
||||
}
|
||||
} else {
|
||||
// Only one value?
|
||||
$regExps[] = '#'.trim($loadedCssExceptionsPatterns).'#';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($regExps as $regExp) {
|
||||
if ( preg_match( $regExp, $href ) || ( strpos($href, $regExp) !== false ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function minifyInlineStyleTags($htmlSource)
|
||||
{
|
||||
if (stripos($htmlSource, '<style') === false) {
|
||||
return $htmlSource; // no STYLE tags
|
||||
}
|
||||
|
||||
$skipTagsContaining = array(
|
||||
'data-wpacu-skip',
|
||||
'astra-theme-css-inline-css',
|
||||
'astra-edd-inline-css',
|
||||
'et-builder-module-design-cached-inline-styles',
|
||||
'fusion-stylesheet-inline-css',
|
||||
'woocommerce-general-inline-css',
|
||||
'woocommerce-inline-inline-css',
|
||||
'data-wpacu-own-inline-style',
|
||||
// Only shown to the admin, irrelevant for any optimization (save resources)
|
||||
'data-wpacu-inline-css-file'
|
||||
// already minified/optimized since the INLINE was generated from the cached file
|
||||
);
|
||||
|
||||
$fetchType = 'regex';
|
||||
|
||||
if ( $fetchType === 'regex' ) {
|
||||
preg_match_all( '@(<style[^>]*?>).*?</style>@si', $htmlSource, $matchesStyleTags, PREG_SET_ORDER );
|
||||
if ( $matchesStyleTags === null ) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
foreach ($matchesStyleTags as $matchedStyle) {
|
||||
if ( ! (isset($matchedStyle[0]) && $matchedStyle[0]) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$originalTag = $matchedStyle[0];
|
||||
|
||||
if (substr($originalTag, -strlen('></style>')) === strtolower('></style>')) {
|
||||
// No empty STYLE tags
|
||||
continue;
|
||||
}
|
||||
|
||||
// No need to use extra resources as the tag is already minified
|
||||
if ( preg_match( '(' . implode( '|', $skipTagsContaining ) . ')', $originalTag ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tagOpen = $matchedStyle[1];
|
||||
|
||||
$withTagOpenStripped = substr($originalTag, strlen($tagOpen));
|
||||
$originalTagContents = substr($withTagOpenStripped, 0, -strlen('</style>'));
|
||||
|
||||
if ( $originalTagContents ) {
|
||||
$newTagContents = OptimizeCss::maybeAlterContentForInlineStyleTag( $originalTagContents, true, array( 'just_minify' ) );
|
||||
|
||||
// Only comments or no content added to the inline STYLE tag? Strip it completely to reduce the number of DOM elements
|
||||
if ( $newTagContents === '/**/' || ! $newTagContents ) {
|
||||
$htmlSource = str_replace( '>' . $originalTagContents . '</', '></', $htmlSource );
|
||||
|
||||
preg_match( '#<style.*?>#si', $originalTag, $matchFromStyle );
|
||||
|
||||
if ( isset( $matchFromStyle[0] ) && $styleTagWithoutContent = $matchFromStyle[0] ) {
|
||||
$styleTagWithoutContentAlt = str_ireplace( '"', '\'', $styleTagWithoutContent );
|
||||
$htmlSource = str_ireplace( array(
|
||||
$styleTagWithoutContent . '</style>',
|
||||
$styleTagWithoutContentAlt . '</style>'
|
||||
), '', $htmlSource );
|
||||
}
|
||||
} else {
|
||||
// It has content; do the replacement
|
||||
$htmlSource = str_replace(
|
||||
'>' . $originalTagContents . '</style>',
|
||||
'>' . $newTagContents . '</style>',
|
||||
$htmlSource
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isMinifyCssEnabled()
|
||||
{
|
||||
if (defined('WPACU_IS_MINIFY_CSS_ENABLED')) {
|
||||
return WPACU_IS_MINIFY_CSS_ENABLED;
|
||||
}
|
||||
|
||||
// Request Minify On The Fly
|
||||
// It will preview the page with CSS minified
|
||||
// Only if the admin is logged-in as it uses more resources (CPU / Memory)
|
||||
if ( isset($_GET['wpacu_css_minify']) && Menu::userCanManageAssets() ) {
|
||||
self::isMinifyCssEnabledChecked('true');
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isset($_REQUEST['wpacu_no_css_minify']) || // not on query string request (debugging purposes)
|
||||
is_admin() || // not for Dashboard view
|
||||
(! Main::instance()->settings['minify_loaded_css']) || // Minify CSS has to be Enabled
|
||||
(Main::instance()->settings['test_mode'] && ! Menu::userCanManageAssets()) ) { // Does not trigger if "Test Mode" is Enabled
|
||||
self::isMinifyCssEnabledChecked('false');
|
||||
return false;
|
||||
}
|
||||
|
||||
$isSingularPage = defined('WPACU_CURRENT_PAGE_ID') && WPACU_CURRENT_PAGE_ID > 0 && is_singular();
|
||||
|
||||
if ($isSingularPage || Misc::isHomePage()) {
|
||||
// If "Do not minify CSS on this page" is checked in "Asset CleanUp: Options" side meta box
|
||||
if ($isSingularPage) {
|
||||
$pageOptions = MetaBoxes::getPageOptions( WPACU_CURRENT_PAGE_ID ); // Singular page
|
||||
} else {
|
||||
$pageOptions = MetaBoxes::getPageOptions(0, 'front_page'); // Home page
|
||||
}
|
||||
|
||||
if ( isset( $pageOptions['no_css_minify'] ) && $pageOptions['no_css_minify'] ) {
|
||||
self::isMinifyCssEnabledChecked('false');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (OptimizeCss::isOptimizeCssEnabledByOtherParty('if_enabled')) {
|
||||
self::isMinifyCssEnabledChecked('false');
|
||||
return false;
|
||||
}
|
||||
|
||||
self::isMinifyCssEnabledChecked('true');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*/
|
||||
public static function isMinifyCssEnabledChecked($value)
|
||||
{
|
||||
if (! defined('WPACU_IS_MINIFY_CSS_ENABLED')) {
|
||||
if ($value === 'true') {
|
||||
define( 'WPACU_IS_MINIFY_CSS_ENABLED', true );
|
||||
} elseif ($value === 'false') {
|
||||
define( 'WPACU_IS_MINIFY_CSS_ENABLED', false );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
namespace WpAssetCleanUp\OptimiseAssets;
|
||||
|
||||
use WpAssetCleanUp\Main;
|
||||
use WpAssetCleanUp\Menu;
|
||||
use WpAssetCleanUp\MetaBoxes;
|
||||
use WpAssetCleanUp\Misc;
|
||||
|
||||
/**
|
||||
* Class MinifyJs
|
||||
* @package WpAssetCleanUp\OptimiseAssets
|
||||
*/
|
||||
class MinifyJs
|
||||
{
|
||||
/**
|
||||
* @param $jsContent
|
||||
*
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
public static function applyMinification($jsContent)
|
||||
{
|
||||
if (class_exists('\MatthiasMullie\Minify\JS')) {
|
||||
$sha1OriginalContent = sha1($jsContent);
|
||||
$checkForAlreadyMinifiedShaOne = mb_strlen($jsContent) > 40000;
|
||||
|
||||
// Let's check if the content is already minified
|
||||
// Save resources as the minify process can take time if the content is very large
|
||||
// Limit the total number of entries to 100: if it's more than that, it's likely because there's dynamic JS altering on every page load
|
||||
if ($checkForAlreadyMinifiedShaOne && OptimizeCommon::originalContentIsAlreadyMarkedAsMinified($sha1OriginalContent, 'scripts')) {
|
||||
return $jsContent;
|
||||
}
|
||||
|
||||
// Minify it
|
||||
$alreadyMinified = false; // default
|
||||
|
||||
$minifier = new \MatthiasMullie\Minify\JS($jsContent);
|
||||
$minifiedContent = trim($minifier->minify());
|
||||
|
||||
if (trim($minifiedContent) === trim(trim($jsContent, ';'))) {
|
||||
$minifiedContent = $jsContent; // consider them the same if only the ';' at the end was stripped (it doesn't worth the resources that would be used)
|
||||
$alreadyMinified = true;
|
||||
}
|
||||
|
||||
// If the resulting content is the same, mark it as minified to avoid the minify process next time
|
||||
if ($checkForAlreadyMinifiedShaOne && $alreadyMinified) {
|
||||
// If the resulting content is the same, mark it as minified to avoid the minify process next time
|
||||
OptimizeCommon::originalContentMarkAsAlreadyMinified( $sha1OriginalContent, 'scripts' );
|
||||
}
|
||||
|
||||
return $minifiedContent;
|
||||
}
|
||||
|
||||
return $jsContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $src
|
||||
* @param string $handle
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function skipMinify($src, $handle = '')
|
||||
{
|
||||
// Things like WP Fastest Cache Toolbar JS shouldn't be minified and take up space on the server
|
||||
if ($handle !== '' && in_array($handle, Main::instance()->skipAssets['scripts'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$regExps = array(
|
||||
'#/wp-content/plugins/wp-asset-clean-up(.*?).js#',
|
||||
|
||||
// Other libraries from the core that end in .min.js
|
||||
'#/wp-includes/(.*?).min.js#',
|
||||
|
||||
// jQuery & jQuery Migrate
|
||||
'#/wp-includes/js/jquery/jquery.js#',
|
||||
'#/wp-includes/js/jquery/jquery-migrate.js#',
|
||||
|
||||
// Files within /wp-content/uploads/
|
||||
// Files within /wp-content/uploads/ or /wp-content/cache/
|
||||
// Could belong to plugins such as "Elementor, "Oxygen" etc.
|
||||
//'#/wp-content/uploads/(.*?).js#',
|
||||
'#/wp-content/cache/(.*?).js#',
|
||||
|
||||
// Already minified, and it also has a random name making the cache folder make bigger
|
||||
'#/wp-content/bs-booster-cache/#',
|
||||
|
||||
// Elementor .min.js
|
||||
'#/wp-content/plugins/elementor/assets/(.*?).min.js#',
|
||||
|
||||
// WooCommerce Assets
|
||||
'#/wp-content/plugins/woocommerce/assets/js/(.*?).min.js#',
|
||||
|
||||
// Google Site Kit
|
||||
// The files are already optimized (they just have comments once in a while)
|
||||
// Minifying them causes some errors, so better to leave them load as they are
|
||||
'#/wp-content/plugins/google-site-kit/#',
|
||||
|
||||
// TranslatePress Multilingual
|
||||
'#/translatepress-multilingual/assets/js/trp-editor.js#',
|
||||
|
||||
);
|
||||
|
||||
$regExps = Misc::replaceRelPluginPath($regExps);
|
||||
|
||||
if (Main::instance()->settings['minify_loaded_js_exceptions'] !== '') {
|
||||
$loadedJsExceptionsPatterns = trim(Main::instance()->settings['minify_loaded_js_exceptions']);
|
||||
|
||||
if (strpos($loadedJsExceptionsPatterns, "\n")) {
|
||||
// Multiple values (one per line)
|
||||
foreach (explode("\n", $loadedJsExceptionsPatterns) as $loadedJsExceptionPattern) {
|
||||
$regExps[] = '#'.trim($loadedJsExceptionPattern).'#';
|
||||
}
|
||||
} else {
|
||||
// Only one value?
|
||||
$regExps[] = '#'.trim($loadedJsExceptionsPatterns).'#';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($regExps as $regExp) {
|
||||
if ( preg_match( $regExp, $src ) || ( strpos($src, $regExp) !== false ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $htmlSource
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function minifyInlineScriptTags($htmlSource)
|
||||
{
|
||||
if (stripos($htmlSource, '<script') === false) {
|
||||
return $htmlSource; // no SCRIPT tags, hmm
|
||||
}
|
||||
|
||||
$skipTagsContaining = array_map( static function ( $toMatch ) {
|
||||
return preg_quote($toMatch, '/');
|
||||
}, array(
|
||||
'data-wpacu-skip',
|
||||
'/* <![CDATA[ */', // added via wp_localize_script()
|
||||
'wpacu-google-fonts-async-load',
|
||||
'wpacu-preload-async-css-fallback',
|
||||
/* [wpacu_pro] */'data-wpacu-inline-js-file',/* [/wpacu_pro] */
|
||||
'document.body.prepend(wpacuLinkTag',
|
||||
'var wc_product_block_data = JSON.parse( decodeURIComponent(',
|
||||
'/(^|\s)(no-)?customize-support(?=\s|$)/', // WP Core
|
||||
'b[c] += ( window.postMessage && request ? \' \' : \' no-\' ) + cs;', // WP Core
|
||||
'data-wpacu-own-inline-script', // Only shown to the admin, irrelevant for any optimization (save resources)
|
||||
// [wpacu_pro]
|
||||
'data-wpacu-inline-js-file', // already minified/optimized since the INLINE was generated from the cached file
|
||||
// [/wpacu_pro]
|
||||
));
|
||||
|
||||
// Do not perform another \DOMDocument call if it was done already somewhere else (e.g. CombineJs)
|
||||
$fetchType = 'regex'; // 'regex' or 'dom'
|
||||
|
||||
if ($fetchType === 'regex') {
|
||||
preg_match_all( '@(<script[^>]*?>).*?</script>@si', $htmlSource, $matchesScriptTags, PREG_SET_ORDER );
|
||||
|
||||
if ( $matchesScriptTags === null ) {
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
foreach ($matchesScriptTags as $matchedScript) {
|
||||
if (isset($matchedScript[0]) && $matchedScript[0]) {
|
||||
$originalTag = $matchedScript[0];
|
||||
|
||||
if (strpos($originalTag, 'src=') && strtolower(substr($originalTag, -strlen('></script>'))) === strtolower('></script>')) {
|
||||
// Only inline SCRIPT tags allowed
|
||||
continue;
|
||||
}
|
||||
|
||||
// No need to use extra resources as the tag is already minified
|
||||
if ( preg_match( '/(' . implode( '|', $skipTagsContaining ) . ')/', $originalTag ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only 'text/javascript' type is allowed for minification
|
||||
$scriptType = Misc::getValueFromTag($originalTag, 'type') ?: 'text/javascript'; // default
|
||||
|
||||
if ($scriptType !== 'text/javascript') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tagOpen = $matchedScript[1];
|
||||
$withTagOpenStripped = substr($originalTag, strlen($tagOpen));
|
||||
$originalTagContents = substr($withTagOpenStripped, 0, -strlen('</script>'));
|
||||
|
||||
$newTagContents = OptimizeJs::maybeAlterContentForInlineScriptTag( $originalTagContents, true );
|
||||
|
||||
if ( $newTagContents !== $originalTagContents ) {
|
||||
$htmlSource = str_ireplace( '>' . $originalTagContents . '</script', '>' . $newTagContents . '</script', $htmlSource );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $htmlSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isMinifyJsEnabled()
|
||||
{
|
||||
if (defined('WPACU_IS_MINIFY_JS_ENABLED')) {
|
||||
return WPACU_IS_MINIFY_JS_ENABLED;
|
||||
}
|
||||
|
||||
// Request Minify On The Fly
|
||||
// It will preview the page with JS minified
|
||||
// Only if the admin is logged-in as it uses more resources (CPU / Memory)
|
||||
if ( isset($_GET['wpacu_js_minify']) && Menu::userCanManageAssets()) {
|
||||
self::isMinifyJsEnabledChecked('true');
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isset($_REQUEST['wpacu_no_js_minify']) || // not on query string request (debugging purposes)
|
||||
is_admin() || // not for Dashboard view
|
||||
(! Main::instance()->settings['minify_loaded_js']) || // Minify JS has to be Enabled
|
||||
(Main::instance()->settings['test_mode'] && ! Menu::userCanManageAssets()) ) { // Does not trigger if "Test Mode" is Enabled
|
||||
self::isMinifyJsEnabledChecked('false');
|
||||
return false;
|
||||
}
|
||||
|
||||
$isSingularPage = defined('WPACU_CURRENT_PAGE_ID') && WPACU_CURRENT_PAGE_ID > 0 && is_singular();
|
||||
|
||||
if ($isSingularPage || Misc::isHomePage()) {
|
||||
// If "Do not minify JS on this page" is checked in "Asset CleanUp: Options" side meta box
|
||||
if ($isSingularPage) {
|
||||
$pageOptions = MetaBoxes::getPageOptions( WPACU_CURRENT_PAGE_ID ); // Singular page
|
||||
} else {
|
||||
$pageOptions = MetaBoxes::getPageOptions(0, 'front_page'); // Home page
|
||||
}
|
||||
|
||||
if ( isset( $pageOptions['no_js_minify'] ) && $pageOptions['no_js_minify'] ) {
|
||||
self::isMinifyJsEnabledChecked('false');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (OptimizeJs::isOptimizeJsEnabledByOtherParty('if_enabled')) {
|
||||
self::isMinifyJsEnabledChecked('false');
|
||||
return false;
|
||||
}
|
||||
|
||||
self::isMinifyJsEnabledChecked('true');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*/
|
||||
public static function isMinifyJsEnabledChecked($value)
|
||||
{
|
||||
if (! defined('WPACU_IS_MINIFY_JS_ENABLED')) {
|
||||
if ($value === 'true') {
|
||||
define( 'WPACU_IS_MINIFY_JS_ENABLED', true );
|
||||
} elseif ($value === 'false') {
|
||||
define( 'WPACU_IS_MINIFY_JS_ENABLED', false );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user