2197 lines
70 KiB
PHP
2197 lines
70 KiB
PHP
<?php
|
||
namespace WpAssetCleanUp\OptimiseAssets;
|
||
|
||
use WpAssetCleanUp\CleanUp;
|
||
use WpAssetCleanUp\FileSystem;
|
||
use WpAssetCleanUp\HardcodedAssets;
|
||
use WpAssetCleanUp\Main;
|
||
use WpAssetCleanUp\Menu;
|
||
use WpAssetCleanUp\Misc;
|
||
use WpAssetCleanUp\ObjectCache;
|
||
use WpAssetCleanUp\Plugin;
|
||
use WpAssetCleanUp\Preloads;
|
||
use WpAssetCleanUp\Settings;
|
||
use WpAssetCleanUp\Tools;
|
||
|
||
/**
|
||
* Class OptimizeCommon
|
||
* @package WpAssetCleanUp
|
||
*/
|
||
class OptimizeCommon
|
||
{
|
||
/**
|
||
* @var string
|
||
*/
|
||
public static $relPathPluginCacheDirDefault = '/cache/asset-cleanup/'; // keep forward slash at the end
|
||
|
||
/**
|
||
* @var string
|
||
*/
|
||
public static $optimizedSingleFilesDir = 'item';
|
||
|
||
/**
|
||
* @var float|int
|
||
*/
|
||
public static $cachedAssetFileExpiresIn = 86400; // 1 day in seconds
|
||
|
||
/**
|
||
* @var array
|
||
*/
|
||
public static $wellKnownExternalHosts = array(
|
||
'googleapis.com',
|
||
'bootstrapcdn.com',
|
||
'cloudflare.com',
|
||
'jsdelivr.net'
|
||
);
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function init()
|
||
{
|
||
add_action('switch_theme', array($this, 'clearCache' ));
|
||
add_action('after_switch_theme', array($this, 'clearCache' ));
|
||
|
||
// Is WP Rocket's page cache cleared? Clear Asset CleanUp's CSS cache files too
|
||
if ( isset($_GET['action']) && $_GET['action'] === 'purge_cache' ) {
|
||
// Leave its default parameters, no redirect needed
|
||
add_action('init', static function() {
|
||
OptimizeCommon::clearCache();
|
||
}, PHP_INT_MAX);
|
||
}
|
||
|
||
add_action('admin_post_assetcleanup_clear_assets_cache', static function() {
|
||
set_transient('wpacu_clear_assets_cache_via_link', true);
|
||
self::clearCache(true);
|
||
});
|
||
|
||
// When a post is moved to the trash / deleted
|
||
// clear its cache as its useless and there's no point in having extra files/directories in the caching directory
|
||
add_action('wp_trash_post', array($this, 'clearJsonStorageForPost')); // $postId is passed as a parameter
|
||
add_action('delete_post', array($this, 'clearJsonStorageForPost')); // $postId is passed as a parameter
|
||
|
||
// When a post is edited are within the Dashboard
|
||
add_action('admin_init', static function() {
|
||
if (($postId = Misc::getVar('get', 'post')) && Misc::getVar('get', 'action') === 'edit') {
|
||
self::clearJsonStorageForPost($postId, true);
|
||
}
|
||
});
|
||
|
||
// Keep used resources to the minimum and trigger any clearing of the page's CSS/JS caching
|
||
// for the admin while he has the right privileges and a single post page is visited
|
||
add_action('wp', static function() {
|
||
if (! is_admin() && Menu::userCanManageAssets() && is_singular()) {
|
||
global $post;
|
||
|
||
if (isset($post->ID) && $post->ID) {
|
||
self::clearJsonStorageForPost($post->ID, true);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Autoptimize Compatibility: Make sure Asset CleanUp's changes are applied
|
||
add_filter('autoptimize_filter_html_before_minify', static function($htmlSource) {
|
||
return self::alterHtmlSource($htmlSource, true);
|
||
});
|
||
|
||
if (Misc::isPluginActive('cache-enabler/cache-enabler.php')) {
|
||
if (defined('CE_VERSION') && version_compare(CE_VERSION, '1.6.0', '<')) {
|
||
// Cache Enabler: BEFORE 1.6.0 (1.5.5 and below)
|
||
// Make sure HTML changes are applied to cached pages from "Cache Enabler" plugin
|
||
add_filter( 'cache_enabler_before_store', static function( $htmlSource ) {
|
||
return self::alterHtmlSource( $htmlSource ); // deprecated, include it in case other users have an older version of "Cache Enabler"
|
||
}, 1, 1 );
|
||
} else {
|
||
// Cache Enabler: 1.6.0+
|
||
global $cache_enabler_constants;
|
||
|
||
if (isset($cache_enabler_constants['CACHE_ENABLER_VERSION']) && version_compare($cache_enabler_constants['CACHE_ENABLER_VERSION'], '1.6.0', '>=')) {
|
||
add_filter( 'cache_enabler_page_contents_before_store', static function( $htmlSource ) {
|
||
return self::alterHtmlSource( $htmlSource );
|
||
}, 1, 1 );
|
||
}
|
||
}
|
||
}
|
||
|
||
// In case HTML Minify is enabled in W3 Total Cache, make sure any settings (e.g. JS combine) in Asset CleanUp will be applied
|
||
add_filter('w3tc_minify_before', static function ($htmlSource) {
|
||
return self::alterHtmlSource($htmlSource);
|
||
}, 1, 1);
|
||
|
||
// LiteSpeed Cache (partial compatibility)
|
||
add_filter('litespeed_optm_html_head', static function($htmlHead) {
|
||
if (! Main::instance()->preventAssetsSettings()) {
|
||
$htmlHead = OptimizeCss::ignoreDependencyRuleAndKeepChildrenLoaded($htmlHead);
|
||
$htmlHead = OptimizeJs::ignoreDependencyRuleAndKeepChildrenLoaded($htmlHead);
|
||
}
|
||
return $htmlHead;
|
||
});
|
||
add_filter('litespeed_optm_html_foot', static function($htmlFoot) {
|
||
if (! Main::instance()->preventAssetsSettings()) {
|
||
$htmlFoot = OptimizeCss::ignoreDependencyRuleAndKeepChildrenLoaded($htmlFoot);
|
||
$htmlFoot = OptimizeJs::ignoreDependencyRuleAndKeepChildrenLoaded($htmlFoot);
|
||
}
|
||
return $htmlFoot;
|
||
});
|
||
|
||
// Make sure HTML changes, especially rules such as the ones from "Ignore dependency rules and keep 'children' loaded"
|
||
// are applied to cached pages from "WP Rocket" plugin
|
||
if (Misc::isPluginActive('wp-rocket/wp-rocket.php')) {
|
||
add_filter('rocket_buffer', static function($htmlSource) {
|
||
return self::alterHtmlSource($htmlSource, true);
|
||
});
|
||
}
|
||
|
||
// "Hide My WP Ghost – Security Plugin" - Make sure the alter the HTML (some files might be cached) before the security plugin proceeds with the alteration of the paths
|
||
// This way, "Hide My WP Ghost – Security Plugin" would process the paths to the CSS/JS files that are already cached from /wp-content/cache/
|
||
if ( ( ! (defined('DOING_AJAX') && DOING_AJAX) ) // not when /wp-admin/admin-ajax.php is called
|
||
&& class_exists('\HMWP_Classes_ObjController')
|
||
&& method_exists('\HMWP_Models_Rewrite', 'find_replace')
|
||
&& apply_filters('hmwp_process_buffer', true) // only when processing the buffer is turned ON
|
||
) {
|
||
add_filter('hmwp_process_buffer', '__return_false');
|
||
add_filter('wpacu_print_info_comments_in_cached_assets', '__return_false'); // hide comments revealing the paths to serve the purpose of "Hide My WP Ghost – Security Plugin"
|
||
|
||
add_filter('wpacu_html_source_after_optimization', function($htmlSource) {
|
||
return \HMWP_Classes_ObjController::getClass('HMWP_Models_Rewrite')->find_replace($htmlSource);
|
||
});
|
||
}
|
||
|
||
add_action('wp_loaded', array($this, 'maybeAlterHtmlSource'), 1);
|
||
|
||
HardcodedAssets::init();
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function maybeAlterHtmlSource()
|
||
{
|
||
if (is_admin()) {
|
||
// Don't apply any changes if not in the front-end view (e.g. Dashboard view)
|
||
return;
|
||
}
|
||
|
||
if (is_feed()) {
|
||
// The plugin should be inactive for feed URLs
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* CASE 1: The admin is logged-in and manages the assets in the front-end view
|
||
* */
|
||
if (HardcodedAssets::useBufferingForEditFrontEndView()) {
|
||
// Alter the HTML via "shutdown" action hook to catch hardcoded CSS/JS that is added via output buffering such as the ones in "Smart Slider 3"
|
||
// via HardcodedAssets.php
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* CASE (most common): The admin is logged-in, but "Manage in the front-end" is deactivated OR the visitor is just a guest
|
||
* */
|
||
ob_start(static function($htmlSource) {
|
||
// Do not do any optimization if "Test Mode" is Enabled
|
||
if (! Menu::userCanManageAssets() && Main::instance()->settings['test_mode']) {
|
||
return $htmlSource;
|
||
}
|
||
|
||
return self::alterHtmlSource($htmlSource);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param $htmlSource
|
||
* @param $triggerOnlyOnce bool
|
||
*
|
||
* @return mixed|string|string[]|void|null
|
||
*/
|
||
public static function alterHtmlSource($htmlSource, $triggerOnlyOnce = false)
|
||
{
|
||
// e.g. if it was called from "autoptimize_filter_html_before_minify", then there's no point in triggering it again from a different hook
|
||
if (defined('WPACU_ALTER_HTML_SOURCE_DONE')) {
|
||
return $htmlSource;
|
||
}
|
||
|
||
if ($triggerOnlyOnce && ! defined('WPACU_ALTER_HTML_SOURCE_DONE')) {
|
||
define('WPACU_ALTER_HTML_SOURCE_DONE', 1);
|
||
}
|
||
|
||
if (is_feed()) {
|
||
// The plugin should not do any alterations for the feed content
|
||
return $htmlSource;
|
||
}
|
||
|
||
// Dashboard View
|
||
// Return the HTML as it is without performing any optimisations to save resources
|
||
// Since the page has to be as clean as possible when fetching the assets
|
||
if (Main::instance()->isGetAssetsCall) {
|
||
return $htmlSource;
|
||
}
|
||
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source' ); /* [/wpacu_timing] */
|
||
|
||
// Front-end View
|
||
// The printing of the hardcoded assets is made via "wpacu_final_frontend_output" filter hook
|
||
// located within "shutdown" action hook only if the user is logged-in and has the right permissions
|
||
|
||
// This is useful to avoid changing the DOM via wp_loaded action hook
|
||
// In order to check how fast the page loads without the DOM changes (for debugging purposes)
|
||
$wpacuNoHtmlChanges = isset($_REQUEST['wpacu_no_html_changes']) || ( defined('WPACU_NO_HTML_CHANGES') && WPACU_NO_HTML_CHANGES );
|
||
|
||
// Not a normal WordPress page load
|
||
// e.g. it could be JS content loaded dynamically such as /?wpml-app=ate-widget
|
||
if ( ! (did_action('wp_head') && did_action('wp_footer')) && Plugin::preventAnyFrontendOptimization('', $htmlSource) ) {
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source', 'end' ); /* [/wpacu_timing] */
|
||
return $htmlSource;
|
||
}
|
||
|
||
if ( $wpacuNoHtmlChanges || Plugin::preventAnyFrontendOptimization() ) {
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source', 'end' ); /* [/wpacu_timing] */
|
||
return $htmlSource;
|
||
}
|
||
|
||
$htmlSource = apply_filters( 'wpacu_html_source_before_optimization', $htmlSource );
|
||
|
||
// For the admin
|
||
$anyHardCodedAssetsList = HardcodedAssets::getAll( $htmlSource, false );
|
||
|
||
// The admin is editing the CSS/JS list within the front-end view
|
||
if (HardcodedAssets::useBufferingForEditFrontEndView()) {
|
||
ObjectCache::wpacu_cache_set('wpacu_hardcoded_assets_encoded', base64_encode( wp_json_encode($anyHardCodedAssetsList) ));
|
||
}
|
||
|
||
$htmlSource = OptimizeCss::alterHtmlSource( $htmlSource );
|
||
$htmlSource = OptimizeJs::alterHtmlSource( $htmlSource );
|
||
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source_cleanup' ); /* [/wpacu_timing] */
|
||
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_remove_html_comments'); /* [/wpacu_timing] */
|
||
$htmlSource = Main::instance()->settings['remove_html_comments'] ? CleanUp::removeHtmlComments( $htmlSource, false ) : $htmlSource;
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_remove_html_comments', 'end'); /* [/wpacu_timing] */
|
||
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_remove_meta_generators'); /* [/wpacu_timing] */
|
||
$htmlSource = Main::instance()->settings['remove_generator_tag'] ? CleanUp::removeMetaGenerators( $htmlSource ) : $htmlSource;
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer('alter_html_source_for_remove_meta_generators', 'end'); /* [/wpacu_timing] */
|
||
|
||
$htmlSource = preg_replace('#<link(.*)data-wpacu-style-handle=\'(.*)\'#Umi', '<link \\1', $htmlSource);
|
||
$htmlSource = preg_replace('#<link(\s+)rel=\'stylesheet\' id=\'#Umi', '<link rel=\'stylesheet\' id=\'', $htmlSource);
|
||
|
||
$htmlSource = str_replace(Preloads::DEL_STYLES_PRELOADS, '', $htmlSource);
|
||
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source_cleanup', 'end' ); /* [/wpacu_timing] */
|
||
|
||
if ( in_array( Main::instance()->settings['disable_xmlrpc'], array( 'disable_all', 'disable_pingback' ) ) ) {
|
||
// Also clean it up from the <head> in case it's hardcoded
|
||
$htmlSource = CleanUp::cleanPingbackLinkRel( $htmlSource );
|
||
}
|
||
|
||
// A script like this one shouldn't be in an AMP page
|
||
if (defined('WPACU_DO_EXTRA_CHECKS_FOR_AMP') && strpos($htmlSource, '<style amp-boilerplate>') !== false && (strpos($htmlSource, '<style amp-custom>') !== false || strpos($htmlSource, '<html amp ') !== false)) {
|
||
$htmlSource = str_replace(Misc::preloadAsyncCssFallbackOutput(true), '', $htmlSource);
|
||
}
|
||
|
||
$htmlSource = apply_filters( 'wpacu_html_source', $htmlSource ); // legacy
|
||
|
||
/* [wpacu_timing] */ Misc::scriptExecTimer( 'alter_html_source', 'end' ); /* [/wpacu_timing] */
|
||
|
||
// [wpacu_debug]
|
||
if (isset($_GET['wpacu_debug'])) {
|
||
$htmlSource = self::applyDebugTiming($htmlSource);
|
||
}
|
||
// [wpacu_debug]
|
||
|
||
return apply_filters( 'wpacu_html_source_after_optimization', $htmlSource );
|
||
}
|
||
|
||
/**
|
||
* @param $htmlSource
|
||
*
|
||
* @return string|string[]
|
||
*/
|
||
public static function applyDebugTiming($htmlSource)
|
||
{
|
||
$timingKeys = array(
|
||
'prepare_optimize_files_css',
|
||
'prepare_optimize_files_js',
|
||
|
||
// All HTML alteration via "wp_loaded" action hook
|
||
'alter_html_source',
|
||
|
||
// HTML CleanUp
|
||
'alter_html_source_cleanup',
|
||
'alter_html_source_for_remove_html_comments',
|
||
'alter_html_source_for_remove_meta_generators',
|
||
|
||
// CSS
|
||
'alter_html_source_for_optimize_css',
|
||
'alter_html_source_unload_ignore_deps_css',
|
||
'alter_html_source_for_google_fonts_optimization_removal',
|
||
'alter_html_source_for_inline_css',
|
||
'alter_html_source_original_to_optimized_css',
|
||
'alter_html_source_for_preload_css',
|
||
|
||
'alter_html_source_for_combine_css',
|
||
'alter_html_source_for_minify_inline_style_tags',
|
||
|
||
// JS
|
||
'alter_html_source_for_optimize_js',
|
||
'alter_html_source_unload_ignore_deps_js',
|
||
|
||
'alter_html_source_original_to_optimized_js',
|
||
'alter_html_source_for_preload_js',
|
||
'alter_html_source_for_combine_js',
|
||
|
||
'fetch_strip_hardcoded_assets',
|
||
'fetch_all_hardcoded_assets',
|
||
|
||
'output_css_js_manager',
|
||
|
||
'style_loader_tag',
|
||
'script_loader_tag'
|
||
);
|
||
|
||
foreach ( $timingKeys as $timingKey ) {
|
||
$htmlSource = Misc::printTimingFor($timingKey, $htmlSource);
|
||
}
|
||
|
||
return $htmlSource;
|
||
}
|
||
|
||
/**
|
||
* @param $htmlSource
|
||
* @param string $for
|
||
*
|
||
* @return \DOMDocument
|
||
*/
|
||
public static function getDomLoadedTag($htmlSource, $for = '')
|
||
{
|
||
$htmlSourceBefore = $htmlSource;
|
||
|
||
$domTag = Misc::initDOMDocument();
|
||
|
||
$cleanerDomRegEx = '';
|
||
|
||
// [HTML CleanUp]
|
||
if ($for === 'removeHtmlComments') {
|
||
// They could contain anything
|
||
$cleanerDomRegEx = '';
|
||
}
|
||
|
||
if ($for === 'removeMetaGenerators') {
|
||
$cleanerDomRegEx = array('@<(noscript|style|script)[^>]*?>.*?</\\1>@si', '#<(link|img)([^<>]+)/?>#iU');
|
||
}
|
||
// [/HTML CleanUp]
|
||
|
||
// [CSS Optimisation]
|
||
if ($for === 'combineCss') {
|
||
$cleanerDomRegEx = array('@<(noscript|style|script)[^>]*?>.*?</\\1>@si', '#<(meta|img)([^<>]+)/?>#iU');
|
||
}
|
||
|
||
if ($for === 'minifyInlineStyleTags') {
|
||
$cleanerDomRegEx = array('@<(noscript|script)[^>]*?>.*?</\\1>@si', '#<(meta|link|img)([^<>]+)/?>#iU');
|
||
}
|
||
// [/CSS Optimisation]
|
||
|
||
// [JS Optimisation]
|
||
if ($for === 'moveInlinejQueryAfterjQuerySrc') {
|
||
$cleanerDomRegEx = '@<(noscript|style)[^>]*?>.*?</\\1>@si';
|
||
}
|
||
|
||
if ($for === 'minifyInlineScriptTags') {
|
||
$cleanerDomRegEx = array('@<(noscript|style)[^>]*?>.*?</\\1>@si', '#<(meta|link|img)([^<>]+)/?>#iU');
|
||
}
|
||
|
||
if ($for === 'combineJs') {
|
||
$cleanerDomRegEx = '@<(noscript|style)[^>]*?>.*?</\\1>@si';
|
||
}
|
||
// [/JS Optimisation]
|
||
|
||
// Default: Strip just the NOSCRIPT tags
|
||
if ($cleanerDomRegEx !== '') {
|
||
$htmlSource = preg_replace( $cleanerDomRegEx, '', $htmlSource );
|
||
}
|
||
|
||
if (Main::instance()->isFrontendEditView) {
|
||
$htmlSource = preg_replace( '@<form action="#wpacu_wrap_assets" method="post">.*?</form>@si', '', $htmlSource );
|
||
}
|
||
|
||
// Avoid "Warning: DOMDocument::loadHTML(): Empty string supplied as input"
|
||
// Just in case $htmlSource has been altered incorrectly for any reason, fallback to the original $htmlSource value ($htmlSourceBefore)
|
||
if ( ! $htmlSource ) {
|
||
$domTag->loadHTML($htmlSourceBefore);
|
||
return $domTag;
|
||
}
|
||
|
||
$domTag->loadHTML($htmlSource);
|
||
return $domTag;
|
||
}
|
||
|
||
/**
|
||
* @param $htmlSource
|
||
* @param $params
|
||
*
|
||
* @return array|mixed|string|string[]
|
||
*/
|
||
public static function matchAndReplaceLinkTags($htmlSource, $params = array())
|
||
{
|
||
if (isset($params['as']) && $params['as']) {
|
||
$fallbackToRegex = false;
|
||
|
||
/*
|
||
* Option 1: DOM + Regular Expression (Best)
|
||
*/
|
||
if ( Misc::isDOMDocumentOn() ) {
|
||
$dom = Misc::initDOMDocument();
|
||
|
||
$dom->loadHTML($htmlSource);
|
||
|
||
$selector = new \DOMXPath($dom);
|
||
|
||
$domTagQuery = $selector->query('//link[@as="'.$params['as'].'"]');
|
||
|
||
if (count($domTagQuery) < 1) {
|
||
// No LINK tags found with the specified "as" attribute? Stop here!
|
||
return $htmlSource;
|
||
}
|
||
|
||
foreach($domTagQuery as $link) {
|
||
if ( ! $link->hasAttributes() ) {
|
||
continue;
|
||
}
|
||
|
||
$linkTagParts = array();
|
||
$linkTagParts[] = '<link ';
|
||
|
||
foreach ($link->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]) && ! empty($matchSourceFromTag[0])) ) {
|
||
$fallbackToRegex = true;
|
||
break;
|
||
}
|
||
|
||
$matchesSourcesFromTags[] = $matchSourceFromTag[0];
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Option 2: Regular Expression (Fallback)
|
||
*/
|
||
if ($fallbackToRegex || ! Misc::isDOMDocumentOn()) {
|
||
preg_match_all( '#<link[^>]*(as(\s+|)=(\s+|)(|"|\')'.$params['as'].'(|"|\'))[^>]*>#Umi', $htmlSource, $matchesSourcesFromTags, PREG_SET_ORDER );
|
||
}
|
||
|
||
// Are there any preloaded / prefetched scripts that are inside the unloaded list?
|
||
// Strip the preloading tag as it's not relevant, since the script was unloaded
|
||
// These can be generated via plugins such as "Pre* Party Resource Hints" where users can manually insert scripts to preload
|
||
if ( ! empty( $matchesSourcesFromTags ) ) {
|
||
foreach ( $matchesSourcesFromTags as $matchedLink ) {
|
||
$matchedLinkTag = isset( $matchedLink[0] ) ? $matchedLink[0] : '';
|
||
|
||
if ( ! ( $matchedLinkTag && strpos( $matchedLinkTag, ' href' ) !== false ) ) {
|
||
continue;
|
||
}
|
||
|
||
foreach ( $params['unloaded_assets_rel_sources'] as $unloadedAssetRelSource ) {
|
||
if ( strpos( $matchedLinkTag, $unloadedAssetRelSource ) !== false ) {
|
||
$htmlSource = str_replace( $matchedLinkTag, '', $htmlSource );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $htmlSource;
|
||
}
|
||
|
||
/**
|
||
* @return string
|
||
*/
|
||
public static function getRelPathPluginCacheDir()
|
||
{
|
||
// In some cases, hosting companies put restriction for writable folders
|
||
// Pantheon, for instance, allows only /wp-content/uploads/ to be writable
|
||
// For security reasons, do not allow ../
|
||
return ((defined('WPACU_CACHE_DIR') && strpos(WPACU_CACHE_DIR, '../') === false)
|
||
? WPACU_CACHE_DIR
|
||
: self::$relPathPluginCacheDirDefault);
|
||
}
|
||
|
||
/**
|
||
* The following output is ONLY used for fetching purposes
|
||
* It will not be part of the final output
|
||
*
|
||
* @param $htmlSourceToFetchFrom
|
||
* @param $params
|
||
*
|
||
* @return string|string[]|null
|
||
*/
|
||
public static function cleanerHtmlSource($htmlSourceToFetchFrom, $params = array('strip_content_between_conditional_comments'))
|
||
{
|
||
if (in_array('for_fetching_link_tags', $params)) {
|
||
$htmlSourceToFetchFrom = preg_replace( array('@<(style|script|noscript)[^>]*?>.*?</\\1>@si', '#<(meta|img)([^<>]+)/?>#iU'), '', $htmlSourceToFetchFrom );
|
||
} else {
|
||
// Strip NOSCRIPT tags
|
||
$htmlSourceToFetchFrom = preg_replace( '@<(noscript)[^>]*?>.*?</\\1>@si', '', $htmlSourceToFetchFrom );
|
||
}
|
||
|
||
// Case: Return the HTML source without any conditional comments and the content within them
|
||
if (in_array('strip_content_between_conditional_comments', $params)) {
|
||
preg_match_all('#<!--\[if(.*?)]>(<!-->|-->|\s|)(.*?)(<!--<!|<!)\[endif]-->#si', $htmlSourceToFetchFrom, $matchedContent);
|
||
|
||
if (isset($matchedContent[0]) && ! empty($matchedContent[0])) {
|
||
foreach ($matchedContent[0] as $conditionalHtmlContent) {
|
||
$htmlSourceToFetchFrom = str_replace($conditionalHtmlContent, '', $htmlSourceToFetchFrom);
|
||
}
|
||
|
||
return $htmlSourceToFetchFrom;
|
||
}
|
||
}
|
||
|
||
return $htmlSourceToFetchFrom;
|
||
}
|
||
|
||
/**
|
||
* Is this a regular WordPress page (not feed, REST API etc.)?
|
||
* If not, do not proceed with any CSS/JS combine
|
||
*
|
||
* @return bool
|
||
*/
|
||
public static function doCombineIsRegularPage()
|
||
{
|
||
// In particular situations, do not process this
|
||
if (strpos($_SERVER['REQUEST_URI'], '/'.Misc::getPluginsDir().'/') !== false
|
||
&& strpos($_SERVER['REQUEST_URI'], '/wp-content/themes/') !== false) {
|
||
return false;
|
||
}
|
||
|
||
if (Misc::endsWith($_SERVER['REQUEST_URI'], '/comments/feed/')) {
|
||
return false;
|
||
}
|
||
|
||
if (str_replace('//', '/', site_url() . '/feed/') === $_SERVER['REQUEST_URI']) {
|
||
return false;
|
||
}
|
||
|
||
if (is_feed()) { // any kind of feed page
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @param $isFile
|
||
* @param $localAssetPath
|
||
* @param $assetHandle
|
||
* @param $fileVer
|
||
*
|
||
* @return string
|
||
*/
|
||
public static function generateUniqueNameForCachedAsset($isFile, $localAssetPath, $assetHandle, $fileVer)
|
||
{
|
||
if ($isFile) {
|
||
$relPathToFileFiltered = str_replace(Misc::getWpRootDirPath(), '', $localAssetPath);
|
||
|
||
// Some people might use plugins to hide the fact that they are using WordPress
|
||
// Strip such information from the cached asset names as it's irrelevant for the visitor anyway
|
||
// if the cached file name loaded in the browser contain references to WordPress
|
||
foreach (array('wp-content/plugins', 'wp-content/themes', 'wp-', 'wordpress') as $toStripFromFileName) {
|
||
$relPathToFileFiltered = str_replace( $toStripFromFileName, '', $relPathToFileFiltered );
|
||
}
|
||
|
||
$relPathToFileFiltered = ltrim($relPathToFileFiltered, '/');
|
||
|
||
$sanitizedRelPathToFileFiltered = str_replace('/', '__', $relPathToFileFiltered);
|
||
$sanitizedRelPathToFileFiltered = sanitize_title($sanitizedRelPathToFileFiltered);
|
||
$uniqueOptimizedAssetName = $sanitizedRelPathToFileFiltered;
|
||
} else {
|
||
$uniqueOptimizedAssetName = sanitize_title( $assetHandle );
|
||
}
|
||
|
||
$uniqueOptimizedAssetName .= '-v' . $fileVer;
|
||
|
||
return $uniqueOptimizedAssetName;
|
||
}
|
||
|
||
/**
|
||
* @param $href
|
||
* @param $assetType
|
||
*
|
||
* @return bool|string
|
||
*/
|
||
public static function getLocalAssetPath($href, $assetType)
|
||
{
|
||
// Check if it starts without "/" or a protocol; e.g. "wp-content/theme/style.css", "wp-content/theme/script.js"
|
||
if (strpos($href, '/') !== 0 &&
|
||
strpos($href, '//') !== 0 &&
|
||
stripos($href, 'http://') !== 0 &&
|
||
stripos($href, 'https://') !== 0
|
||
) {
|
||
$href = '/'.$href; // append the forward slash to be processed as relative later on
|
||
}
|
||
|
||
// starting with "/", but not with "//"
|
||
$isRelHref = (strpos($href, '/') === 0 && strpos($href, '//') !== 0);
|
||
|
||
if (! $isRelHref) {
|
||
$href = self::isSourceFromSameHost($href);
|
||
|
||
if (! $href) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
$hrefRelPath = self::getSourceRelPath($href);
|
||
|
||
if (strpos($hrefRelPath, '/') === 0) {
|
||
$hrefRelPath = substr($hrefRelPath, 1);
|
||
}
|
||
|
||
$localAssetPossiblePaths = array(Misc::getWpRootDirPath() . $hrefRelPath);
|
||
|
||
// Perhaps the URL starts with / (not //) and site_url() was not used
|
||
$parseSiteUrlPath = (string)parse_url(site_url(), PHP_URL_PATH);
|
||
|
||
// This is in case we have something like this in the source (hardcoded or generated through a plugin)
|
||
// /blog/wp-content/plugins/custom-plugin-slug/script.js
|
||
// and the site_url() is equal with https://www.mysite.com/blog
|
||
if ($parseSiteUrlPath !== '/' && strlen($parseSiteUrlPath) > 1 && strpos($href, $parseSiteUrlPath) === 0) {
|
||
$relPathFromWpRootDir = str_replace($parseSiteUrlPath, '', $href);
|
||
$altHrefRelPath = str_replace('//', '/', Misc::getWpRootDirPath() . $relPathFromWpRootDir);
|
||
$localAssetPossiblePaths[] = $altHrefRelPath;
|
||
}
|
||
|
||
foreach ($localAssetPossiblePaths as $localAssetPath) {
|
||
if ( strpos( $localAssetPath, '?ver' ) !== false ) {
|
||
list( $localAssetPathAlt, ) = explode( '?ver', $localAssetPath );
|
||
$localAssetPath = $localAssetPathAlt;
|
||
}
|
||
|
||
// Not using "?ver="
|
||
if ( strpos( $localAssetPath, '.' . $assetType . '?' ) !== false ) {
|
||
list( $localAssetPathAlt, ) = explode( '.' . $assetType . '?', $localAssetPath );
|
||
$localAssetPath = $localAssetPathAlt . '.' . $assetType;
|
||
}
|
||
|
||
if ( strrchr( $localAssetPath, '.' ) === '.' . $assetType && is_file( $localAssetPath ) ) {
|
||
return $localAssetPath;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @param $assetHref
|
||
*
|
||
* @return array|false|string|string[]
|
||
*/
|
||
public static function getPathToAssetDir($assetHref)
|
||
{
|
||
$posLastSlash = strrpos($assetHref, '/');
|
||
$pathToAssetDir = substr($assetHref, 0, $posLastSlash);
|
||
|
||
$parseUrl = parse_url($pathToAssetDir);
|
||
|
||
if (isset($parseUrl['scheme']) && $parseUrl['scheme'] !== '') {
|
||
$pathToAssetDir = str_replace(
|
||
array('http://'.$parseUrl['host'], 'https://'.$parseUrl['host']),
|
||
'',
|
||
$pathToAssetDir
|
||
);
|
||
} elseif (strpos($pathToAssetDir, '//') === 0) {
|
||
$pathToAssetDir = str_replace(
|
||
array('//'.$parseUrl['host'], '//'.$parseUrl['host']),
|
||
'',
|
||
$pathToAssetDir
|
||
);
|
||
}
|
||
|
||
return $pathToAssetDir;
|
||
}
|
||
|
||
/**
|
||
* @param $sourceTag
|
||
*
|
||
* @return array|bool
|
||
*/
|
||
public static function getLocalCleanSourceFromTag($sourceTag)
|
||
{
|
||
$sourceFromTag = Misc::getValueFromTag($sourceTag);
|
||
|
||
if (! $sourceFromTag) {
|
||
return false;
|
||
}
|
||
|
||
// Check if it starts without "/" or a protocol; e.g. "wp-content/theme/style.css", "wp-content/theme/script.js"
|
||
if (strpos($sourceFromTag, '/') !== 0 &&
|
||
strpos($sourceFromTag, '//') !== 0 &&
|
||
stripos($sourceFromTag, 'http://') !== 0 &&
|
||
stripos($sourceFromTag, 'https://') !== 0
|
||
) {
|
||
$sourceFromTag = '/'.$sourceFromTag; // append the forward slash to be processed as relative later on
|
||
}
|
||
|
||
// Perhaps the URL starts with / (not //) and site_url() was not used
|
||
$altFilePathForRelSource = $isRelPath = false;
|
||
$parseSiteUrlPath = (string)parse_url(site_url(), PHP_URL_PATH);
|
||
|
||
// This is in case we have something like this in the HTML source (hardcoded or generated through a plugin)
|
||
// <link href="/blog/wp-content/plugins/custom-plugin-slug/script.js" rel="preload" as="script" type="text/javascript">
|
||
// and the site_url() is equal with https://www.mysite.com/blog
|
||
if ($parseSiteUrlPath !== '/' && strlen($parseSiteUrlPath) > 1 && strpos($sourceFromTag, $parseSiteUrlPath) !== false) {
|
||
$relPathFromRootDir = str_replace($parseSiteUrlPath, '', $sourceFromTag);
|
||
$altFilePathForRelSource = str_replace('//', '/', Misc::getWpRootDirPath() . $relPathFromRootDir);
|
||
} elseif (strpos($sourceFromTag, '/') === 0 && strpos($sourceFromTag, '//') !== 0) {
|
||
$altFilePathForRelSource = str_replace('//', '/', Misc::getWpRootDirPath() . $sourceFromTag);
|
||
}
|
||
|
||
if ($altFilePathForRelSource && (strpos($altFilePathForRelSource, '.css?') !== false || strpos($altFilePathForRelSource, '.js?') !== false)) {
|
||
list($altFilePathForRelSource) = explode('?', $altFilePathForRelSource);
|
||
}
|
||
|
||
if ( $altFilePathForRelSource && (is_file(Misc::getWpRootDirPath() . $sourceFromTag) || is_file($altFilePathForRelSource)) ) {
|
||
$isRelPath = true;
|
||
}
|
||
|
||
// In case the match was something like "src='//mydomain.com/file.js'"
|
||
// Leave nothing to chance as often the prefix is stripped
|
||
$cleanSiteUrl = str_replace(array('http://', 'https://'), '//', site_url());
|
||
|
||
if ($isRelPath || (stripos($sourceFromTag, $cleanSiteUrl) !== false) || (stripos($sourceFromTag, site_url()) !== false)) {
|
||
$cleanSourceUrlFromTag = trim($sourceFromTag, '?&');
|
||
$afterQuestionMark = WPACU_PLUGIN_VERSION;
|
||
|
||
// Is it a dynamic URL? Keep the full path
|
||
if (strpos($cleanSourceUrlFromTag, '.php') !== false ||
|
||
strpos($cleanSourceUrlFromTag, '/?') !== false ||
|
||
strpos($cleanSourceUrlFromTag, rtrim(site_url(), '/').'?') !== false) {
|
||
list(,$afterQuestionMark) = explode('?', $sourceFromTag);
|
||
} elseif (strpos($sourceFromTag, '?') !== false) {
|
||
list($cleanSourceUrlFromTag, $afterQuestionMark) = explode('?', $sourceFromTag);
|
||
}
|
||
|
||
if (! $afterQuestionMark) {
|
||
return false;
|
||
}
|
||
|
||
return array('source' => $cleanSourceUrlFromTag, 'after_question_mark' => $afterQuestionMark);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @param $href
|
||
*
|
||
* @return bool
|
||
*/
|
||
public static function isSourceFromSameHost($href)
|
||
{
|
||
// Check the host name
|
||
$siteDbUrl = get_option('siteurl');
|
||
$siteUrlHost = strtolower(parse_url($siteDbUrl, PHP_URL_HOST));
|
||
|
||
$cdnUrls = self::getAnyCdnUrls();
|
||
|
||
// Are there any CDN urls set? Check them out
|
||
if (! empty($cdnUrls)) {
|
||
$hrefAlt = $href;
|
||
|
||
foreach ($cdnUrls as $cdnUrl) {
|
||
$hrefCleanedArray = self::getCleanHrefAfterCdnStrip(trim($cdnUrl), $hrefAlt);
|
||
$cdnNoPrefix = $hrefCleanedArray['cdn_no_prefix'];
|
||
$hrefAlt = $hrefCleanedArray['rel_href'];
|
||
|
||
if ($hrefAlt !== $href && stripos($href, '//'.$cdnNoPrefix) !== false) {
|
||
return $href;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (strpos($href, '//') === 0) {
|
||
list ($urlPrefix) = explode('//', $siteDbUrl);
|
||
$href = $urlPrefix . $href;
|
||
}
|
||
|
||
/*
|
||
* Validate it first
|
||
*/
|
||
$assetHost = strtolower(parse_url($href, PHP_URL_HOST));
|
||
|
||
if (preg_match('#'.$assetHost.'#si', implode('', self::$wellKnownExternalHosts))) {
|
||
return false;
|
||
}
|
||
|
||
// Different host name (most likely 3rd party one such as fonts.googleapis.com or an external CDN)
|
||
// Do not add it to the combine list
|
||
if ($assetHost !== $siteUrlHost) {
|
||
return false;
|
||
}
|
||
|
||
return $href;
|
||
}
|
||
|
||
/**
|
||
* @param $href
|
||
*
|
||
* @return mixed
|
||
*/
|
||
public static function getSourceRelPath($href)
|
||
{
|
||
// Already starts with / but not with //
|
||
// Path is relative, just return it
|
||
if (strpos($href, '/') === 0 && strpos($href, '//') !== 0) {
|
||
return $href;
|
||
}
|
||
|
||
// Starts with // (protocol is missing)
|
||
// Add a dummy one to validate the whole URL and get the host
|
||
if (strpos($href, '//') === 0) {
|
||
$href = (Misc::isHttpsSecure() ? 'https:' : 'http:') . $href;
|
||
}
|
||
|
||
$parseUrl = parse_url($href);
|
||
$hrefHost = isset($parseUrl['host']) ? $parseUrl['host'] : false;
|
||
|
||
if (! $hrefHost) {
|
||
return $href;
|
||
}
|
||
|
||
// Sometimes host is different on Staging websites such as the ones from Siteground
|
||
// e.g. staging1.domain.com and domain.com
|
||
// We need to make sure that the URI path is fetched correctly based on the host value from the $href
|
||
$siteDbUrl = get_option('siteurl');
|
||
$parseDbSiteUrl = parse_url($siteDbUrl);
|
||
|
||
$dbSiteUrlHost = $parseDbSiteUrl['host'];
|
||
|
||
$finalBaseUrl = str_replace($dbSiteUrlHost, $hrefHost, $siteDbUrl);
|
||
|
||
$hrefAlt = $finalRelPath = $href;
|
||
|
||
$cdnUrls = self::getAnyCdnUrls();
|
||
|
||
// Are there any CDN urls set? Filter them out in order to retrieve the relative path
|
||
if (! empty($cdnUrls)) {
|
||
foreach ($cdnUrls as $cdnUrl) {
|
||
$hrefCleanArray = self::getCleanHrefAfterCdnStrip(trim($cdnUrl), $hrefAlt);
|
||
$cdnNoPrefix = $hrefCleanArray['cdn_no_prefix'];
|
||
|
||
$finalRelPath = str_replace(
|
||
array('http://'.$cdnNoPrefix, 'https://'.$cdnNoPrefix, '//'.$cdnNoPrefix),
|
||
'',
|
||
$finalRelPath
|
||
);
|
||
}
|
||
}
|
||
|
||
if (strpos($finalRelPath, 'http') === 0) {
|
||
list(,$noProtocol) = explode('://', $finalBaseUrl);
|
||
$finalBaseUrls = array(
|
||
'http://'.$noProtocol,
|
||
'https://'.$noProtocol
|
||
);
|
||
} else {
|
||
$finalBaseUrls = array($finalBaseUrl);
|
||
}
|
||
|
||
$finalRelPath = str_replace($finalBaseUrls, '', $finalRelPath);
|
||
|
||
if (defined('WP_ROCKET_CACHE_BUSTING_URL') && function_exists('get_current_blog_id') && get_current_blog_id()) {
|
||
$finalRelPath = str_replace(
|
||
array(WP_ROCKET_CACHE_BUSTING_URL . get_current_blog_id(), WP_ROCKET_CACHE_BUSTING_URL),
|
||
'',
|
||
$finalRelPath
|
||
);
|
||
}
|
||
|
||
return $finalRelPath;
|
||
}
|
||
|
||
/**
|
||
* @param $cdnUrl
|
||
* @param $hrefAlt
|
||
*
|
||
* @return array
|
||
*/
|
||
public static function getCleanHrefAfterCdnStrip($cdnUrl, $hrefAlt)
|
||
{
|
||
if (strpos($cdnUrl, '//') !== false) {
|
||
$parseUrl = parse_url($cdnUrl);
|
||
$cdnNoPrefix = $parseUrl['host'];
|
||
|
||
if (isset($parseUrl['path']) && $parseUrl['path'] !== '') {
|
||
$cdnNoPrefix .= $parseUrl['path'];
|
||
}
|
||
} else {
|
||
$cdnNoPrefix = $cdnUrl; // CNAME
|
||
}
|
||
|
||
$hrefAlt = str_ireplace(array('http://' . $cdnNoPrefix, 'https://' . $cdnNoPrefix, '//'.$cdnNoPrefix), '', $hrefAlt);
|
||
|
||
return array('cdn_no_prefix' => $cdnNoPrefix, 'rel_href' => $hrefAlt);
|
||
}
|
||
|
||
/**
|
||
* @param $jsonStorageFile
|
||
* @param $relPathAssetCacheDir
|
||
* @param $assetType
|
||
* @param $forType
|
||
*
|
||
* @return array|mixed|object
|
||
*/
|
||
public static function getAssetCachedData($jsonStorageFile, $relPathAssetCacheDir, $assetType, $forType = 'combine')
|
||
{
|
||
if ($forType === 'combine') {
|
||
// Only clean request URIs allowed
|
||
if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
|
||
list($requestUri) = explode('?', $_SERVER['REQUEST_URI']);
|
||
} else {
|
||
$requestUri = $_SERVER['REQUEST_URI'];
|
||
}
|
||
|
||
$requestUriPart = $requestUri;
|
||
|
||
// Same results for Homepage (any pagination), 404 Not Found & Date archive pages
|
||
// The JSON files will get stored in the root directory of the targeted website
|
||
if ($requestUri === '/' || is_404() || is_date() || Misc::isHomePage()) {
|
||
$requestUriPart = '';
|
||
}
|
||
|
||
// Treat the pagination pages the same as the main page (same it's done for the unloading rules)
|
||
if (($currentPageNo = get_query_var('paged')) && (is_archive() || is_singular())) {
|
||
$paginationBase = isset($GLOBALS['wp_rewrite']->pagination_base) ? $GLOBALS['wp_rewrite']->pagination_base : 'page';
|
||
$requestUriPart = str_replace('/'.$paginationBase.'/'.$currentPageNo.'/', '', $requestUriPart);
|
||
}
|
||
|
||
$dirToFilename = WP_CONTENT_DIR . dirname($relPathAssetCacheDir) . '/_storage/'
|
||
. parse_url(site_url(), PHP_URL_HOST) .
|
||
$requestUriPart . '/';
|
||
|
||
$dirToFilename = str_replace('//', '/', $dirToFilename);
|
||
|
||
$assetsFile = $dirToFilename . self::filterStorageFileName($jsonStorageFile);
|
||
} elseif ($forType === 'item') {
|
||
$dirToFilename = WP_CONTENT_DIR . dirname($relPathAssetCacheDir) . '/_storage/'.self::$optimizedSingleFilesDir.'/';
|
||
$assetsFile = $dirToFilename . $jsonStorageFile;
|
||
}
|
||
|
||
if (! is_file($assetsFile)) {
|
||
return array();
|
||
}
|
||
|
||
if ($assetType === 'css' || $assetType === 'js') {
|
||
$cachedAssetsFileExpiresIn = self::$cachedAssetFileExpiresIn;
|
||
} else {
|
||
return array();
|
||
}
|
||
|
||
// Delete cached file after it expired as it will be regenerated
|
||
if (filemtime($assetsFile) < (time() - $cachedAssetsFileExpiresIn)) {
|
||
self::clearAssetCachedData($jsonStorageFile);
|
||
return array();
|
||
}
|
||
|
||
$optionValue = FileSystem::fileGetContents($assetsFile);
|
||
|
||
if ($optionValue) {
|
||
$optionValueArray = @json_decode($optionValue, ARRAY_A);
|
||
|
||
if ($forType === 'combine') {
|
||
if (! empty($optionValueArray)) {
|
||
foreach ($optionValueArray as $assetsValues) {
|
||
foreach ($assetsValues as $finalValues) {
|
||
// Check if the combined CSS file exists (e.g. maybe it was removed by mistake from the caching directory
|
||
// Or it wasn't created in the first place due to an error
|
||
if ($assetType === 'css' && isset($finalValues['uri_to_final_css_file'], $finalValues['link_hrefs'])
|
||
&& is_file(WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() . $finalValues['uri_to_final_css_file'])) {
|
||
return $optionValueArray;
|
||
}
|
||
|
||
// Check if the combined JS file exists (e.g. maybe it was removed by mistake from the caching directory
|
||
// Or it wasn't created in the first place due to an error
|
||
if ($assetType === 'js' && isset($finalValues['uri_to_final_js_file'], $finalValues['script_srcs'])
|
||
&& is_file(WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir() . $finalValues['uri_to_final_js_file'])) {
|
||
return $optionValueArray;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} elseif ($forType === 'item') {
|
||
return $optionValueArray;
|
||
}
|
||
}
|
||
|
||
// File exists, but it's invalid or outdated; Delete it as it has to be re-generated
|
||
self::clearAssetCachedData($jsonStorageFile);
|
||
|
||
return array();
|
||
}
|
||
|
||
/**
|
||
* @param $jsonStorageFile
|
||
* @param $relPathAssetCacheDir
|
||
* @param $list
|
||
* @param $forType
|
||
*/
|
||
public static function setAssetCachedData($jsonStorageFile, $relPathAssetCacheDir, $list, $forType = 'combine')
|
||
{
|
||
// Combine CSS/JS JSON Storage
|
||
if ($forType === 'combine') {
|
||
// Only clean request URIs allowed
|
||
if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
|
||
list($requestUri) = explode('?', $_SERVER['REQUEST_URI']);
|
||
} else {
|
||
$requestUri = $_SERVER['REQUEST_URI'];
|
||
}
|
||
|
||
$requestUriPart = $requestUri;
|
||
|
||
// Same results for Homepage (any pagination), 404 Not Found & Date archive pages
|
||
if ($requestUri === '/' || is_404() || is_date() || Misc::isHomePage()) {
|
||
$requestUriPart = '';
|
||
}
|
||
|
||
// Treat the pagination pages the same as the main page (same it's done for the unloading rules)
|
||
if (($currentPage = get_query_var('paged')) && (is_archive() || is_singular())) {
|
||
$paginationBase = isset($GLOBALS['wp_rewrite']->pagination_base) ? $GLOBALS['wp_rewrite']->pagination_base : 'page';
|
||
$requestUriPart = str_replace('/'.$paginationBase.'/'.$currentPage.'/', '', $requestUriPart);
|
||
}
|
||
|
||
$dirToFilename = WP_CONTENT_DIR . dirname($relPathAssetCacheDir) . '/_storage/'
|
||
. parse_url(site_url(), PHP_URL_HOST) .
|
||
$requestUriPart . '/';
|
||
|
||
$dirToFilename = str_replace('//', '/', $dirToFilename);
|
||
|
||
if (! is_dir($dirToFilename)) {
|
||
$makeFileDir = @mkdir($dirToFilename, FS_CHMOD_DIR, true);
|
||
|
||
if (! $makeFileDir) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
$assetsFile = $dirToFilename . self::filterStorageFileName($jsonStorageFile);
|
||
|
||
// CSS/JS JSON FILE DATA
|
||
$assetsValue = $list;
|
||
}
|
||
|
||
// Optimize single CSS/JS item JSON Storage
|
||
if ($forType === 'item') {
|
||
$dirToFilename = WP_CONTENT_DIR . dirname($relPathAssetCacheDir) . '/_storage/'.self::$optimizedSingleFilesDir.'/';
|
||
|
||
$dirToFilename = str_replace('//', '/', $dirToFilename);
|
||
|
||
if (! is_dir($dirToFilename)) {
|
||
$makeFileDir = @mkdir($dirToFilename, FS_CHMOD_DIR, true);
|
||
|
||
if (! $makeFileDir) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
$assetsFile = $dirToFilename . $jsonStorageFile;
|
||
$assetsValue = $list;
|
||
}
|
||
|
||
FileSystem::filePutContents($assetsFile, $assetsValue);
|
||
}
|
||
|
||
/**
|
||
* @param $jsonStorageFile
|
||
*/
|
||
public static function clearAssetCachedData($jsonStorageFile)
|
||
{
|
||
if (strpos($jsonStorageFile, '-combined') !== false) {
|
||
/*
|
||
* #1: Combined CSS/JS JSON
|
||
*/
|
||
// Only clean request URIs allowed
|
||
if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
|
||
list($requestUri) = explode('?', $_SERVER['REQUEST_URI']);
|
||
} else {
|
||
$requestUri = $_SERVER['REQUEST_URI'];
|
||
}
|
||
|
||
$requestUriPart = $requestUri;
|
||
|
||
// Same results for Homepage (any pagination), 404 Not Found & Date archive pages
|
||
if ($requestUri === '/' || is_404() || is_date() || Misc::isHomePage()) {
|
||
$requestUriPart = '';
|
||
}
|
||
|
||
// Treat the pagination pages the same as the main page (same it's done for the unloading rules)
|
||
if (($currentPage = get_query_var('paged')) && (is_archive() || is_singular())) {
|
||
$paginationBase = isset($GLOBALS['wp_rewrite']->pagination_base) ? $GLOBALS['wp_rewrite']->pagination_base : 'page';
|
||
$requestUriPart = str_replace('/'.$paginationBase.'/'.$currentPage.'/', '', $requestUriPart);
|
||
}
|
||
|
||
$dirToFilename = WP_CONTENT_DIR . self::getRelPathPluginCacheDir() . '_storage/'
|
||
. parse_url(site_url(), PHP_URL_HOST) .
|
||
$requestUriPart;
|
||
|
||
// If it doesn't have "/" at the end, append it (it will prevent double forward slashes)
|
||
if (substr($dirToFilename, - 1) !== '/') {
|
||
$dirToFilename .= '/';
|
||
}
|
||
|
||
$assetsFile = $dirToFilename . self::filterStorageFileName($jsonStorageFile);
|
||
} elseif (strpos($jsonStorageFile, '_optimize_') !== false) {
|
||
/*
|
||
* #2: Optimized CSS/JS JSON
|
||
*/
|
||
$dirToFilename = WP_CONTENT_DIR . self::getRelPathPluginCacheDir() . '_storage/'.self::$optimizedSingleFilesDir.'/';
|
||
$assetsFile = $dirToFilename . $jsonStorageFile;
|
||
}
|
||
|
||
if (is_file($assetsFile)) { // avoid E_WARNING errors | check if it exists first
|
||
@unlink($assetsFile);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Clears all CSS & JS cache
|
||
*
|
||
* @param bool $redirectAfter
|
||
*/
|
||
public static function clearCache($redirectAfter = false)
|
||
{
|
||
if (self::doNotClearCache()) {
|
||
return;
|
||
}
|
||
|
||
// Any actions before clearing the cache?
|
||
do_action('wpacu_clear_cache_before');
|
||
|
||
// No settings available? Must be triggered very early before 'init' action hook; Get the settings!
|
||
if ( ! isset(Main::instance()->settings['clear_cached_files_after']) ) {
|
||
$wpacuSettingsClass = new Settings();
|
||
Main::instance()->settings = $wpacuSettingsClass->getAll();
|
||
}
|
||
|
||
$isUriRequest = isset($_GET['wpacu_clear_cache_print']);
|
||
$isAjaxCallOrUriRequest = (isset($_REQUEST['action']) && $_REQUEST['action'] === WPACU_PLUGIN_ID . '_clear_cache' && is_admin()) || $isUriRequest;
|
||
$clearedOutput = $keptOutput = array();
|
||
|
||
/*
|
||
* STEP 1: Clear all JSON & all assets (.css & .js) files older than $clearFilesOlderThan days
|
||
*/
|
||
$skipFiles = array('index.php', '.htaccess');
|
||
$fileExtToRemove = array('.json', '.css', '.js');
|
||
|
||
$clearFilesOlderThanXDays = (int)Main::instance()->settings['clear_cached_files_after']; // days
|
||
|
||
$assetCleanUpCacheDir = WP_CONTENT_DIR . self::getRelPathPluginCacheDir();
|
||
$storageDir = $assetCleanUpCacheDir . '_storage';
|
||
|
||
/*
|
||
* Targeted directories:
|
||
*
|
||
* $storageDir.'/item/'
|
||
* $assetCleanUpCacheDir.'/css/'
|
||
* $assetCleanUpCacheDir.'/js/'
|
||
*
|
||
* SKIP anything else from $storageDir apart from "item"
|
||
* If a lot of posts are on the website and combine CSS/JS it could lead to memory errors (to be cleared later on)
|
||
*/
|
||
|
||
$userIdDirs = array();
|
||
|
||
if (is_dir($assetCleanUpCacheDir)) {
|
||
$storageEmptyDirs = $allClearableAssets = $allAssetsToKeep = array();
|
||
|
||
$siteHost = (string)parse_url(site_url(), PHP_URL_HOST);
|
||
$siteUri = (string)parse_url(site_url(), PHP_URL_PATH);
|
||
|
||
$relPathToPossibleDir = $storageDir.'/'.$siteHost . $siteUri;
|
||
|
||
$targetedDirs = array(
|
||
$storageDir.'/item/',
|
||
$assetCleanUpCacheDir.'css/',
|
||
$assetCleanUpCacheDir.'js/',
|
||
// Possible common directories with fewer files
|
||
$relPathToPossibleDir.'/category/',
|
||
$relPathToPossibleDir.'/author/',
|
||
$relPathToPossibleDir.'/tag/'
|
||
);
|
||
|
||
foreach ( $targetedDirs as $targetedDir ) {
|
||
$targetedDir = rtrim(str_replace('//', '/', $targetedDir), '/'); // clean it
|
||
|
||
if ( ! is_dir($targetedDir) ) { continue; }
|
||
|
||
$dirItems = new \RecursiveDirectoryIterator( $targetedDir, \RecursiveDirectoryIterator::SKIP_DOTS );
|
||
|
||
foreach (
|
||
new \RecursiveIteratorIterator(
|
||
$dirItems,
|
||
\RecursiveIteratorIterator::SELF_FIRST,
|
||
\RecursiveIteratorIterator::CATCH_GET_CHILD
|
||
) as $item
|
||
) {
|
||
$fileMtime = filemtime($item);
|
||
$fileBaseName = trim( strrchr( $item, '/' ), '/' );
|
||
$fileExt = strrchr( $fileBaseName, '.' );
|
||
|
||
if ( is_file( $item ) && in_array( $fileExt, $fileExtToRemove ) && ( ! in_array( $fileBaseName, $skipFiles ) ) ) {
|
||
$isJsonFile = ( $fileExt === '.json' );
|
||
$isAssetFile = in_array( $fileExt, array( '.css', '.js' ) );
|
||
|
||
// Remove all JSONs & .css & .js (depending on other things as well) ONLY if they are older than $clearFilesOlderThanXDays days (at least one day)
|
||
$clearOlderThanInSeconds = self::$cachedAssetFileExpiresIn; // minimum
|
||
|
||
if ($clearFilesOlderThanXDays > 0) {
|
||
$clearOlderThanInSeconds = (86400 * $clearFilesOlderThanXDays); // 1 day = 86400 seconds
|
||
}
|
||
|
||
// Conditions to delete the cached CSS/JS file:
|
||
// 1) It's older than $clearOlderThanInSeconds since its content was modified
|
||
// 2) It's not within the most recent cached files from /_storage/_recent_items/ (the latest cached assets should always be kept)
|
||
|
||
// $clearFilesOlderThanXDays is taken from
|
||
// "Settings" -> "Plugin Usage Preferences" -> "Clear cached CSS/JS files older than (x) days"
|
||
$isAssetFileToClear = ( $isAssetFile &&
|
||
( strtotime( '-' . $clearOlderThanInSeconds . ' seconds' ) > $fileMtime ) );
|
||
|
||
if ( $isJsonFile || $isAssetFileToClear ) {
|
||
if ( $isJsonFile ) {
|
||
// Clear the JSON files as new ones will be generated
|
||
@unlink($item);
|
||
// [clear output]
|
||
if ($isAjaxCallOrUriRequest && ! is_file($item)) { $clearedOutput[] = $item. ' (storage file)'; }
|
||
// [/clear output]
|
||
}
|
||
|
||
if ( $isAssetFileToClear ) {
|
||
$allClearableAssets[] = $item;
|
||
}
|
||
}
|
||
} elseif ( is_dir( $item ) && ( strpos( $item, '/css/logged-in/' ) !== false || strpos( $item, '/js/logged-in/' ) !== false ) ) {
|
||
$userIdDirs[] = $item;
|
||
} elseif ( $item != $storageDir && strpos( $item, $storageDir ) !== false ) {
|
||
$storageEmptyDirs[] = $item;
|
||
}
|
||
}
|
||
|
||
Misc::rmDir($targetedDir); // if it's empty, remove it
|
||
}
|
||
|
||
if ( ! defined('WPACU_SITE_URL_HOST') ) {
|
||
define( 'WPACU_SITE_URL_HOST', parse_url(site_url(), PHP_URL_HOST) );
|
||
}
|
||
|
||
// Clear all JSON files separately from the storage directory as it will be rebuilt
|
||
self::rmNonEmptyJsonStorageDir($storageDir);
|
||
|
||
// Now go through the JSONs and collect the latest assets, so they would be kept
|
||
// Finally, collect the rest of $allAssetsToKeep from the database transients (if any)
|
||
// Do not check if they are expired or not as their assets could still be referenced
|
||
// until those pages will be accessed in a non-cached way
|
||
global $wpdb;
|
||
|
||
if (in_array(Main::instance()->settings['fetch_cached_files_details_from'], array('db', 'db_disk'))) {
|
||
$sqlGetCacheTransients = <<<SQL
|
||
SELECT option_value FROM `{$wpdb->options}`
|
||
WHERE `option_name` LIKE '%transient_wpacu_css_optimize%' OR `option_name` LIKE '%transient_wpacu_js_optimize%'
|
||
SQL;
|
||
$cacheDbTransients = $wpdb->get_col( $sqlGetCacheTransients );
|
||
|
||
if (! empty($cacheDbTransients)) {
|
||
foreach ($cacheDbTransients as $optionValue) {
|
||
$jsonValueArray = @json_decode($optionValue, ARRAY_A);
|
||
|
||
if (isset($jsonValueArray['optimize_uri'])) {
|
||
$allAssetsToKeep[] = rtrim(Misc::getWpRootDirPath(), '/') . $jsonValueArray['optimize_uri'];
|
||
}
|
||
}
|
||
}
|
||
} elseif (Main::instance()->settings['fetch_cached_files_details_from'] === 'disk') {
|
||
// Since the asset's info is retrieved ONLY from the disk, any transients in the database are irrelevant, thus clear them
|
||
$sqlClearCacheTransients = <<<SQL
|
||
DELETE FROM `{$wpdb->options}`
|
||
WHERE `option_name` LIKE '%transient_wpacu_css_optimize%' OR `option_name` LIKE '%transient_wpacu_js_optimize%'
|
||
SQL;
|
||
$wpdb->query( $sqlClearCacheTransients );
|
||
}
|
||
|
||
/* [clear output] */
|
||
if ($isAjaxCallOrUriRequest) {
|
||
foreach ($allAssetsToKeep as $assetToKeep) {
|
||
$keptOutput[] = $assetToKeep . ' (cached asset file)';
|
||
}
|
||
}
|
||
/* [/clear output] */
|
||
|
||
sort($allAssetsToKeep);
|
||
$allAssetsToKeep = array_unique($allAssetsToKeep);
|
||
|
||
// Finally clear the matched assets, except the active ones
|
||
foreach ($allClearableAssets as $assetFile) {
|
||
if (in_array($assetFile, $allAssetsToKeep)) {
|
||
continue;
|
||
}
|
||
@unlink($assetFile);
|
||
/* [clear output] */if ($isAjaxCallOrUriRequest && ! is_file($assetFile)) { $clearedOutput[] = $assetFile. ' (cached asset file)'; }/* [/clear output] */
|
||
}
|
||
|
||
foreach (array_reverse($storageEmptyDirs) as $storageEmptyDir) {
|
||
Misc::rmDir($storageEmptyDir);
|
||
/* [clear output] */if ($isAjaxCallOrUriRequest && ! is_dir($storageEmptyDir)) { $clearedOutput[] = $storageEmptyDir. ' (storage empty directory)'; }/* [/clear output] */
|
||
}
|
||
|
||
// Remove empty dirs from /css/logged-in/ and /js/logged-in/
|
||
if (! empty($userIdDirs)) {
|
||
foreach ($userIdDirs as $userIdDir) {
|
||
Misc::rmDir($userIdDir); // it needs to be empty, otherwise, it will not be removed
|
||
/* [clear output] */if ($isAjaxCallOrUriRequest && ! is_dir($userIdDir)) { $clearedOutput[] = $userIdDir. ' (user empty directory)'; }/* [/clear output] */
|
||
}
|
||
}
|
||
}
|
||
|
||
self::clearAllCacheOldLegacyDirs();
|
||
self::clearAllCacheInlineContentFromTagsNonStatic();
|
||
|
||
/*
|
||
* STEP 2: Remove all transients related to the Minify CSS/JS files feature
|
||
*/
|
||
$toolsClass = new Tools();
|
||
$toolsClass->clearAllCacheTransients();
|
||
|
||
// Make sure all the caching files/folders are there in case the plugin was upgraded
|
||
Plugin::createCacheFoldersFiles(array('css', 'js'));
|
||
|
||
if ($isAjaxCallOrUriRequest) {
|
||
if (! empty($clearedOutput)) {
|
||
echo 'The following files/directories have been cleared:'."\n";
|
||
if ($isUriRequest) { echo '<br />'; }
|
||
|
||
foreach ($clearedOutput as $clearedInfo) {
|
||
echo esc_html($clearedInfo)."\n";
|
||
if ($isUriRequest) { echo '<br />'; }
|
||
}
|
||
}
|
||
|
||
if (! empty($keptOutput)) {
|
||
echo "\n".'The following files have been kept:'."\n";
|
||
if ($isUriRequest) { echo '<br />'; }
|
||
|
||
foreach ($keptOutput as $keptInfo) {
|
||
echo esc_html($keptInfo)."\n";
|
||
if ($isUriRequest) { echo '<br />'; }
|
||
}
|
||
}
|
||
}
|
||
|
||
// Any actions after clearing the cache?
|
||
do_action('wpacu_clear_cache_after');
|
||
|
||
// [START - Clear cache for other plugins if they are enabled]
|
||
// If, for any reason, someone uses Cache Enabler and want to prevent clearing its cache after Asset CleanUp Pro clears its own cache
|
||
// they can do so via the following code (e.g. in functions.php of their Child theme):
|
||
// add_filter('wpacu_clear_cache_enabler_cache', '__return_false');
|
||
if (assetCleanUpClearCacheEnablerCache()) {
|
||
if ($isAjaxCallOrUriRequest) {
|
||
echo '<br />"Cache Enabler" plugin is active. The following action was called: "cache_enabler_clear_complete_cache"';
|
||
}
|
||
do_action('cache_enabler_clear_complete_cache'); // Cache Enabler
|
||
}
|
||
|
||
// [END - Clear cache for other plugins if they are enabled]
|
||
|
||
set_transient('wpacu_last_clear_cache', time());
|
||
|
||
if ($isUriRequest) {
|
||
exit();
|
||
}
|
||
|
||
if ($redirectAfter && wp_get_referer()) {
|
||
wp_safe_redirect(wp_get_referer());
|
||
exit();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Special Case: Any CSS/JS files from /wp-content/cache//asset-cleanup/(css|js)/item/inline/
|
||
* These files are never loaded as static, externally (from LINK or SCRIPT tag);
|
||
* Their content is just pulled (if not expired) into the STYLE/SCRIPT inline tag
|
||
* If there are any expired files there, remove them
|
||
*
|
||
* @return void
|
||
*/
|
||
public static function clearAllCacheInlineContentFromTagsNonStatic()
|
||
{
|
||
foreach (array('.css', '.js') as $assetExt) {
|
||
$assetTypeDir = ($assetExt === '.css') ? OptimizeCss::getRelPathCssCacheDir() : OptimizeJs::getRelPathJsCacheDir();
|
||
|
||
$assetsInlineTagsContentDir = WP_CONTENT_DIR . $assetTypeDir . self::$optimizedSingleFilesDir . '/inline/';
|
||
|
||
if ( is_dir( $assetsInlineTagsContentDir ) ) {
|
||
$assetInlineTagsContentDirFiles = scandir( $assetsInlineTagsContentDir );
|
||
|
||
foreach ( $assetInlineTagsContentDirFiles as $assetFile ) {
|
||
if ( strpos( $assetFile, $assetExt ) === false ) {
|
||
continue;
|
||
}
|
||
|
||
$fullPathToFile = $assetsInlineTagsContentDir . $assetFile;
|
||
|
||
$isExpired = ( ( time() - 1 * self::$cachedAssetFileExpiresIn ) > filemtime( $fullPathToFile ) );
|
||
|
||
if ( $isExpired ) {
|
||
@unlink( $fullPathToFile );
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @return void
|
||
*/
|
||
public static function clearAllCacheOldLegacyDirs()
|
||
{
|
||
if (is_dir(WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() .'min')) { Misc::rmDir( WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() .'min' ); }
|
||
if (is_dir(WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir() .'min')) { Misc::rmDir( WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir() .'min' ); }
|
||
if (is_dir(WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() .'one')) { Misc::rmDir( WP_CONTENT_DIR . OptimizeCss::getRelPathCssCacheDir() .'one' ); }
|
||
if (is_dir(WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir() .'one')) { Misc::rmDir( WP_CONTENT_DIR . OptimizeJs::getRelPathJsCacheDir() .'one' ); }
|
||
}
|
||
|
||
/**
|
||
* Alias for clearCache() - some developers might have implemented the old clearAllCache()
|
||
*
|
||
* @param bool $redirectAfter
|
||
*/
|
||
public static function clearAllCache($redirectAfter = false)
|
||
{
|
||
self::clearCache($redirectAfter);
|
||
}
|
||
|
||
/**
|
||
* This is usually done when the plugin is deactivated
|
||
* e.g. if you use Autoptimize, and it remains active, you will likely want to have its caching cleared with traces from Asset CleanUp
|
||
*/
|
||
public static function clearOtherPluginsCache()
|
||
{
|
||
self::clearAutoptimizeCache();
|
||
self::clearCacheEnablerCache();
|
||
}
|
||
|
||
/**
|
||
* @return void
|
||
*/
|
||
public static function clearAutoptimizeCache()
|
||
{
|
||
if ( assetCleanUpClearAutoptimizeCache() && Misc::isPluginActive('autoptimize/autoptimize.php')
|
||
&& class_exists('\autoptimizeCache')
|
||
&& method_exists('\autoptimizeCache', 'clearall') ) {
|
||
\autoptimizeCache::clearall();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param string $triggeredFrom (e.g. ajax_call)
|
||
*
|
||
* @return void
|
||
*/
|
||
public static function clearCacheEnablerCache($triggeredFrom = '')
|
||
{
|
||
$isCacheEnablerActive = Misc::isPluginActive('cache-enabler/cache-enabler.php');
|
||
|
||
// [IF AJAX CALL]
|
||
if ($triggeredFrom === 'ajax_call') {
|
||
if ($isCacheEnablerActive) {
|
||
echo '"Cache Enabler" plugin is active.<br />';
|
||
} else {
|
||
echo '"Cache Enabler" plugin is NOT active.<br />';
|
||
exit();
|
||
}
|
||
|
||
if (assetCleanUpClearCacheEnablerCache()) {
|
||
echo '"Cache Enabler" plugin is set to have its cache cleared.<br />';
|
||
} else {
|
||
echo '"Cache Enabler" plugin is set to not have its caching cleared via "WPACU_DO_NOT_ALSO_CLEAR_CACHE_ENABLER_CACHE" constant';
|
||
exit();
|
||
}
|
||
}
|
||
// [/IF AJAX CALL]
|
||
|
||
if ($isCacheEnablerActive && assetCleanUpClearCacheEnablerCache()) {
|
||
do_action('cache_enabler_clear_complete_cache');
|
||
}
|
||
|
||
// [IF AJAX CALL]
|
||
if ($triggeredFrom === 'ajax_call') {
|
||
if (did_action('cache_enabler_clear_complete_cache')) {
|
||
echo '"Cache Enabler" plugin had its "cache_enabler_clear_complete_cache" action triggered.<br />';
|
||
}
|
||
|
||
exit();
|
||
}
|
||
// [/IF AJAX CALL]
|
||
}
|
||
|
||
/**
|
||
* @param bool $includeHtmlTags
|
||
*
|
||
* @return array
|
||
*/
|
||
public static function getStorageStats($includeHtmlTags = true)
|
||
{
|
||
$assetCleanUpCacheDir = WP_CONTENT_DIR . self::getRelPathPluginCacheDir();
|
||
|
||
if (is_dir($assetCleanUpCacheDir)) {
|
||
$dirItems = new \RecursiveDirectoryIterator($assetCleanUpCacheDir, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||
$fileDirs = $fileDirsWithCssJs = array();
|
||
|
||
// All files
|
||
$totalFiles = 0;
|
||
$totalSize = 0;
|
||
|
||
// Just .css & .js
|
||
$totalSizeAssets = 0;
|
||
$totalFilesAssets = 0;
|
||
|
||
foreach (new \RecursiveIteratorIterator($dirItems, \RecursiveIteratorIterator::SELF_FIRST, \RecursiveIteratorIterator::CATCH_GET_CHILD) as $item) {
|
||
$fileBaseName = trim(strrchr($item, '/'), '/');
|
||
$fileExt = strrchr($fileBaseName, '.');
|
||
|
||
if ($item->isFile()) {
|
||
$fileSize = $item->getSize();
|
||
|
||
$fileDir = trim(dirname($item));
|
||
$fileDirs[$fileDir][] = $fileSize;
|
||
|
||
$totalSize += $fileSize;
|
||
$totalFiles ++;
|
||
|
||
if (in_array($fileExt, array('.css', '.js'))) {
|
||
$fileDirsWithCssJs[] = $fileDir;
|
||
$totalSizeAssets += $fileSize;
|
||
$totalFilesAssets ++;
|
||
}
|
||
}
|
||
}
|
||
|
||
ksort($fileDirs, SORT_ASC);
|
||
|
||
return array(
|
||
'total_size' => Misc::formatBytes($totalSize, 2, '', $includeHtmlTags),
|
||
'total_files' => $totalFiles,
|
||
|
||
'total_size_assets' => Misc::formatBytes($totalSizeAssets, 2, '', $includeHtmlTags),
|
||
'total_files_assets' => $totalFilesAssets,
|
||
|
||
'dirs_files_sizes' => $fileDirs,
|
||
'dirs_css_js' => array_unique($fileDirsWithCssJs)
|
||
);
|
||
}
|
||
|
||
return array();
|
||
}
|
||
|
||
/**
|
||
* Prevent clear cache function in the following situations
|
||
*
|
||
* @return bool
|
||
*/
|
||
public static function doNotClearCache()
|
||
{
|
||
// WooCommerce GET or AJAX call
|
||
if (isset($_GET['wc-ajax']) && $_GET['wc-ajax']) {
|
||
return true;
|
||
}
|
||
|
||
if (defined('WC_DOING_AJAX') && WC_DOING_AJAX === true) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @param $fileName
|
||
*
|
||
* @return array|string|string[]
|
||
*/
|
||
public static function filterStorageFileName($fileName)
|
||
{
|
||
$filterString = '';
|
||
|
||
if (is_404()) {
|
||
$filterString = '-404-not-found';
|
||
} elseif (is_date()) {
|
||
$filterString = '-date';
|
||
} elseif (Misc::isHomePage()) {
|
||
$filterString = '-homepage';
|
||
}
|
||
|
||
$current_user = wp_get_current_user();
|
||
|
||
if (isset($current_user->ID) && $current_user->ID > 0) {
|
||
$fileName = str_replace(
|
||
'{maybe-extra-info}',
|
||
$filterString.'-logged-in',
|
||
$fileName
|
||
);
|
||
} else {
|
||
// Just clear {maybe-extra-info}
|
||
$fileName = str_replace('{maybe-extra-info}', $filterString, $fileName);
|
||
}
|
||
|
||
return $fileName;
|
||
}
|
||
|
||
/**
|
||
* @param string $anyCdnUrl
|
||
*
|
||
* @return array|string|string[]
|
||
*/
|
||
public static function filterWpContentUrl($anyCdnUrl = '')
|
||
{
|
||
$wpContentUrl = WP_CONTENT_URL;
|
||
|
||
$parseContentUrl = parse_url($wpContentUrl);
|
||
$parseBaseUrl = parse_url(site_url());
|
||
|
||
// Perhaps WPML plugin is used and the content URL is different from the current domain which might be for a different language
|
||
if ( ($parseContentUrl['host'] !== $parseBaseUrl['host']) &&
|
||
(isset($_SERVER['HTTP_HOST'], $parseContentUrl['path']) && $_SERVER['HTTP_HOST'] !== $parseContentUrl['host']) &&
|
||
is_dir(rtrim(ABSPATH, '/') . $parseContentUrl['path']) ) {
|
||
$wpContentUrl = str_replace($parseContentUrl['host'], $parseBaseUrl['host'], $wpContentUrl);
|
||
}
|
||
|
||
// Is the page loaded via SSL, but the site url from the database starts with 'http://'
|
||
// Then use '//' in front of CSS/JS generated via Asset CleanUp
|
||
if (Misc::isHttpsSecure() && strpos($wpContentUrl, 'http://') !== false) {
|
||
$wpContentUrl = str_replace('http://', '//', $wpContentUrl);
|
||
}
|
||
|
||
if ($anyCdnUrl) {
|
||
$wpContentUrl = str_replace(site_url(), self::cdnToUrlFormat($anyCdnUrl, 'raw'), $wpContentUrl);
|
||
}
|
||
|
||
return $wpContentUrl;
|
||
}
|
||
|
||
/**
|
||
* @param $assetContent
|
||
* @param $forAssetType
|
||
*
|
||
* @return string|string[]
|
||
*/
|
||
public static function stripSourceMap($assetContent, $forAssetType)
|
||
{
|
||
if ($forAssetType === 'css') {
|
||
$sourceMappingURLStr = '/*# sourceMappingURL=';
|
||
$sourceMappingURLStrReplaceStart = '/*';
|
||
} else {
|
||
$sourceMappingURLStr = '//# sourceMappingURL=';
|
||
$sourceMappingURLStrReplaceStart = '//';
|
||
}
|
||
|
||
$assetContent = trim($assetContent);
|
||
|
||
if (strpos($assetContent, "\n") !== false) {
|
||
$allContentLines = explode("\n", $assetContent);
|
||
$lastContentLine = end($allContentLines);
|
||
|
||
if (strpos($lastContentLine, $sourceMappingURLStr) !== false) {
|
||
return str_replace( $sourceMappingURLStr, $sourceMappingURLStrReplaceStart.'# Current File Updated by '.WPACU_PLUGIN_TITLE.' - Original Source Map: ', $assetContent );
|
||
}
|
||
}
|
||
|
||
return $assetContent;
|
||
}
|
||
|
||
/**
|
||
* @param $for ("css" or "js")
|
||
*
|
||
* @return bool
|
||
*/
|
||
public static function appendInlineCodeToCombineAssetType($for)
|
||
{
|
||
$settingsIndex = '_combine_loaded_'.$for.'_append_handle_extra';
|
||
return (Misc::isWpVersionAtLeast('5.5') &&
|
||
isset(Main::instance()->settings[$settingsIndex]) &&
|
||
Main::instance()->settings[$settingsIndex]);
|
||
}
|
||
|
||
/**
|
||
* URLs with query strings are not loading Optimised Assets (e.g. combine CSS files into one file)
|
||
* However, there are exceptions such as the ones below (preview, debugging purposes)
|
||
*
|
||
* @return bool
|
||
*/
|
||
public static function loadOptimizedAssetsIfQueryStrings()
|
||
{
|
||
$isPreview = (isset($_GET['preview_id'], $_GET['preview_nonce'], $_GET['preview'])
|
||
|| isset($_GET['preview'])); // show the CSS/JS as combined IF the option is enabled despite the query string (for debugging purposes)
|
||
|
||
if ($isPreview) {
|
||
return true;
|
||
}
|
||
|
||
$ignoreQueryStrings = array(
|
||
'wpacu_no_css_minify',
|
||
'wpacu_no_js_minify',
|
||
'wpacu_no_css_combine',
|
||
'wpacu_no_js_combine',
|
||
'wpacu_debug',
|
||
'wpacu_preload',
|
||
'wpacu_skip_test_mode',
|
||
);
|
||
|
||
$queryStringsToIgnoreFromTheURIForOptimizingAssets = array(
|
||
'_ga',
|
||
'_ke',
|
||
'adgroupid',
|
||
'adid',
|
||
'age-verified',
|
||
'ao_noptimize',
|
||
'campaignid',
|
||
'ck_subscriber_id', // ConvertKit's query parameter
|
||
'cn-reloaded',
|
||
'dclid',
|
||
'dm_i', // dotdigital
|
||
'dm_t', // dotdigital
|
||
'ef_id',
|
||
'epik', // Pinterest
|
||
'fb_action_ids',
|
||
'fb_action_types',
|
||
'fb_source',
|
||
'fbclick',
|
||
'fbclid',
|
||
'gclid',
|
||
'gclsrc',
|
||
'mc_cid',
|
||
'mc_eid',
|
||
'mkt_tok', // Marketo (tracking users)
|
||
'msclkid', // Microsoft Click ID
|
||
'mtm_campaign',
|
||
'mtm_cid',
|
||
'mtm_content',
|
||
'mtm_keyword',
|
||
'mtm_medium',
|
||
'mtm_source',
|
||
'pk_campaign', // Piwik PRO URL builder
|
||
'pk_cid', // Piwik PRO URL builder
|
||
'pk_content', // Piwik PRO URL builder
|
||
'pk_keyword', // Piwik PRO URL builder
|
||
'pk_medium', // Piwik PRO URL builder
|
||
'pk_source', // Piwik PRO URL builder
|
||
'ref',
|
||
'SSAID',
|
||
'sscid',
|
||
'usqp',
|
||
'utm_campaign',
|
||
'utm_content',
|
||
'utm_expid',
|
||
'utm_expid',
|
||
'utm_medium',
|
||
'utm_referrer',
|
||
'utm_source',
|
||
'utm_term',
|
||
);
|
||
|
||
$isQueryString = false;
|
||
|
||
foreach (array_merge($ignoreQueryStrings, $queryStringsToIgnoreFromTheURIForOptimizingAssets) as $ignoreQueryString) {
|
||
if (isset($_GET[$ignoreQueryString])) {
|
||
$isQueryString = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return $isQueryString;
|
||
}
|
||
|
||
/**
|
||
* Possible values returned: 'db', 'disk'
|
||
*
|
||
* @return mixed|string
|
||
*/
|
||
public static function fetchCachedFilesFrom()
|
||
{
|
||
if (Main::instance()->settings['fetch_cached_files_details_from'] === 'db_disk') {
|
||
if ( ! isset( $GLOBALS['wpacu_from_location_inc'] ) ) {
|
||
$GLOBALS['wpacu_from_location_inc'] = 1;
|
||
}
|
||
$fromLocation = ( $GLOBALS['wpacu_from_location_inc'] % 2 ) ? 'db' : 'disk';
|
||
} else {
|
||
$fromLocation = Main::instance()->settings['fetch_cached_files_details_from'];
|
||
}
|
||
|
||
return $fromLocation;
|
||
}
|
||
|
||
/**
|
||
* The following custom methods of transients work for both (MySQL) database and local storage
|
||
* By default, the data is stored in the disk only
|
||
*
|
||
* @param $transient
|
||
*
|
||
* @return bool|mixed
|
||
*/
|
||
public static function getTransient($transient)
|
||
{
|
||
$fromLocation = self::fetchCachedFilesFrom();
|
||
|
||
$contents = '';
|
||
|
||
// Stored in the "Disk": Local record
|
||
if ($fromLocation === 'disk') {
|
||
$dirToFilename = WP_CONTENT_DIR . self::getRelPathPluginCacheDir() . '_storage/'.self::$optimizedSingleFilesDir.'/';
|
||
$assetsFile = $dirToFilename . $transient.'.json';
|
||
|
||
if (is_file($assetsFile)) {
|
||
$contents = trim(FileSystem::fileGetContents($assetsFile));
|
||
|
||
if (! $contents) {
|
||
// The file is empty or the contents could not be retrieved
|
||
// If a PHP reading error was triggered, it should be logged in the "error_log" file
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return $contents;
|
||
}
|
||
|
||
// Stored in the "Database"
|
||
// MySQL record: $fromLocation default 'db'
|
||
return get_transient($transient);
|
||
}
|
||
|
||
/**
|
||
* @param $transientName
|
||
*/
|
||
public static function deleteTransient($transientName)
|
||
{
|
||
$fetchFrom = Main::instance()->settings['fetch_cached_files_details_from'];
|
||
|
||
if (in_array($fetchFrom, array('db', 'db_disk'))) {
|
||
// MySQL record
|
||
delete_transient( $transientName );
|
||
}
|
||
|
||
if (in_array($fetchFrom, array('disk', 'db_disk'))) {
|
||
// File record (in case there is any)
|
||
self::clearAssetCachedData( $transientName . '.json' );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param $transient
|
||
* @param $value
|
||
* @param int $expiration
|
||
*/
|
||
public static function setTransient($transient, $value, $expiration = 0)
|
||
{
|
||
$fetchFrom = Main::instance()->settings['fetch_cached_files_details_from'];
|
||
|
||
if (in_array($fetchFrom, array('db', 'db_disk'))) {
|
||
// MySQL record
|
||
set_transient( $transient, $value, $expiration );
|
||
}
|
||
|
||
if (in_array($fetchFrom, array('disk', 'db_disk'))) {
|
||
// File record
|
||
self::setAssetCachedData(
|
||
$transient . '.json',
|
||
OptimizeCss::getRelPathCssCacheDir(),
|
||
$value,
|
||
'item'
|
||
);
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* @return array
|
||
*/
|
||
public static function getAnyCdnUrls()
|
||
{
|
||
if (! Main::instance()->settings['cdn_rewrite_enable']) {
|
||
return array();
|
||
}
|
||
|
||
$cdnUrls = array();
|
||
|
||
$cdnCssUrl = trim(Main::instance()->settings['cdn_rewrite_url_css']) ?: '';
|
||
$cdnJsUrl = trim(Main::instance()->settings['cdn_rewrite_url_js']) ?: '';
|
||
|
||
if ($cdnCssUrl) {
|
||
$cdnUrls['css'] = $cdnCssUrl;
|
||
}
|
||
|
||
if ($cdnJsUrl) {
|
||
$cdnUrls['js'] = $cdnJsUrl;
|
||
}
|
||
|
||
return $cdnUrls;
|
||
}
|
||
|
||
/**
|
||
* @param $cdnUrl
|
||
* @param $getType
|
||
*
|
||
* @return string
|
||
*/
|
||
public static function cdnToUrlFormat($cdnUrl, $getType)
|
||
{
|
||
if (! $cdnUrl) {
|
||
return site_url();
|
||
}
|
||
|
||
$cdnUrlFinal = $cdnUrl;
|
||
|
||
// CNAME (not URL) was added
|
||
if (strpos($cdnUrl, '//') === false) {
|
||
$cdnUrlFinal = '//'.$cdnUrl;
|
||
}
|
||
|
||
// The URL will start with //
|
||
if ($getType === 'rel') {
|
||
$cdnUrlFinal = trim(str_ireplace(array('http://', 'https://'), '//', $cdnUrl));
|
||
}
|
||
|
||
return rtrim($cdnUrlFinal, '/'); // no trailing slash after the CDN URL
|
||
}
|
||
|
||
/**
|
||
* This is related to the cached CSS/JS combined files from _storage directory located within getRelPathPluginCacheDir() caching directory
|
||
*
|
||
* @param $postId
|
||
* @param bool $checkTiming | if set to "true" it will check if the caching timing expires and if it did, then delete the file
|
||
*/
|
||
public static function clearJsonStorageForPost($postId, $checkTiming = false)
|
||
{
|
||
$postPermalink = get_permalink($postId);
|
||
$requestUriPath = (string)parse_url($postPermalink, PHP_URL_PATH);
|
||
|
||
$dirToFilename = WP_CONTENT_DIR . self::getRelPathPluginCacheDir() . '/_storage/'
|
||
. parse_url(site_url(), PHP_URL_HOST) . '/'. $requestUriPath;
|
||
|
||
$dirToFilename = str_replace('//', '/', $dirToFilename);
|
||
|
||
$clearOlderThanInSeconds = self::$cachedAssetFileExpiresIn;
|
||
|
||
$clearFilesOlderThanXDays = Main::instance()->settings['clear_cached_files_after'];
|
||
|
||
if ($clearFilesOlderThanXDays > 0) {
|
||
$clearOlderThanInSeconds += (86400 * $clearFilesOlderThanXDays);
|
||
}
|
||
|
||
if (is_dir($dirToFilename)) {
|
||
$filesInDir = scandir($dirToFilename);
|
||
|
||
if (! empty($filesInDir)) {
|
||
foreach ($filesInDir as $wpacuFile) {
|
||
if ( $wpacuFile === '.' || $wpacuFile === '..' ) {
|
||
continue;
|
||
}
|
||
|
||
$pathToFile = $dirToFilename . $wpacuFile;
|
||
|
||
if (strrchr($wpacuFile, '.') === '.json' && is_file($pathToFile)) {
|
||
if ($checkTiming) {
|
||
$isExpired = ( strtotime( '-' . $clearOlderThanInSeconds . ' seconds' ) > filemtime($pathToFile) );
|
||
|
||
if (! $isExpired) {
|
||
// Not expired yet, do not remove it by skipping this loop
|
||
continue;
|
||
}
|
||
}
|
||
|
||
@unlink($dirToFilename . $wpacuFile);
|
||
}
|
||
}
|
||
|
||
Misc::rmDir($dirToFilename);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param $targetDir
|
||
*/
|
||
public static function rmNonEmptyJsonStorageDir($targetDir)
|
||
{
|
||
$dirFiles = glob($targetDir . '/*');
|
||
|
||
foreach ($dirFiles as $targetFile) {
|
||
if (is_dir($targetFile)) {
|
||
self::rmNonEmptyJsonStorageDir($targetFile);
|
||
} elseif(strrchr($targetFile, '.') === '.json') {
|
||
@unlink($targetFile);
|
||
}
|
||
}
|
||
|
||
if (strpos($targetDir, WPACU_SITE_URL_HOST) !== false) {
|
||
Misc::rmDir($targetDir);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param $assetContentSha1
|
||
* @param $assetType
|
||
*
|
||
* @return bool
|
||
*/
|
||
public static function originalContentIsAlreadyMarkedAsMinified($assetContentSha1, $assetType)
|
||
{
|
||
$optionToCheck = WPACU_PLUGIN_ID . '_global_data';
|
||
$globalKey = 'already_minified'; // HEAD or BODY
|
||
|
||
$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
|
||
$existingListJson = get_option($optionToCheck);
|
||
|
||
$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
|
||
$existingList = $existingListData['list'];
|
||
|
||
return isset( $existingList[ $assetType ]['already_minified'] ) && in_array( $assetContentSha1, $existingList[ $assetType ]['already_minified'] );
|
||
}
|
||
|
||
/**
|
||
* @param $assetContentSha1
|
||
* @param $assetType
|
||
*/
|
||
public static function originalContentMarkAsAlreadyMinified($assetContentSha1, $assetType)
|
||
{
|
||
$optionToUpdate = WPACU_PLUGIN_ID . '_global_data';
|
||
$globalKey = 'already_minified'; // HEAD or BODY
|
||
|
||
$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
|
||
$existingListJson = get_option($optionToUpdate);
|
||
|
||
$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
|
||
$existingList = $existingListData['list'];
|
||
|
||
// Limit it to 100 maximum entries
|
||
$totalEntries = isset($existingList[$assetType]['already_minified']) ? count($existingList[$assetType]['already_minified']) : 0;
|
||
|
||
if ($totalEntries === 100) {
|
||
return; // stop here
|
||
}
|
||
|
||
if ($totalEntries < 1) { // declare the array if no entries are there
|
||
$existingList[$assetType]['already_minified'] = array();
|
||
} else if ($totalEntries < 100) { // append to the array
|
||
$existingList[$assetType]['already_minified'][] = $assetContentSha1;
|
||
} else if ($totalEntries > 100) { // already passed the number, trim the list
|
||
$existingList[$assetType]['already_minified'] = array_slice($existingList[$assetType]['already_minified'], 0, 100);
|
||
}
|
||
|
||
update_option($optionToUpdate, wp_json_encode(Misc::filterList($existingList)));
|
||
}
|
||
|
||
// [START] For debugging purposes
|
||
/**
|
||
* @return array
|
||
*/
|
||
public static function getAlreadyMarkedAsMinified()
|
||
{
|
||
$alreadyMinified = array();
|
||
|
||
$optionToUpdate = WPACU_PLUGIN_ID . '_global_data';
|
||
$globalKey = 'already_minified';
|
||
|
||
$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
|
||
$existingListJson = get_option($optionToUpdate);
|
||
|
||
$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
|
||
$existingList = $existingListData['list'];
|
||
|
||
if (isset($existingList['styles']['already_minified'])) {
|
||
$alreadyMinified['styles'] = $existingList['styles']['already_minified'];
|
||
}
|
||
|
||
if (isset($existingList['scripts']['already_minified'])) {
|
||
$alreadyMinified['scripts'] = $existingList['scripts']['already_minified'];
|
||
}
|
||
|
||
return $alreadyMinified;
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public static function removeAlreadyMarkedAsMinified()
|
||
{
|
||
$optionToUpdate = WPACU_PLUGIN_ID . '_global_data';
|
||
$globalKey = 'already_minified';
|
||
|
||
$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
|
||
$existingListJson = get_option($optionToUpdate);
|
||
|
||
$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
|
||
$existingList = $existingListData['list'];
|
||
|
||
if (isset($existingList['styles']['already_minified'])) {
|
||
unset($existingList['styles']['already_minified']);
|
||
}
|
||
|
||
if (isset($existingList['scripts']['already_minified'])) {
|
||
unset($existingList['scripts']['already_minified']);
|
||
}
|
||
|
||
update_option($optionToUpdate, wp_json_encode(Misc::filterList($existingList)));
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public static function limitAlreadyMarkedAsMinified()
|
||
{
|
||
$optionToUpdate = WPACU_PLUGIN_ID . '_global_data';
|
||
$globalKey = 'already_minified';
|
||
|
||
$existingListEmpty = array('styles' => array($globalKey => array()), 'scripts' => array($globalKey => array()));
|
||
$existingListJson = get_option($optionToUpdate);
|
||
|
||
$existingListData = Main::instance()->existingList($existingListJson, $existingListEmpty);
|
||
$existingList = $existingListData['list'];
|
||
|
||
$maxEntries = 100;
|
||
|
||
// Limit it to $maxEntries maximum entries
|
||
foreach (array('styles', 'scripts') as $assetType) {
|
||
$totalEntries = isset( $existingList[ $assetType ]['already_minified'] ) ? count( $existingList[ $assetType ]['already_minified'] ) : 0;
|
||
if ($totalEntries > $maxEntries) {
|
||
$existingList[ $assetType ]['already_minified'] = array_slice( $existingList[ $assetType ]['already_minified'], 0, $maxEntries );
|
||
}
|
||
}
|
||
|
||
update_option($optionToUpdate, wp_json_encode(Misc::filterList($existingList)));
|
||
}
|
||
// [END] For debugging purposes
|
||
|
||
}
|