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

2197 lines
70 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

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

<?php
namespace WpAssetCleanUp\OptimiseAssets;
use WpAssetCleanUp\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
}