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