884 lines
32 KiB
PHP
884 lines
32 KiB
PHP
|
<?php
|
||
|
namespace WpAssetCleanUp;
|
||
|
|
||
|
use WpAssetCleanUp\OptimiseAssets\OptimizeCommon;
|
||
|
use WpAssetCleanUp\OptimiseAssets\OptimizeJs;
|
||
|
|
||
|
/**
|
||
|
* Class HardcodedAssets
|
||
|
* @package WpAssetCleanUp
|
||
|
*/
|
||
|
class HardcodedAssets
|
||
|
{
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
public static function init()
|
||
|
{
|
||
|
add_action( 'init', static function() {
|
||
|
if (Main::instance()->isGetAssetsCall) {
|
||
|
// Case 1: An AJAX call is made from the Dashboard
|
||
|
self::initBufferingForAjaxCallFromTheDashboard();
|
||
|
} elseif (self::useBufferingForEditFrontEndView()) {
|
||
|
// Case 2: The logged-in admin manages the assets from the front-end view
|
||
|
self::initBufferingForFrontendManagement();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
public static function initBufferingForAjaxCallFromTheDashboard()
|
||
|
{
|
||
|
ob_start();
|
||
|
|
||
|
add_action('shutdown', static function() {
|
||
|
$htmlSource = '';
|
||
|
|
||
|
// We'll need to get the number of ob levels we're in, so that we can iterate over each, collecting
|
||
|
// that buffer's output into the final output.
|
||
|
$htmlSourceLevel = ob_get_level();
|
||
|
|
||
|
for ($wpacuI = 0; $wpacuI < $htmlSourceLevel; $wpacuI++) {
|
||
|
$htmlSource .= ob_get_clean();
|
||
|
}
|
||
|
|
||
|
$anyHardCodedAssets = HardcodedAssets::getAll($htmlSource); // Fetch all for this type of request
|
||
|
|
||
|
echo str_replace('{wpacu_hardcoded_assets}', $anyHardCodedAssets, $htmlSource);
|
||
|
}, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
public static function initBufferingForFrontendManagement()
|
||
|
{
|
||
|
// Used to print the hardcoded CSS/JS
|
||
|
ob_start();
|
||
|
|
||
|
add_action('shutdown', static function() {
|
||
|
if (! defined('NEXTEND_SMARTSLIDER_3_URL_PATH')) {
|
||
|
ob_flush();
|
||
|
}
|
||
|
|
||
|
$htmlSource = '';
|
||
|
|
||
|
// We'll need to get the number of ob levels we're in, so that we can iterate over each, collecting
|
||
|
// that buffer's output into the final output.
|
||
|
$htmlSourceLevel = ob_get_level();
|
||
|
|
||
|
for ($wpacuI = 0; $wpacuI < $htmlSourceLevel; $wpacuI++) {
|
||
|
$htmlSource .= ob_get_clean();
|
||
|
}
|
||
|
|
||
|
echo OptimizeCommon::alterHtmlSource($htmlSource);
|
||
|
|
||
|
}, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function useBufferingForEditFrontEndView()
|
||
|
{
|
||
|
// The logged-in admin needs to be outside the Dashboard (in the front-end view)
|
||
|
// "Manage in the Front-end" is enabled in "Settings" -> "Plugin Usage Preferences"
|
||
|
return (Main::instance()->frontendShow() && ! is_admin() && Menu::userCanManageAssets() && ! Main::instance()->isGetAssetsCall);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $htmlSource
|
||
|
* @param bool $encodeIt - if set to "false", it's mostly for testing purposes
|
||
|
*
|
||
|
* @return string|array
|
||
|
*/
|
||
|
public static function getAll($htmlSource, $encodeIt = true)
|
||
|
{
|
||
|
$htmlSourceAlt = CleanUp::removeHtmlComments($htmlSource, true);
|
||
|
|
||
|
$collectLinkStyles = true; // default
|
||
|
$collectScripts = true; // default
|
||
|
|
||
|
$hardCodedAssets = array(
|
||
|
'link_and_style_tags' => array(), // LINK (rel="stylesheet") & STYLE (inline)
|
||
|
'script_nosrc_and_inline_tags' => array(), // SCRIPT (with "src" attribute) & SCRIPT (inline)
|
||
|
);
|
||
|
|
||
|
$matchesSourcesFromTags = array();
|
||
|
|
||
|
$stickToRegEx = true;
|
||
|
|
||
|
if ( $collectLinkStyles ) {
|
||
|
if ( ! $stickToRegEx && Misc::isDOMDocumentOn() ) {
|
||
|
$domDoc = Misc::initDOMDocument();
|
||
|
$domDoc->loadHTML($htmlSourceAlt);
|
||
|
|
||
|
$selector = new \DOMXPath($domDoc);
|
||
|
|
||
|
$domTagQuery = $selector->query('//link[@rel="stylesheet"]|//style|//script|//noscript');
|
||
|
|
||
|
if (count($domTagQuery) > 1) {
|
||
|
foreach($domTagQuery as $tagFound) {
|
||
|
$tagType = in_array($tagFound->nodeName, array('link', 'style')) ? 'css' : 'js';
|
||
|
|
||
|
if (self::skipTagIfNotRelevant( Misc::getOuterHTML( $tagFound ), 'whole_tag', $tagType)) {
|
||
|
continue; // no point in wasting more resources as the tag will never be shown, since it's irrelevant
|
||
|
}
|
||
|
|
||
|
if ( $tagFound->hasAttributes() ) {
|
||
|
foreach ( $tagFound->attributes as $attr ) {
|
||
|
if ( self::skipTagIfNotRelevant( $attr->nodeName, 'attribute', $tagType ) ) {
|
||
|
continue 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($tagFound->nodeName === 'link') {
|
||
|
if ( ! $tagFound->hasAttributes() ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$linkTagParts = array();
|
||
|
$linkTagParts[] = '<link ';
|
||
|
|
||
|
foreach ($tagFound->attributes as $attr) {
|
||
|
$attrName = $attr->nodeName;
|
||
|
$attrValue = $attr->nodeValue;
|
||
|
|
||
|
if ($attrName) {
|
||
|
if ($attrValue !== '') {
|
||
|
$linkTagParts[] = '(\s+|)' . preg_quote($attrName, '/') . '(\s+|)=(\s+|)(|"|\')' . preg_quote($attrValue, '/') . '(|"|\')(|\s+)';
|
||
|
} else {
|
||
|
$linkTagParts[] = '(\s+|)' . preg_quote($attrName, '/') . '(|((\s+|)=(\s+|)(|"|\')(|"|\')))';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$linkTagParts[] = '(|\s+)(|/)>';
|
||
|
|
||
|
$linkTagFinalRegExPart = implode('', $linkTagParts);
|
||
|
|
||
|
preg_match_all(
|
||
|
'#'.$linkTagFinalRegExPart.'#Umi',
|
||
|
$htmlSource,
|
||
|
$matchSourceFromTag,
|
||
|
PREG_SET_ORDER
|
||
|
);
|
||
|
|
||
|
// It always has to be a match from the DOM generated tag
|
||
|
// Otherwise, default it to RegEx
|
||
|
if ( empty($matchSourceFromTag) || ! (isset($matchSourceFromTag[0][0]) && ! empty($matchSourceFromTag[0][0])) ) {
|
||
|
$stickToRegEx = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$matchesSourcesFromTags[] = array('link_tag' => $matchSourceFromTag[0][0]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (! $stickToRegEx) {
|
||
|
$shaOneToOriginal = array();
|
||
|
|
||
|
$htmlSourceAltEncoded = $htmlSourceAlt;
|
||
|
|
||
|
foreach($domTagQuery as $tagFound) {
|
||
|
if ( $tagFound->nodeValue && in_array( $tagFound->nodeName, array( 'style', 'script', 'noscript' ) ) ) {
|
||
|
if (strpos($htmlSourceAlt, $tagFound->nodeValue) === false) {
|
||
|
$stickToRegEx = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$shaOneToOriginal[sha1($tagFound->nodeValue)] = $tagFound->nodeValue;
|
||
|
|
||
|
$htmlSourceAltEncoded = str_replace(
|
||
|
$tagFound->nodeValue,
|
||
|
'/*[wpacu]*/' . sha1($tagFound->nodeValue) . '/*[/wpacu]*/',
|
||
|
$htmlSourceAltEncoded
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$domDocForTwo = Misc::initDOMDocument();
|
||
|
|
||
|
$domDocForTwo->loadHTML($htmlSourceAltEncoded);
|
||
|
|
||
|
$selectorTwo = new \DOMXPath($domDocForTwo);
|
||
|
|
||
|
$domTagQueryTwo = $selectorTwo->query('//style|//script|//noscript');
|
||
|
|
||
|
foreach($domTagQueryTwo as $tagFoundTwo) {
|
||
|
$tagType = in_array($tagFoundTwo->nodeName, array('link', 'style')) ? 'css' : 'js';
|
||
|
|
||
|
if ( $tagFoundTwo->hasAttributes() ) {
|
||
|
foreach ( $tagFoundTwo->attributes as $attr ) {
|
||
|
if ( self::skipTagIfNotRelevant( $attr->nodeName, 'attribute', $tagType ) ) {
|
||
|
continue 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$tagParts = array();
|
||
|
$tagParts[] = '<'.$tagFoundTwo->nodeName;
|
||
|
|
||
|
foreach ($tagFoundTwo->attributes as $attr) {
|
||
|
$attrName = $attr->nodeName;
|
||
|
$attrValue = $attr->nodeValue;
|
||
|
|
||
|
if ($attrName) {
|
||
|
if ($attrValue !== '') {
|
||
|
$tagParts[] = '(\s+|)' . preg_quote($attrName, '/') . '(\s+|)=(\s+|)(|"|\')' . preg_quote($attrValue, '/') . '(|"|\')(|\s+)';
|
||
|
} else {
|
||
|
$tagParts[] = '(\s+|)' . preg_quote($attrName, '/') . '(|((\s+|)=(\s+|)(|"|\')(|"|\')))';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$tagParts[] = '(|\s+)>';
|
||
|
|
||
|
if ($tagFoundTwo->nodeValue) {
|
||
|
$tagParts[] = preg_quote($tagFoundTwo->nodeValue, '/');
|
||
|
}
|
||
|
|
||
|
$tagParts[] = '</'.$tagFoundTwo->nodeName.'>';
|
||
|
|
||
|
$tagFinalRegExPart = implode('', $tagParts);
|
||
|
|
||
|
preg_match_all(
|
||
|
'#'.$tagFinalRegExPart.'#Umi',
|
||
|
$htmlSourceAltEncoded,
|
||
|
$matchSourceFromTagTwo,
|
||
|
PREG_SET_ORDER
|
||
|
);
|
||
|
|
||
|
// It always has to be a match from the DOM generated tag
|
||
|
// Otherwise, default it to RegEx
|
||
|
if ( empty($matchSourceFromTagTwo) || ! (isset($matchSourceFromTagTwo[0][0]) && ! empty($matchSourceFromTagTwo[0][0])) ) {
|
||
|
$stickToRegEx = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$encodedNodeValue = Misc::extractBetween($matchSourceFromTagTwo[0][0], '/*[wpacu]*/', '/*[/wpacu]*/');
|
||
|
|
||
|
$matchedTag = str_replace('/*[wpacu]*/'.$encodedNodeValue.'/*[/wpacu]*/', $shaOneToOriginal[$encodedNodeValue], $matchSourceFromTagTwo[0][0]);
|
||
|
|
||
|
$tagTypeForReference = ($tagFoundTwo->nodeName === 'style') ? 'style_tag' : 'script_noscript_tag';
|
||
|
|
||
|
$matchesSourcesFromTags[] = array($tagTypeForReference => $matchedTag);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* [START] Collect Hardcoded LINK (stylesheet) & STYLE tags
|
||
|
*/
|
||
|
if ($stickToRegEx || ! Misc::isDOMDocumentOn()) {
|
||
|
preg_match_all(
|
||
|
'#(?=(?P<link_tag><link[^>]*stylesheet[^>]*(>)))|(?=(?P<style_tag><style[^>]*?>.*</style>))#Umsi',
|
||
|
$htmlSourceAlt,
|
||
|
$matchesSourcesFromTags,
|
||
|
PREG_SET_ORDER
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if ( ! empty( $matchesSourcesFromTags ) ) {
|
||
|
// Only the hashes are set
|
||
|
// For instance, 'd1eae32c4e99d24573042dfbb71f5258a86e2a8e' is the hash for the following script:
|
||
|
/*
|
||
|
* <style media="print">#wpadminbar { display:none; }</style>
|
||
|
*/
|
||
|
$stripsSpecificStylesHashes = array(
|
||
|
'5ead5f033961f3b8db362d2ede500051f659dd6d',
|
||
|
'25bd090513716c34b48b0495c834d2070088ad24'
|
||
|
);
|
||
|
|
||
|
// Sometimes, the hash checking might fail (if there's a small change to the JS content)
|
||
|
// Consider using a fallback verification by checking the actual content
|
||
|
$stripsSpecificStylesContaining = array(
|
||
|
'<style media="print">#wpadminbar { display:none; }</style>',
|
||
|
'id="edd-store-menu-styling"',
|
||
|
'#wp-admin-bar-gform-forms'
|
||
|
);
|
||
|
|
||
|
foreach ( $matchesSourcesFromTags as $matchedTag ) {
|
||
|
// LINK "stylesheet" tags (if any)
|
||
|
if ( isset( $matchedTag['link_tag'] ) && trim( $matchedTag['link_tag'] ) !== '' && ( trim( strip_tags( $matchedTag['link_tag'] ) ) === '' ) ) {
|
||
|
$matchedTagOutput = trim( $matchedTag['link_tag'] );
|
||
|
|
||
|
// Own plugin assets and enqueued ones since they aren't hardcoded
|
||
|
if (self::skipTagIfNotRelevant($matchedTagOutput)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$hardCodedAssets['link_and_style_tags'][] = $matchedTagOutput;
|
||
|
}
|
||
|
|
||
|
// STYLE inline tags (if any)
|
||
|
if ( isset( $matchedTag['style_tag'] ) && trim( $matchedTag['style_tag'] ) !== '' ) {
|
||
|
$matchedTagOutput = trim( $matchedTag['style_tag'] );
|
||
|
|
||
|
/*
|
||
|
* Strip certain STYLE tags irrelevant for the list (e.g. related to the WordPress Admin Bar, etc.)
|
||
|
*/
|
||
|
if ( in_array( self::determineHardcodedAssetSha1( $matchedTagOutput ), $stripsSpecificStylesHashes ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
foreach ( $stripsSpecificStylesContaining as $cssContentTargeted ) {
|
||
|
if ( strpos( $matchedTagOutput, $cssContentTargeted ) !== false ) {
|
||
|
continue 2; // applies for this "foreach": ($matchesSourcesFromTags as $matchedTag)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Own plugin assets and enqueued ones since they aren't hardcoded
|
||
|
if (self::skipTagIfNotRelevant($matchedTagOutput)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
foreach ( wp_styles()->done as $cssHandle ) {
|
||
|
if ( strpos( $matchedTagOutput,
|
||
|
'<style id=\'' . trim( $cssHandle ) . '-inline-css\'' ) !== false ) {
|
||
|
// Do not consider the STYLE added via WordPress with wp_add_inline_style() as it's not hardcoded
|
||
|
continue 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$hardCodedAssets['link_and_style_tags'][] = $matchedTagOutput;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* [END] Collect Hardcoded LINK (stylesheet) & STYLE tags
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
if ($collectScripts) {
|
||
|
/*
|
||
|
* [START] Collect Hardcoded SCRIPT (src/inline)
|
||
|
*/
|
||
|
if ($stickToRegEx || ! Misc::isDOMDocumentOn()) {
|
||
|
preg_match_all( '@<script[^>]*?>.*?</script>|<noscript[^>]*?>.*?</noscript>@si', $htmlSourceAlt, $matchesScriptTags, PREG_SET_ORDER );
|
||
|
} else {
|
||
|
$matchesScriptTags = array();
|
||
|
|
||
|
if (! empty($matchesSourcesFromTags)) {
|
||
|
foreach ($matchesSourcesFromTags as $matchedTag) {
|
||
|
if (isset($matchedTag['script_noscript_tag']) && $matchedTag['script_noscript_tag']) {
|
||
|
$matchesScriptTags[][0] = $matchedTag['script_noscript_tag'];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$allInlineAssocWithJsHandle = array();
|
||
|
|
||
|
if ( isset( wp_scripts()->done ) && ! empty( wp_scripts()->done ) ) {
|
||
|
foreach ( wp_scripts()->done as $assetHandle ) {
|
||
|
// Now, go through the list of inline SCRIPTs associated with an enqueued SCRIPT (with "src" attribute)
|
||
|
// And make sure they do not show to the hardcoded list, since they are related to the handle, and they are stripped when the handle is dequeued
|
||
|
$anyInlineAssocWithJsHandle = OptimizeJs::getInlineAssociatedWithScriptHandle( $assetHandle, wp_scripts()->registered, 'handle' );
|
||
|
if ( ! empty( $anyInlineAssocWithJsHandle ) ) {
|
||
|
foreach ( $anyInlineAssocWithJsHandle as $jsInlineTagContent ) {
|
||
|
if ( trim( $jsInlineTagContent ) === '' ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$allInlineAssocWithJsHandle[] = trim($jsInlineTagContent);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$allInlineAssocWithJsHandle = array_unique($allInlineAssocWithJsHandle);
|
||
|
}
|
||
|
|
||
|
// Go through the hardcoded SCRIPT tags
|
||
|
if ( isset( $matchesScriptTags ) && ! empty( $matchesScriptTags ) ) {
|
||
|
// Only the hashes are set
|
||
|
// For instance, 'd1eae32c4e99d24573042dfbb71f5258a86e2a8e' is the hash for the following script:
|
||
|
/*
|
||
|
* <script>
|
||
|
(function() {
|
||
|
var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
|
||
|
request = true;
|
||
|
b[c] = b[c].replace( rcs, ' ' );
|
||
|
// The customizer requires postMessage and CORS (if the site is cross domain)
|
||
|
b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
|
||
|
}());
|
||
|
</script>
|
||
|
*/
|
||
|
$stripsSpecificScriptsHashes = array(
|
||
|
'd1eae32c4e99d24573042dfbb71f5258a86e2a8e',
|
||
|
'1a8f46f9f33e5d95919620df54781acbfa9efff7'
|
||
|
);
|
||
|
|
||
|
// Sometimes, the hash checking might fail (if there's a small change to the JS content)
|
||
|
// Consider using a fallback verification by checking the actual content
|
||
|
$stripsSpecificScriptsContaining = array(
|
||
|
'// The customizer requires postMessage and CORS (if the site is cross domain)',
|
||
|
'b[c] += ( window.postMessage && request ? \' \' : \' no-\' ) + cs;',
|
||
|
"(function(){var request,b=document.body,c='className',cs='customize-support',rcs=new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');request=!0;b[c]=b[c].replace(rcs,' ');b[c]+=(window.postMessage&&request?' ':' no-')+cs}())",
|
||
|
'document.body.className = document.body.className.replace( /(^|\s)(no-)?customize-support(?=\s|$)/, \'\' ) + \' no-customize-support\'',
|
||
|
"c = c.replace(/woocommerce-no-js/, 'woocommerce-js');" // WooCommerce related
|
||
|
);
|
||
|
|
||
|
foreach ( $matchesScriptTags as $matchedTag ) {
|
||
|
if ( isset( $matchedTag[0] ) && $matchedTag[0]
|
||
|
&& (stripos( $matchedTag[0], '<script' ) === 0 || stripos( $matchedTag[0], '<noscript' ) === 0) ) {
|
||
|
$matchedTagOutput = trim( $matchedTag[0] );
|
||
|
|
||
|
// Own plugin assets and enqueued ones since they aren't hardcoded
|
||
|
if (self::skipTagIfNotRelevant($matchedTagOutput, 'whole_tag', 'js', array('all_inline_assoc_with_js_handle' => $allInlineAssocWithJsHandle))) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Strip certain SCRIPT tags irrelevant for the list (e.g. related to WordPress Customiser, Admin Bar, etc.)
|
||
|
*/
|
||
|
if ( in_array( self::determineHardcodedAssetSha1( $matchedTagOutput ), $stripsSpecificScriptsHashes ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
foreach ( $stripsSpecificScriptsContaining as $jsContentTargeted ) {
|
||
|
if ( strpos( $matchedTagOutput, $jsContentTargeted ) !== false ) {
|
||
|
continue 2; // applies for this "foreach": ($matchesScriptTags as $matchedTag)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$hardCodedAssets['script_src_or_inline_and_noscript_inline_tags'][] = trim( $matchedTag[0] );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* [END] Collect Hardcoded SCRIPT (src/inline)
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
if ($stickToRegEx && ! empty($hardCodedAssets['link_and_style_tags']) && ! empty($hardCodedAssets['script_src_or_inline_and_noscript_inline_tags'])) {
|
||
|
$hardCodedAssets = self::removeAnyLinkTagsThatMightBeDetectedWithinScriptTags( $hardCodedAssets );
|
||
|
}
|
||
|
|
||
|
$tagsWithinConditionalComments = self::extractHtmlFromConditionalComments( $htmlSourceAlt );
|
||
|
|
||
|
if (Main::instance()->isGetAssetsCall) {
|
||
|
// AJAX call within the Dashboard
|
||
|
$hardCodedAssets['within_conditional_comments'] = $tagsWithinConditionalComments;
|
||
|
}
|
||
|
|
||
|
if ($encodeIt) {
|
||
|
return base64_encode( wp_json_encode( $hardCodedAssets ) );
|
||
|
}
|
||
|
|
||
|
return $hardCodedAssets;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $value
|
||
|
* @param $via ('whole_tag', 'attribute')
|
||
|
* @param string $type ('css', 'js')
|
||
|
* @param array $extras ('all_inline_assoc_with_js_handle')
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function skipTagIfNotRelevant($value, $via = 'whole_tag', $type = 'css', $extras = array())
|
||
|
{
|
||
|
if ($via === 'whole_tag') {
|
||
|
if ($type === 'css') {
|
||
|
if ( strpos( $value, 'data-wpacu-style-handle=' ) !== false ) {
|
||
|
// skip the SCRIPT with src that was enqueued properly and keep the hardcoded ones
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( ( strpos( $value, 'data-wpacu-own-inline-style=' ) !== false ) ||
|
||
|
( strpos( $value, 'data-wpacu-inline-css-file=' ) !== false ) ) {
|
||
|
// remove plugin's own STYLE tags as they are not relevant in this context
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Do not add to the list elements such as Emojis (not relevant for hard-coded tags)
|
||
|
if ( strpos( $value, 'img.wp-smiley' ) !== false
|
||
|
&& strpos( $value, 'img.emoji' ) !== false
|
||
|
&& strpos( $value, '!important;' ) !== false ) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($type === 'js') {
|
||
|
if ( strpos( $value, 'data-wpacu-script-handle=' ) !== false ) {
|
||
|
// skip the SCRIPT with src that was enqueued properly and keep the hardcoded ones
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( ( strpos( $value, 'data-wpacu-own-inline-script=' ) !== false ) ||
|
||
|
( strpos( $value, 'data-wpacu-inline-js-file=' ) !== false ) ) {
|
||
|
// skip plugin's own SCRIPT tags as they are not relevant in this context
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( strpos( $value, 'wpacu-preload-async-css-fallback' ) !== false ) {
|
||
|
// skip plugin's own SCRIPT tags as they are not relevant in this context
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( strpos( $value, 'window._wpemojiSettings' ) !== false
|
||
|
&& strpos( $value, 'twemoji' ) !== false ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Check the type and only allow SCRIPT tags with type='text/javascript' or no type at all (it will default to 'text/javascript')
|
||
|
$matchedTagInner = strip_tags( $value );
|
||
|
$matchedTagOnlyTags = str_replace( $matchedTagInner, '', $value );
|
||
|
|
||
|
$scriptType = Misc::getValueFromTag($matchedTagOnlyTags, 'type') ?: 'text/javascript';
|
||
|
|
||
|
if ( strpos( $scriptType, 'text/javascript' ) === false ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
$allInlineAssocWithJsHandle = isset($extras['all_inline_assoc_with_js_handle']) ? $extras['all_inline_assoc_with_js_handle'] : array();
|
||
|
|
||
|
$hasSrc = false;
|
||
|
|
||
|
if (strpos($matchedTagOnlyTags, ' src=') !== false) {
|
||
|
$hasSrc = true;
|
||
|
}
|
||
|
|
||
|
if ( ! $hasSrc && ! empty( $allInlineAssocWithJsHandle ) ) {
|
||
|
preg_match_all("'<script[^>]*?>(.*?)</script>'si", $value, $matchesFromTagOutput);
|
||
|
$matchedTagOutputInner = isset($matchesFromTagOutput[1][0]) && trim($matchesFromTagOutput[1][0])
|
||
|
? trim($matchesFromTagOutput[1][0]) : false;
|
||
|
|
||
|
$matchedTagOutputInnerCleaner = $matchedTagOutputInner;
|
||
|
|
||
|
$stripStrStart = '/* <![CDATA[ */';
|
||
|
$stripStrEnd = '/* ]]> */';
|
||
|
|
||
|
if (strpos($matchedTagOutputInnerCleaner, $stripStrStart) === 0
|
||
|
&& Misc::endsWith($matchedTagOutputInnerCleaner, '/* ]]> */')) {
|
||
|
$matchedTagOutputInnerCleaner = substr($matchedTagOutputInnerCleaner, strlen($stripStrStart));
|
||
|
$matchedTagOutputInnerCleaner = substr($matchedTagOutputInnerCleaner, 0, -strlen($stripStrEnd));
|
||
|
$matchedTagOutputInnerCleaner = trim($matchedTagOutputInnerCleaner);
|
||
|
}
|
||
|
|
||
|
if (in_array($matchedTagOutputInnerCleaner, $allInlineAssocWithJsHandle)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($via === 'attribute') {
|
||
|
if ( $type === 'css' ) {
|
||
|
$possibleSignatures = array(
|
||
|
'data-wpacu-style-handle',
|
||
|
'data-wpacu-own-inline-style',
|
||
|
'data-wpacu-inline-css-file'
|
||
|
);
|
||
|
} else {
|
||
|
$possibleSignatures = array(
|
||
|
'data-wpacu-script-handle',
|
||
|
'data-wpacu-own-inline-script',
|
||
|
'data-wpacu-inline-js-file',
|
||
|
'wpacu-preload-async-css-fallback'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (in_array($value, $possibleSignatures)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false; // default
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param $hardcodedAssets
|
||
|
*
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public static function removeAnyLinkTagsThatMightBeDetectedWithinScriptTags($hardcodedAssets)
|
||
|
{
|
||
|
foreach ($hardcodedAssets['link_and_style_tags'] as $cssTagIndex => $cssTag) {
|
||
|
if ($cssTag) {
|
||
|
foreach ($hardcodedAssets['script_src_or_inline_and_noscript_inline_tags'] as $scriptTag) {
|
||
|
if (strpos($scriptTag, $cssTag) !== false) {
|
||
|
// e.g. could be '<script>var linkToCss="<link href='[path_to_custom_css_file_here]'>";</script>'
|
||
|
unset($hardcodedAssets['link_and_style_tags'][$cssTagIndex]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $hardcodedAssets;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $htmlSource
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public static function extractHtmlFromConditionalComments($htmlSource)
|
||
|
{
|
||
|
preg_match_all('#<!--\[if(.*?)]>(<!-->|-->|\s|)(.*?)(<!--<!|<!)\[endif]-->#si', $htmlSource, $matchedContent);
|
||
|
|
||
|
if (isset($matchedContent[1], $matchedContent[3]) && ! empty($matchedContent[1]) && ! empty($matchedContent[3])) {
|
||
|
$conditions = array_map('trim', $matchedContent[1]);
|
||
|
$tags = array_map('trim', $matchedContent[3]);
|
||
|
|
||
|
return array(
|
||
|
'conditions' => $conditions,
|
||
|
'tags' => $tags,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $targetedTag
|
||
|
* @param $contentWithinConditionalComments
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function isWithinConditionalComment($targetedTag, $contentWithinConditionalComments)
|
||
|
{
|
||
|
if (empty($contentWithinConditionalComments)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$targetedTag = trim($targetedTag);
|
||
|
|
||
|
foreach ($contentWithinConditionalComments['tags'] as $tagIndex => $tagFromList) {
|
||
|
$tagFromList = trim($tagFromList);
|
||
|
|
||
|
if ($targetedTag === $tagFromList || strpos($targetedTag, $tagFromList) !== false) {
|
||
|
return $contentWithinConditionalComments['conditions'][$tagIndex]; // Stops here and returns the condition
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false; // Not within a conditional comment (most cases)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $htmlTag
|
||
|
*
|
||
|
* @return bool|string
|
||
|
*/
|
||
|
public static function belongsTo($htmlTag)
|
||
|
{
|
||
|
$belongList = array(
|
||
|
'wpcf7recaptcha.' => '"Contact Form 7" plugin',
|
||
|
'c = c.replace(/woocommerce-no-js/, \'woocommerce-js\');' => '"WooCommerce" plugin',
|
||
|
'.woocommerce-product-gallery{ opacity: 1 !important; }' => '"WooCommerce" plugin',
|
||
|
'-ss-slider-3' => '"Smart Slider 3" plugin',
|
||
|
'N2R(["nextend-frontend","smartslider-frontend","smartslider-simple-type-frontend"]' => '"Smart Slider 3" plugin',
|
||
|
'function setREVStartSize' => '"Slider Revolution" plugin',
|
||
|
'jQuery(\'.rev_slider_wrapper\')' => '"Slider Revolution" plugin',
|
||
|
'jQuery(\'#wp-admin-bar-revslider-default' => '"Slider Revolution" plugin'
|
||
|
);
|
||
|
|
||
|
foreach ($belongList as $ifContains => $isFromSource) {
|
||
|
if ( strpos( $htmlTag, $ifContains) !== false ) {
|
||
|
return $isFromSource;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $tagOutput
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function determineHardcodedAssetSha1($tagOutput)
|
||
|
{
|
||
|
// Look if the "href" or "src" ends with '.css' or '.js'
|
||
|
// Only hash the actual path to the file
|
||
|
// In case the tag changes (e.g. an attribute will be added), the tag will be considered the same for the plugin rules
|
||
|
// To avoid the rules from not working / e.g. the file could have a dynamic "?ver=" at the end
|
||
|
if ( ! (stripos($tagOutput, '<link') !== false || stripos($tagOutput, '<style') !== false
|
||
|
|| stripos($tagOutput, '<script') !== false || stripos($tagOutput, '<noscript') !== false) ) {
|
||
|
return sha1( $tagOutput ); // default
|
||
|
}
|
||
|
|
||
|
$isLinkWithHref = (stripos($tagOutput, '<link') !== false) && preg_match('#href(\s+|)=(\s+|)(["\'])(.*)(["\'])#Usmi', $tagOutput);
|
||
|
$isScriptWithSrc = (stripos($tagOutput, '<script') !== false) && preg_match('#src(\s+|)=(\s+|)(["\'])(.*)(["\'])|src(\s+|)=(\s+|)(.*)(\s+)#Usmi', $tagOutput);
|
||
|
|
||
|
if ($isLinkWithHref || $isScriptWithSrc) {
|
||
|
return self::determineHardcodedAssetSha1ForAssetsWithSource($tagOutput);
|
||
|
}
|
||
|
|
||
|
if (stripos($tagOutput, '<style') !== false || stripos($tagOutput, '<script') !== false || stripos($tagOutput, '<noscript') !== false) {
|
||
|
return self::determineHardcodedAssetSha1ForAssetsWithoutSource($tagOutput);
|
||
|
}
|
||
|
|
||
|
return sha1( $tagOutput ); // default
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
// In case there are STYLE tags and SCRIPT tags without any SRC, make sure to consider only the content of the tag as a reference
|
||
|
// e.g. if the user updates <style type="text/css"> to <style> the tag should be considered the same if the content is the same
|
||
|
// also, do not consider any whitespaces from the beginning and ending of the tag's content
|
||
|
*
|
||
|
* @param $tagOutput
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function determineHardcodedAssetSha1ForAssetsWithoutSource($tagOutput)
|
||
|
{
|
||
|
if (stripos($tagOutput, '<style') !== false) {
|
||
|
preg_match_all('@(<style[^>]*?>)(.*?)</style>@si', $tagOutput, $matches);
|
||
|
|
||
|
if (isset($matches[0][0], $matches[2][0]) && strlen($tagOutput) === strlen($matches[0][0])) {
|
||
|
return sha1( trim($matches[2][0]) ); // the hashed content of the STYLE tag
|
||
|
}
|
||
|
} elseif (stripos($tagOutput, '<script') !== false) {
|
||
|
preg_match_all('@(<script[^>]*?>)(.*?)</script>@si', $tagOutput, $matches);
|
||
|
|
||
|
if (isset($matches[0][0], $matches[2][0]) && strlen($tagOutput) === strlen($matches[0][0])) {
|
||
|
return sha1( trim($matches[2][0]) ); // the hashed content of the SCRIPT tag
|
||
|
}
|
||
|
} elseif (stripos($tagOutput, '<noscript') !== false) {
|
||
|
preg_match_all('@(<noscript[^>]*?>)(.*?)</noscript>@si', $tagOutput, $matches);
|
||
|
|
||
|
if (isset($matches[0][0], $matches[2][0]) && strlen($tagOutput) === strlen($matches[0][0])) {
|
||
|
return sha1( trim($matches[2][0]) ); // the hashed content of the NOSCRIPT tag
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return sha1($tagOutput);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Only the LINK tags and SCRIPT tags with the "href" and "src" attributes would be considered
|
||
|
*
|
||
|
* @param $tagOutput
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function determineHardcodedAssetSha1ForAssetsWithSource($tagOutput)
|
||
|
{
|
||
|
if ($finalCleanSource = self::getRelSourceFromTagOutputForReference($tagOutput)) {
|
||
|
return sha1($finalCleanSource);
|
||
|
}
|
||
|
|
||
|
return sha1( $tagOutput ); // default
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $tagOutput
|
||
|
*
|
||
|
* @return array|false|string|string[]
|
||
|
*/
|
||
|
public static function getRelSourceFromTagOutputForReference($tagOutput)
|
||
|
{
|
||
|
if (stripos($tagOutput, 'href') !== false && stripos($tagOutput, 'stylesheet') !== false && stripos(trim($tagOutput), '<link') === 0) {
|
||
|
$attrToGet = 'href';
|
||
|
$extToCheck = 'css';
|
||
|
} else {
|
||
|
$attrToGet = 'src';
|
||
|
$extToCheck = 'js';
|
||
|
}
|
||
|
|
||
|
$sourceValue = Misc::getValueFromTag($tagOutput, $attrToGet);
|
||
|
|
||
|
if (! $sourceValue) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( stripos( $sourceValue, '.' . $extToCheck . '?' ) !== false ) {
|
||
|
list( $cleanSource ) = explode( '.' . $extToCheck . '?', $sourceValue );
|
||
|
$finalCleanSource = $cleanSource . '.' . $extToCheck;
|
||
|
} else {
|
||
|
$finalCleanSource = $sourceValue;
|
||
|
}
|
||
|
|
||
|
if ( $finalCleanSource ) {
|
||
|
$localAssetPath = OptimizeCommon::getLocalAssetPath( $finalCleanSource, $extToCheck );
|
||
|
|
||
|
if ( $localAssetPath ) {
|
||
|
$sourceRelPath = OptimizeCommon::getSourceRelPath( $finalCleanSource );
|
||
|
|
||
|
if ( $sourceRelPath ) {
|
||
|
return $finalCleanSource;
|
||
|
}
|
||
|
} else {
|
||
|
$finalCleanSource = str_ireplace( array( 'http://', 'https://' ), '', $finalCleanSource );
|
||
|
$finalCleanSource = ( strpos( $finalCleanSource, '//' ) === 0 ) ? substr( $finalCleanSource, 2 ) : $finalCleanSource; // if the string starts with '//', remove it
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $finalCleanSource;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $data
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function getHardCodedManageAreaForFrontEndView($data)
|
||
|
{
|
||
|
$dataSettingsFrontEnd = ObjectCache::wpacu_cache_get('wpacu_settings_frontend_data') ?: array();
|
||
|
$dataSettingsFrontEnd['page_unload_text'] = esc_html($data['page_unload_text']);
|
||
|
// The following string will be replaced by the values got from the AJAX call to /?wpassetcleanup_load=1&wpacu_just_hardcoded
|
||
|
$dataWpacuSettingsFrontend = base64_encode(wp_json_encode($dataSettingsFrontEnd));
|
||
|
|
||
|
$currentHardcodedAssetRules = '';
|
||
|
|
||
|
// When the form is submitted it will clear some values if they are not sent anymore which can happen with a failed AJAX call to retrieve the list of hardcoded assets
|
||
|
// Place the current values to the area in case the AJAX call fails, and it won't print the list
|
||
|
// If the user presses "Update", it won't clear any existing rules
|
||
|
// If the list is printed, obviously it will be with all the fields in place as they should be
|
||
|
foreach (array('current_unloaded_page_level', 'load_exceptions', 'handle_unload_regex', 'handle_load_regex', 'handle_load_logged_in') as $ruleKey) {
|
||
|
foreach ( array( 'styles', 'scripts' ) as $assetType ) {
|
||
|
if ( isset( $dataSettingsFrontEnd[$ruleKey][ $assetType ] ) && ! empty( $dataSettingsFrontEnd[$ruleKey][$assetType] ) ) {
|
||
|
// Go through the values, depending on how the array is structured
|
||
|
// handle_unload_regex, handle_load_regex
|
||
|
if (in_array($ruleKey, array('handle_unload_regex', 'handle_load_regex'))) {
|
||
|
foreach ( $dataSettingsFrontEnd[ $ruleKey ][ $assetType ] as $assetHandle => $assetValues ) {
|
||
|
if ( strpos( $assetHandle, 'wpacu_hardcoded_' ) !== false ) {
|
||
|
if ($ruleKey === 'handle_unload_regex') {
|
||
|
$enableValue = isset( $assetValues['enable'] ) ? $assetValues['enable'] : '';
|
||
|
$regExValue = isset( $assetValues['value'] ) ? $assetValues['value'] : '';
|
||
|
$currentHardcodedAssetRules .= '<input type="hidden" name="wpacu_handle_unload_regex[' . $assetType . '][' . $assetHandle . '][enable]" value="' . $enableValue . '" />';
|
||
|
$currentHardcodedAssetRules .= '<input type="hidden" name="wpacu_handle_unload_regex[' . $assetType . '][' . $assetHandle . '][value]" value="' . esc_attr( $regExValue ) . '" />';
|
||
|
} elseif ($ruleKey === 'handle_load_regex') {
|
||
|
$enableValue = isset( $assetValues['enable'] ) ? $assetValues['enable'] : '';
|
||
|
$regExValue = isset( $assetValues['value'] ) ? $assetValues['value'] : '';
|
||
|
$currentHardcodedAssetRules .= '<input type="hidden" name="wpacu_handle_load_regex[' . $assetType . '][' . $assetHandle . '][enable]" value="' . $enableValue . '" />';
|
||
|
$currentHardcodedAssetRules .= '<input type="hidden" name="wpacu_handle_load_regex[' . $assetType . '][' . $assetHandle . '][value]" value="' . esc_attr( $regExValue ) . '" />';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// current unloaded on a page level, load_exceptions, handle_load_logged_in
|
||
|
foreach ( $dataSettingsFrontEnd[ $ruleKey ][ $assetType ] as $assetHandle ) {
|
||
|
if ( strpos( $assetHandle, 'wpacu_hardcoded_' ) !== false ) {
|
||
|
if ( $ruleKey === 'current_unloaded_page_level' ) {
|
||
|
$currentHardcodedAssetRules .= '<input type="hidden" name="wpassetcleanup[' . $assetType . '][]" value="' . $assetHandle . '" />';
|
||
|
} elseif ( $ruleKey === 'load_exceptions' ) {
|
||
|
$currentHardcodedAssetRules .= '<input type="hidden" name="wpacu_styles_load_it[]" value="' . $assetHandle . '" />';
|
||
|
} elseif ($ruleKey === 'handle_load_logged_in') {
|
||
|
$currentHardcodedAssetRules .= '<input type="hidden" name="wpacu_load_it_logged_in['.$assetType.']['.$assetHandle.']" value="1" />';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return '<div class="wpacu-assets-collapsible-wrap wpacu-wrap-area wpacu-hardcoded" id="wpacu-assets-collapsible-wrap-hardcoded-list" data-wpacu-settings-frontend="'.esc_attr($dataWpacuSettingsFrontend).'">
|
||
|
<a class="wpacu-assets-collapsible wpacu-assets-collapsible-active" href="#" style="padding: 15px 15px 15px 44px;"><span class="dashicons dashicons-code-standards"></span> Hardcoded (non-enqueued) Styles & Scripts</a>
|
||
|
<div class="wpacu-assets-collapsible-content" style="max-height: inherit;">
|
||
|
<div style="padding: 20px 0; margin: 0;"><img src="'.esc_url(admin_url('images/spinner.gif')).'" align="top" width="20" height="20" alt="" /> The list of hardcoded assets is fetched... Please wait...</div>
|
||
|
'.wp_kses($currentHardcodedAssetRules, array('input' => array('type' => array(), 'name' => array(), 'value' => array()))).'
|
||
|
</div>
|
||
|
</div>';
|
||
|
}
|
||
|
}
|