wp_back/wp-content/plugins/wp-asset-clean-up/classes/OptimiseAssets/OptimizeCommon.php

2197 lines
70 KiB
PHP
Raw Normal View History

2024-05-20 15:37:46 +03:00
<?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
}