diff --git a/changelog.txt b/changelog.txt index d5253d3..6c157a8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,33 @@ += v22.07.03 (2023-02-07) = +- Fixed: Tweaks::woocommerce_misc() -> Check if action_scheduler_migration_status is complete to prevent the list on the Scheduled Actions page from disappearing. +- Fixed: Tweaks::woocommerce_widget_remove() -> The classic widget is not disabled. +- Fixed: Plugin::get_precache_maxfile() -> Invalid constant, replace maxfile with precache_maxfile. +- Fixed: Filesystem::sanitize_precache_maxfile() -> Set the limit to 100 by default. +- Fixed: Becache::export() -> Invalid expiration time. Already in timestamp format not in seconds. +- Fixed: WP_Object_Cache::dc_save() -> Serialize twice when checking object size. +- Fixed: Configuration -> A notice is not shown when the constant is already defined. +- Added: Configuration -> Storage Options, Check file limits in real-time and Exclude Empty Object Data. +- Added: Configuration -> Runtime Options, Deactivate Concatenate WP-Admin Scripts and Deactivate WP Cron. +- Added: WP-CLI command -> run:optimizedb. +- Added: DOCKET_CACHE_MAXFILE_LIVECHECK constant to enable checking file limits in real-time. +- Added: DOCKET_CACHE_PRECACHE_MAXKEY, DOCKET_CACHE_PRECACHE_MAXGROUP constant to limit cache keys and groups. +- Added: DOCKET_CACHE_STALECACHE_IGNORE constant to enable excluding stale cache from being stored on disk. +- Added: DOCKET_CACHE_EMPTYCACHE constant to enable excluding empty caches from being stored on disk. +- Added: DOCKET_CACHE_AUTOUPDATE_TOGGLE constant, only to sync with WordPress auto_update_plugins option. +- Added: DOCKET_CACHE_GCRON_DISABLED constant to disable garbage collector cron event. +- Added: Filesystem::suspend_cache_write() -> Temporarily suspends new cache from being stored on disk. +- Changed: DOCKET_CACHE_AUTOUPDATE constant can only be defined manually to force an automatic update. +- Improved: Increase timeout limit if lower than 180 seconds. +- Improved: Constans::maybe_define() -> Keep track of constants that have been defined in the $GLOBAL['DOCKET_CACHE_RUNTIME'] list. +- Improved: WP_Object_Cache::maybe_expire() -> Set expiration to 1 day for key/group matches with the stale cache. +- Improved: Event::garbage_collector() -> Improve wc_cache filtering and other possible stale caches. +- Improved: WP_Object_Cache::dc_code() -> Use native var_export for data type objects and arrays if only have stdClass. +- Removed: Event::watchproc() -> No longer needed. +- Updated: DOCKET_CACHE_ADVCPOST_POSTTYPE -> Set the built-in Post Type as the default. +- Updated: Filesystem::get_max_execution_time() -> Accept value to set time limit. + +Thanks to Kevin Shenk of Avunu LLC for providing access to the staging server for testing purposes. + = v22.07.02 (2022-12-10) = - Fixed: Tweaks::cache_http_response() -> Default TTL. - Fixed: Tweaks::wpservehappy() -> missing array key. diff --git a/dist/docket-cache.zip b/dist/docket-cache.zip index 3519a7c..ad21303 100644 Binary files a/dist/docket-cache.zip and b/dist/docket-cache.zip differ diff --git a/docket-cache.php b/docket-cache.php index 2c2070b..9fb6c8a 100644 --- a/docket-cache.php +++ b/docket-cache.php @@ -12,8 +12,8 @@ * @wordpress-plugin * Plugin Name: Docket Cache * Plugin URI: https://docketcache.com/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash - * Version: 22.07.02 - * VerPrev: 21.07.01 + * Version: 22.07.03 + * VerPrev: 22.07.02 * Description: A persistent object cache stored as a plain PHP code, accelerates caching with OPcache backend. * GitHub Plugin URI: https://github.com/nawawi/docket-cache * Author: Nawawi Jamili diff --git a/includes/admin/config.php b/includes/admin/config.php index 6d47ad5..0035253 100644 --- a/includes/admin/config.php +++ b/includes/admin/config.php @@ -169,7 +169,7 @@
+ + tab_title(esc_html__('Resources', 'docket-cache')); ?> diff --git a/includes/cache.php b/includes/cache.php index 75b7862..b32362c 100644 --- a/includes/cache.php +++ b/includes/cache.php @@ -18,6 +18,7 @@ /** * Core class that implements an object cache. */ +#[AllowDynamicProperties] class WP_Object_Cache { /** @@ -90,6 +91,13 @@ class WP_Object_Cache */ private $multisite; + /** + * Holds the value of Network Id. + * + * @var bool + */ + private $network_id = 1; + /** * The cache path. * @@ -154,11 +162,39 @@ class WP_Object_Cache private $precache_hashkey = ''; /** - * Precache max entries. + * Precache max keys. + * + * @var int + */ + private $precache_maxkey = 20; + + /** + * Precache max groups. + * + * @var int + */ + private $precache_maxgroup = 20; + + /** + * Precache max cache file (url). * * @var int */ - private $precache_maxlist = 500; + private $precache_maxfile = 100; + + /** + * Maximum cache file. + * + * @var int + */ + private $maxfile = 50000; + + /** + * Check cache file limit in real-time. + * + * @var bool + */ + private $maxfile_livecheck = true; /** * The maximum time in seconds a script is allowed to run. @@ -175,25 +211,25 @@ class WP_Object_Cache private $wp_start_timestamp = 0; /** - * Stalecache status. + * Dev mode. * * @var bool */ - private $is_stalecache = false; + private $is_dev = false; /** - * List of stale cache to remove. + * Ignore stale cache. * - * @var array + * @var bool */ - private $stalecache_list = []; + private $ignore_stalecache = false; /** - * Dev mode. + * Ignore empty cache. * * @var bool */ - private $is_dev = false; + private $ignore_emptycache = false; /** * Sets up object properties. @@ -202,6 +238,7 @@ public function __construct() { $this->multisite = \function_exists('is_multisite') && is_multisite(); $this->blog_prefix = $this->switch_to_blog(get_current_blog_id()); + $this->network_id = (int) nwdcx_network_id(); $this->dc_init(); } @@ -213,7 +250,7 @@ public function __construct() * * @return bool whether the key exists in the cache for the given group */ - protected function _exists($key, $group) + protected function _exists($key, $group, $force = false) { // check key if (!$this->is_valid_key($key)) { @@ -229,15 +266,20 @@ protected function _exists($key, $group) return false; } - $is_exists = !empty($this->cache) && isset($this->cache[$group]) && (isset($this->cache[$group][$key]) || \array_key_exists($key, $this->cache[$group])); - if (!$is_exists && !$this->is_non_persistent_groups($group) && !$this->is_non_persistent_keys($key) && !$this->is_non_persistent_groupkey($group, $key)) { + $is_exists = !$force && !empty($this->cache) && isset($this->cache[$group]) && (isset($this->cache[$group][$key]) || \array_key_exists($key, $this->cache[$group])); + + if (!$is_exists && !$this->is_non_persistent_groups($group) && !$this->is_non_persistent_keys($key) && !$this->is_non_persistent_groupkey($group, $key) && !$this->is_stalecache_ignored($key, $group)) { $data = $this->dc_get($key, $group, false); if (false !== $data) { $is_exists = true; $this->cache[$group][$key] = $data; - if ($this->is_precache && !$this->is_bypass_precache($group, $key)) { - $this->precache[$group][$key] = 1; + if ($this->is_precache && !$this->fs()->suspend_cache_write() && !$this->is_bypass_precache($group, $key)) { + if (empty($this->precache[$group])) { + $this->precache[$group][$key] = 1; + } elseif (\count($this->precache[$group]) < $this->precache_maxkey && \count($this->precache) < $this->precache_maxgroup) { + $this->precache[$group][$key] = 1; + } } } } @@ -359,11 +401,6 @@ public function set($key, $data, $group = 'default', $expire = 0) $group = 'default'; } - // from invalidate cache - if ($this->is_stalecache) { - $this->dc_stalecache_filter($key, $group); - } - $cache_key = $this->dc_key($key, $group); if (\is_object($data)) { @@ -372,7 +409,12 @@ public function set($key, $data, $group = 'default', $expire = 0) $this->cache[$group][$cache_key] = $data; - if ((!$this->is_non_persistent_groups($group) && !$this->is_non_persistent_keys($key) && !$this->is_non_persistent_groupkey($group, $key)) || $this->is_filtered_groups($group, $key)) { + // suspend new cache + if ($this->fs()->suspend_cache_write() && !is_file($this->get_file_path($cache_key, $group))) { + return true; + } + + if ((!$this->is_non_persistent_groups($group) && !$this->is_non_persistent_keys($key) && !$this->is_non_persistent_groupkey($group, $key) && !$this->is_stalecache_ignored($key, $group)) || $this->is_filtered_groups($group, $key)) { $expire = $this->maybe_expire($group, $expire, $key); $this->dc_save($cache_key, $this->cache[$group][$cache_key], $group, $expire, $key); } @@ -431,9 +473,8 @@ public function get($key, $group = 'default', $force = false, &$found = null) $cache_key = $this->dc_key($key, $group); - if ($this->_exists($cache_key, $group)) { + if ($this->_exists($cache_key, $group, $force)) { $found = true; - if (\is_object($this->cache[$group][$cache_key])) { return clone $this->cache[$group][$cache_key]; } @@ -680,7 +721,9 @@ public function stats() public function add_non_persistent_groups($groups) { $groups = (array) $groups; - $this->non_persistent_groups = array_unique(array_merge($this->non_persistent_groups, $groups)); + + $groups = array_fill_keys($groups, true); + $this->non_persistent_groups = array_merge($this->non_persistent_groups, $groups); } /** @@ -690,7 +733,20 @@ public function add_non_persistent_groups($groups) */ protected function is_non_persistent_groups($group) { - return !empty($this->non_persistent_groups) && \in_array($group, $this->non_persistent_groups); + return !empty($this->non_persistent_groups) && \array_key_exists($group, $this->non_persistent_groups); + } + + /** + * Sets the list of non persistent keys. + * + * @param array $keys list of keys that are to be ignored + */ + public function add_non_persistent_keys($keys) + { + $keys = (array) $keys; + + $keys = array_fill_keys($keys, true); + $this->non_persistent_keys = array_merge($this->non_persistent_keys, $keys); } /** @@ -700,7 +756,7 @@ protected function is_non_persistent_groups($group) */ protected function is_non_persistent_keys($key) { - return !empty($this->non_persistent_keys) && \in_array($key, $this->non_persistent_keys); + return !empty($this->non_persistent_keys) && \array_key_exists($key, $this->non_persistent_keys); } /** @@ -711,26 +767,50 @@ protected function is_non_persistent_keys($key) */ protected function is_non_persistent_groupkey($group, $key) { - return !empty($this->non_persistent_groupkey) && \in_array($group.':'.$key, $this->non_persistent_groupkey); + if (!empty($this->non_persistent_groupkey) && !empty($this->non_persistent_groupkey[$group])) { + $bypass_data = $this->non_persistent_groupkey[$group]; + + if (\is_array($bypass_data)) { + return false !== array_search($key, $bypass_data); + } + + if ($key === $bypass_data) { + return true; + } + } + + return false; } /** - * Check if key in non persistent index. + * Bypass preache. * * @param bool $group cache group * @param bool $key cache key */ protected function is_bypass_precache($group, $key) { - if (!empty($_POST) || ($this->fs()->is_docketcachegroup($group) || $this->fs()->is_transient($group) || $this->is_non_persistent_groups($group)) - // wc: woocommerce/includes/class-wc-cache-helper.php - || ('wc_cache_' === substr($key, 0, 9) || 'wc_session_id' === $group || @preg_match('@^wc_.*_cache_prefix@', $key)) - // stale cache *last_changed - || (false !== strpos($key, ':') && @preg_match('@(.*):([a-z0-9]{32}):([0-9\. ]+)$@', $key))) { + if ($this->fs()->is_docketcachegroup($group, $key) || $this->fs()->is_transient($group) || $this->is_non_persistent_groups($group) || $this->is_non_persistent_keys($key) || $this->has_stalecache($key, $group)) { return true; } - return !empty($this->bypass_precache) && \in_array($group.':'.$key, $this->bypass_precache); + if ('gbmedia-cpttables' === $group || 'doing_cron' === $key || preg_match('@^(\d+|[0-9a-f]{32})$@', $key)) { + return true; + } + + if (!empty($this->bypass_precache) && !empty($this->bypass_precache[$group])) { + $bypass_data = $this->bypass_precache[$group]; + + if (\is_array($bypass_data)) { + return false !== array_search($key, $bypass_data); + } + + if ($key === $bypass_data) { + return true; + } + } + + return false; } /** @@ -824,35 +904,72 @@ private function maybe_expire($group, $expire = 0, $key = '') if (\in_array($group, ['site-transient', 'transient'])) { if ('site-transient' === $group && \in_array($key, ['update_plugins', 'update_themes', 'update_core', '_woocommerce_helper_updates'])) { $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d - } elseif ('transient' === $group && 'health-check-site-status-result' === $key) { - $expire = 0; // to check with is_data_uptodate } else { - $expire = $maxttl < 604800 ? 604800 : $maxttl; // 7d + // $expire = $maxttl < 604800 ? 604800 : $maxttl; // 7d + $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d } } elseif (\in_array($group, ['options', 'site-options'])) { $expire = $maxttl < 1209600 ? 1209600 : $maxttl; // 14d - } elseif (\in_array($group, ['terms', 'posts', 'post_meta', 'comments'])) { + } elseif (\in_array($group, ['terms', 'posts', 'post_meta', 'comments', 'comment_feed', 'sites', 'networks'])) { $expire = $maxttl < 1209600 ? 1209600 : $maxttl; // 14d + } - // wp stale cache - // prefix:md5hash:microtime - if (false !== strpos($key, ':') && @preg_match('@(.*):([a-z0-9]{32}):([0-9\. ]+)$@', $key)) { - $expire = $maxttl < 345600 ? $maxttl : 345600; // 4d - } + // wp stale cache + // prefix:md5hash:microtime + // wp_query|get_terms|get_comments|comment_feed|get_sites|get_network_ids|get_page_by_path|other? + elseif (false !== strpos($key, ':') && @preg_match('@^([a-zA-Z0-9\._-]+):([0-9a-f]{32}):([0-9\. ]+)$@', $key)) { + $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d + } + + // wp stale cache + // cache timestamp + elseif ('last_changed' === $key) { + $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d } + // advcpost - // docketcache-post-timestamp + // docketcache-post-(found|media|timestamp) elseif (false !== strpos($group, 'docketcache-post-')) { - $expire = $maxttl < 345600 ? $maxttl : 345600; // 4d + $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d + } + + // advcpost + // docketcache-post-media + elseif ('docketcache-post-media' === $group) { + $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d + } + + // advcpost + // cache timestamp + elseif ('docketcache-post' === $group && 'cache_incr' === $key) { + $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d + } + + // woocommerce stale cache + // cache prefix + elseif (false !== strpos($key, '_cache_prefix') && @preg_match('@^wc_(.*?)_cache_prefix$@', $key)) { + $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d } + // woocommerce stale cache // wc_cache_0.72953700 1651592702 elseif (false !== strpos($key, 'wc_cache_') && @preg_match('@^wc_cache_([0-9\. ]+)_@', $key)) { - $expire = $maxttl < 345600 ? $maxttl : 345600; // 4d + $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d + } elseif (false !== strpos($group, 'wc_cache_') && @preg_match('@^wc_cache_([0-9\. ]+)_@', $group)) { + $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d + } + + // common cache + elseif (preg_match('@[0-9a-f]{32}@', $key)) { + $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d + } + + // else + else { + $expire = $maxttl; } } - // if 0 let's gc handle it by comparing file mtime. return $expire; } @@ -890,7 +1007,7 @@ private function get_file_path($key, $group) $index = $hash_group.'-'.$hash_key; - if ($this->cf()->is_dcfalse('CHUNKCACHEDIR')) { + if ($this->cf()->is_dcfalse('CHUNKCACHEDIR', true)) { return $this->cache_path.$index.'.php'; } @@ -904,7 +1021,7 @@ private function get_file_path($key, $group) */ private function skip_stats($group, $key = '') { - if ($this->is_non_persistent_groups($group)) { + if ($this->is_non_persistent_groups($group) || (!empty($keys) && $this->is_non_persistent_keys($key))) { return true; } @@ -914,7 +1031,7 @@ private function skip_stats($group, $key = '') /** * is_data_uptodate. */ - private function is_data_uptodate($key, $group, $data, $data_serialized = null) + private function is_data_uptodate($key, $group, $data) { $file = $this->get_file_path($key, $group); $data_p = $this->fs()->cache_get($file); @@ -923,24 +1040,47 @@ private function is_data_uptodate($key, $group, $data, $data_serialized = null) } $data_p = $data_p['data']; - $data_p_type = \gettype($data_p); - $data_type = \gettype($data); - $doserialize = 'array' === $data_type || 'object' === $data_type; - if ($data_p_type !== $data_type) { - return false; + if (\function_exists('nwdcx_arraysimilar') && nwdcx_arraysimilar($data_p, $data)) { + nwdcx_debuglog(__FUNCTION__.': '.$this->get_item_hash($file).' No data changes.'); + + return true; } - if (!$doserialize && ((false !== strpos($data_type, 'string') && 0 === strcmp($data_p, $data)) || $data_p === $data)) { + return false; + } + + /** + * has_stalecache. + */ + private function has_stalecache($key, $group = '') + { + if ('wc_' === substr($key, 0, 3) && '_cache_prefix' === substr($key, -13)) { return true; } - // @note 2122: use md5, serialize can be large. - if ($doserialize) { - $data_ps = !empty($data_serialized) ? $data_serialized : @serialize($data_p); - if (@md5($data_serialized) === @md5(@serialize($data))) { - return true; - } + if ('wc_cache_' === substr($key, 0, 9) || 'wc_cache_' === substr($group, 0, 9)) { + return true; + } + + if (false !== strpos($key, ':') && @preg_match('@^([a-zA-Z0-9\._-]+):([0-9a-f]{32}):([0-9\. ]+)$@', $key)) { + return true; + } + + if (false !== strpos($group, 'docketcache-post-') && preg_match('@^docketcache-post-\d+$@', $group)) { + return true; + } + + return false; + } + + /** + * is_stalecache_ignored. + */ + private function is_stalecache_ignored($key, $group = '') + { + if ($this->ignore_stalecache) { + return $this->has_stalecache($key, $group = ''); } return false; @@ -1090,15 +1230,21 @@ public function dc_remove_group($group) $group = implode(',', $group); } + $this->fs()->suspend_cache_write(true); + $max_execution_time = $this->fs()->get_max_execution_time(180); + $slowdown = 0; foreach ($this->fs()->scanfiles($this->cache_path, null, $pattern) as $object) { if ($object->isFile()) { $fx = $object->getPathName(); $fn = $object->getFileName(); $this->fs()->unlink($fx, true); - $this->dc_log('flush', $this->get_item_hash($fx), $group.':*'); ++$total; - unset($this->cache[$group]); + + array_map(function ($grp) use ($fx) { + unset($this->cache[$grp]); + $this->dc_log('flush', $this->get_item_hash($fx), $grp.':*'); + }, explode(',', $group)); } if ($slowdown > 10) { @@ -1108,11 +1254,13 @@ public function dc_remove_group($group) ++$slowdown; - if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) { + if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) { break; } } + $this->fs()->suspend_cache_write(false); + return $total; } @@ -1126,6 +1274,9 @@ public function dc_remove_group_match($group) return $total; } + $this->fs()->suspend_cache_write(true); + $max_execution_time = $this->fs()->get_max_execution_time(180); + $slowdown = 0; $pattern = '@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@'; foreach ($this->fs()->scanfiles($this->cache_path, null, $pattern) as $object) { @@ -1165,50 +1316,14 @@ public function dc_remove_group_match($group) ++$slowdown; - if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) { + if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) { break; } } - return $total; - } + $this->fs()->suspend_cache_write(false); - /** - * dc_stalecache_filter. - */ - private function dc_stalecache_filter($key, $group) - { - if ('wc_' === substr($key, 0, 3) && '_cache_prefix' === substr($key, -13)) { - // get previous usec - $usec = $this->get('wc_'.$group.'_cache_prefix', $group); - if ($usec) { - $val = 'wc_cache:'.$group.':'.$usec; - $this->stalecache_list[md5($val)] = $val; - } - } elseif ('last_changed' === $key) { - // get previous usec - $usec = $this->get('last_changed', $group); - if ($usec) { - $val = 'last_changed:'.$group.':'.$usec; - $this->stalecache_list[md5($val)] = $val; - } - } - // can't capture by last_changed. - // we compare key prefix and timestamp. - elseif (false !== strpos($key, ':') && @preg_match('@(.*):([a-z0-9]{32}):([0-9\. ]+)$@', $key, $mm)) { - $val = 'after:'.$group.':'.$mm[3].':'.$mm[1]; - $this->stalecache_list[md5($val)] = $val; - } - } - - /** - * advcpost_stalecache_se. - */ - public function add_stalecache($lists) - { - if ($this->is_stalecache && !empty($lists) && \is_array($lists)) { - $this->stalecache_list = array_merge($this->stalecache_list, $lists); - } + return $total; } /** @@ -1238,9 +1353,9 @@ private function dc_get($key, $group, $is_raw = false) } // incase gc not run - if (!$is_timeout && !empty($this->cache_maxttl) && !empty($data['timestamp']) && $this->fs()->valid_timestamp($data['timestamp'])) { + if (!$is_timeout && !empty($data['timestamp']) && $this->fs()->valid_timestamp($data['timestamp'])) { $maxttl = time() - $this->cache_maxttl; - if ($data['timestamp'] < $maxttl) { + if ($maxttl > $data['timestamp']) { $this->dc_log('exp', $logkey, $group.':'.$key); $this->fs()->unlink($file, true); // true = delete it instead of truncate } @@ -1280,7 +1395,18 @@ private function dc_code($file, $arr) $logkey = $this->get_item_hash($file); $logpref = __FUNCTION__.'():'; - $data = $this->fs()->export_var($arr, $error); + // use (object) instead of VarExporter. + if (\PHP_VERSION_ID >= 70433 && \in_array($arr['type'], ['object', 'array'])) { + $arr_data = var_export($arr, true); + if (!empty($arr_data) && false != strpos($arr_data, '(object) array(') && false === strpos($arr_data, '::__set_state')) { + $data = $arr_data; + } + } + + if (!isset($data)) { + $data = $this->fs()->export_var($arr, $error); + } + if (false === $data) { $this->dc_log('err', $logkey, $logpref.' Failed to export var -> '.$error); @@ -1319,13 +1445,11 @@ private function dc_save($cache_key, $data, $group = 'default', $expire = 0, $ke $logpref = __FUNCTION__.'():'; // skip save to disk, return true; - if ('' === $data && $this->fs()->is_transient($group)) { - if ($this->is_dev) { - $this->dc_log('debug', $logkey, $group.':'.$cache_key.' '.$logpref.' Data empty'); - } + /*if ('' === $data && $this->fs()->is_transient($group)) { + nwdcx_debuglog(__FUNCTION__.': '.$logkey.': Process aborted. No data availale.'); return true; - } + }*/ if (!$this->fs()->mkdir_p($this->cache_path)) { return false; @@ -1335,8 +1459,16 @@ private function dc_save($cache_key, $data, $group = 'default', $expire = 0, $ke $file = $this->get_file_path($cache_key, $group); + // skip save to disk, return true; + if (('' === $data || (\is_array($data) && empty($data))) && ($this->fs()->is_transient($group) || $this->ignore_emptycache)) { + nwdcx_debuglog(__FUNCTION__.': '.$logkey.': Process aborted. No data availale.'); + $this->fs()->unlink($file, false); + + return true; + } + // chunk dir - if ($this->cf()->is_dctrue('CHUNKCACHEDIR') && !$this->fs()->mkdir_p(\dirname($file))) { + if ($this->cf()->is_dctrue('CHUNKCACHEDIR', true) && !$this->fs()->mkdir_p(\dirname($file))) { return false; } @@ -1348,6 +1480,7 @@ private function dc_save($cache_key, $data, $group = 'default', $expire = 0, $ke $data = ''; } + // unserialize content if (!empty($data)) { if ('string' === $type) { $data = nwdcx_unserialize($data); @@ -1362,43 +1495,37 @@ private function dc_save($cache_key, $data, $group = 'default', $expire = 0, $ke } // abort if object too large - $data_serialized = serialize($data); - $len = \strlen(serialize($data_serialized)); + $len = \strlen(serialize($data)); if ($len >= $this->cache_maxsize) { $this->dc_log('err', $logkey, $group.':'.$cache_key.' '.$logpref.' Object too large -> '.$len.'/'.$this->cache_maxsize); + nwdcx_debuglog(__FUNCTION__.': '.$logkey.': Process aborted. Object too large ('.$len.'/'.$this->cache_maxsize.')'); + return false; } // since timeout set to timestamp. - if (0 === $expire && !empty($key) && @is_file($file) && $this->is_data_uptodate($key, $group, $data, $data_serialized)) { - if ($this->is_dev) { - $this->dc_log('debug', $logkey, $group.':'.$cache_key.' '.$logpref.' No changes'); - } - + /*clearstatcache(true, $file); + if (0 === $expire && !empty($key) && @is_file($file) && $this->is_data_uptodate($key, $group, $data)) { return false; - } + }*/ $meta = []; $meta['timestamp'] = time(); if ($this->multisite) { - // try to avoid error-prone - // in rare condition, get_current_network_id dependencies not load properly. - try { - $meta['network_id'] = get_current_network_id(); - } catch (\Throwable $e) { - $meta['network_id'] = 0; - } + $meta['network_id'] = $this->network_id; } $final_type = \gettype($data); if ('string' === $final_type && nwdcx_serialized($data)) { $final_type = 'string_serialize'; - } elseif ('array' === $final_type) { + } elseif ('array' === $final_type && (!\defined('DOCKET_CACHE_USE_CLASSMAP') || !DOCKET_CACHE_USE_CLASSMAP)) { // may lead to __PHP_Incomplete_Class // headers => Requests_Utility_CaseInsensitiveDictionary Object - if (!empty($data['headers']) && \is_object($data['headers']) && false !== strpos(var_export($data['headers'], 1), 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) { + // headers => Requests_Utility_CaseInsensitiveDictionary Object + // if (!empty($data['headers']) && \is_object($data['headers']) && false !== strpos(var_export($data['headers'], 1), 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) { + if (false !== strpos(var_export($data, 1), 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) { $data = @serialize($data); if (nwdcx_serialized($data)) { $final_type = 'array_serialize'; @@ -1415,11 +1542,21 @@ private function dc_save($cache_key, $data, $group = 'default', $expire = 0, $ke // and maxttl constants. $meta['timeout'] = $timeout; + // final data $meta['data'] = $data; + // only count new file. + clearstatcache(true, $file); + $has_cache_file = is_file($file); if (true === $this->dc_code($file, $meta)) { - if ($this->is_dev) { - $this->dc_log('debug', $logkey, $group.':'.$cache_key.' '.$logpref.' Storing to disk'); + nwdcx_debuglog(__FUNCTION__.': '.$this->get_item_hash($file).': Storing to disk.'); + + if (!$has_cache_file && $this->maxfile_livecheck) { + $count_file = (int) $this->get('count_file', 'docketcache-gc'); + if ($this->maxfile > $count_file) { + ++$count_file; + $this->set('count_file', $count_file, 'docketcache-gc', 86400); // 1d + } } return true; @@ -1484,7 +1621,7 @@ private function dc_precache_load($hash) foreach ($keys as $cache_group => $arr) { foreach ($arr as $cache_key) { - if ($cnt_max >= $this->precache_maxlist) { + if ($cnt_max >= $this->precache_maxkey) { break 2; } @@ -1520,30 +1657,37 @@ private function dc_precache_load($hash) */ private function dc_precache_set($hash) { + $group = 'docketcache-precache'; + $file = $this->get_file_path($hash, $group); + if (empty($this->precache) || !\is_array($this->precache)) { + $this->fs()->unlink($file, true); + return; } - $group = 'docketcache-precache'; + $file_hash = $this->get_item_hash($file); $data = []; $slowdown = 0; $cnt_max = 0; - $logkey = $this->item_hash('docketcache-precache').'-'.$this->item_hash(__FUNCTION__); - $logpref = __FUNCTION__.'():'; + nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process started.'); - if ($this->is_dev) { - $this->dc_log('debug', $logkey, $logpref.' Precache Set: Start'); + // docketcache-precache-gc + $count_file = (int) $this->get('count_file', $group.'-gc'); + if ($count_file >= $this->precache_maxfile) { + nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum file limit ('.$count_file.'/'.$this->precache_maxfile.')'); + + return; } foreach ($this->precache as $cache_group => $cache_keys) { - if ($cnt_max >= $this->precache_maxlist) { + if ($cnt_max >= $this->precache_maxkey) { break; } if ($cache_group !== $group) { - $cache_keys = array_keys($cache_keys); - $data[$cache_group] = $cache_keys; + $data[$cache_group] = array_keys($cache_keys); } ++$cnt_max; @@ -1556,36 +1700,51 @@ private function dc_precache_set($hash) ++$slowdown; if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) { - // bypass, maybe data too big - $data = []; - $this->delete($hash); + nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum execution time.'); break; } } - if ($this->is_dev) { - $this->dc_log('debug', $logkey, $logpref.' Precache Set: End -> '.\count($data)); - } - if (!empty($data)) { - if (!empty($this->precache_loaded) && md5(serialize($this->precache_loaded[$hash])) === md5(serialize($data))) { - if ($this->is_dev) { - $this->dc_log('debug', $logkey, $logpref.' '.$hash.' No changes'); - } + nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Total items = '.\count($data, 1)); + + if (!empty($this->precache_loaded) && \function_exists('nwdcx_arraysimilar') && nwdcx_arraysimilar($this->precache_loaded[$hash], $data)) { + nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. No data changes.'); return; } - $this->set($hash, $data, $group, 86400); // 1d + // docketcache-precache-gc + if ($this->precache_maxfile > $count_file) { + clearstatcache(true, $file); + + // only count new file. + $has_precache_file = is_file($file); + + if ($this->set($hash, $data, $group, 86400)) { // 1d + nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. Storing cache to disk.'); + + if (!$has_precache_file) { + ++$count_file; + $this->set('count_file', $count_file, $group.'-gc', 86400); // 1d + } + + return; + } + } + nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum file limit ('.$count_file.'/'.$this->precache_maxfile.')'); + + return; } + nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. No data available.'); unset($data, $hash); } /** * dc_precache. */ - private function dc_precache() + private function dc_precache_init() { if (!empty($_POST) || empty($_SERVER['REQUEST_URI']) || $this->cf()->is_dctrue('WPCLI')) { return; @@ -1640,7 +1799,8 @@ private function dc_precache() return; } - $this->precache_hashkey = $this->item_hash($req_host.$req_uri); + // $this->precache_hashkey = $this->item_hash($req_host.$req_uri); + $this->precache_hashkey = md5($req_host.$req_uri); $this->dc_precache_load($this->precache_hashkey); } @@ -1653,18 +1813,14 @@ private function dc_precache() */ public function dc_close() { - $this->fs()->close_buffer(); + // $this->fs()->close_buffer(); static $is_done = false; if (!$is_done) { - if ($this->is_precache && !empty($this->precache_hashkey) && $this->fs()->close_buffer()) { + if ($this->is_precache && !empty($this->precache_hashkey) && !$this->fs()->suspend_cache_write() /* && $this->fs()->close_buffer() */) { $this->dc_precache_set($this->precache_hashkey); } - if ($this->is_stalecache && !empty($this->stalecache_list)) { - $this->add('items', $this->stalecache_list, 'docketcache-stalecache', 3600); - } - $is_done = true; } } @@ -1695,11 +1851,11 @@ private function dc_init() } if ($this->cf()->is_dcarray('IGNORED_GROUPS', $dcvalue)) { - $this->non_persistent_groups = $dcvalue; + $this->add_non_persistent_groups($dcvalue); } if ($this->cf()->is_dcarray('IGNORED_KEYS', $dcvalue)) { - $this->non_persistent_keys = $dcvalue; + $this->add_non_persistent_keys($dcvalue); } if ($this->cf()->is_dcarray('FILTERED_GROUPS', $dcvalue)) { @@ -1775,7 +1931,6 @@ function ($option) { add_action( 'shutdown', function () { - $this->fs()->close_buffer(); $this->delete('alloptions', 'options'); }, \PHP_INT_MAX - 1 @@ -1792,21 +1947,16 @@ function () { add_action( $prefix.'_plugin', function ($plugin, $network) { - if ($this->multisite) { - add_action( - 'shutdown', - function () { - $this->fs()->close_buffer(); - $this->delete(get_current_network_id().':active_sitewide_plugins', 'site-options'); - }, - \PHP_INT_MAX - 1 - ); - } add_action( 'shutdown', function () { - $this->fs()->close_buffer(); + if ($this->multisite) { + $this->delete($this->network_id.':active_sitewide_plugins', 'site-options'); + $this->delete($this->network_id.':auto_update_plugins', 'site-options'); + } + $this->delete('uninstall_plugins', 'options'); + $this->delete('auto_update_plugins', 'options'); }, \PHP_INT_MAX - 1 ); @@ -1916,29 +2066,52 @@ function () { function () { if ($this->add_signature && !$this->is_user_logged_in()) { echo apply_filters('docketcache/filter/signature/htmlfooter', "\n\n"); - $this->fs()->close_buffer(); + // $this->fs()->close_buffer(); } }, \PHP_INT_MAX ); } - // stalecache - $this->is_stalecache = $this->cf()->is_dctrue('FLUSH_STALECACHE'); + // bypass stalecache + $this->ignore_stalecache = $this->cf()->is_dctrue('STALECACHE_IGNORE', true); + + // bypass emptyache + $this->ignore_emptycache = $this->cf()->is_dctrue('EMPTYCACHE_IGNORE', true); + + // maxfile check + // true = count file at dc_save, false = will handle by GC. + $this->maxfile_livecheck = $this->cf()->is_dctrue('MAXFILE_LIVECHECK', true); + + // maxfile + $this->maxfile = (int) $this->fs()->sanitize_maxfile($this->cf()->dcvalue('MAXFILE', true)); + $count_file = (int) $this->get('count_file', 'docketcache-gc'); + if ($count_file >= $this->maxfile) { + $this->fs()->suspend_cache_write(true); + } // load precache - $this->is_precache = $this->cf()->is_dctrue('PRECACHE'); + $this->is_precache = $this->cf()->is_dctrue('PRECACHE', true); if ($this->is_precache) { - $this->precache_maxlist = (int) $this->cf()->dcvalue('PRECACHE_MAXLIST'); - $this->dc_precache(); - } + if ($this->cf()->is_dcint('PRECACHE_MAXGROUP', $dcvalue)) { + if (!empty($dcvalue)) { + $this->precache_maxgroup = $dcvalue; + } + } - // maxfile - $maxfile = (int) $this->fs()->sanitize_maxfile($this->cf()->dcvalue('MAXFILE')); - $numfile = (int) $this->get('numfile', 'docketcache-gc'); - $numfile = $numfile > 0 ? $numfile : 0; - if ($numfile > $maxfile) { - wp_suspend_cache_addition(true); + if ($this->cf()->is_dcint('PRECACHE_MAXKEY', $dcvalue)) { + if (!empty($dcvalue)) { + $this->precache_maxkey = $dcvalue; + } + } + + if ($this->cf()->is_dcint('PRECACHE_MAXFILE', $dcvalue)) { + if (!empty($dcvalue)) { + $this->precache_maxfile = $this->fs()->sanitize_precache_maxfile($dcvalue); + } + } + + $this->dc_precache_init(); } } } @@ -2134,6 +2307,15 @@ function wp_cache_add_non_persistent_groups($groups) $wp_object_cache->add_non_persistent_groups($groups); } +/** + * @see WP_Object_Cache::add_non_persistent_keys() + */ +function wp_cache_add_non_persistent_keys($keys) +{ + global $wp_object_cache; + $wp_object_cache->add_non_persistent_keys($keys); +} + /** * @see WP_Object_Cache::switch_to_blog() */ @@ -2182,13 +2364,3 @@ function wp_cache_flush_group_match($group = 'default') return $wp_object_cache->dc_remove_group_match($group); } - -/** - * @see WP_Object_Cache::add_stalecache() - */ -function wp_cache_add_stalecache($lists) -{ - global $wp_object_cache; - - return $wp_object_cache->add_stalecache($lists); -} diff --git a/includes/compat.php b/includes/compat.php index d2460b0..17879e2 100644 --- a/includes/compat.php +++ b/includes/compat.php @@ -31,9 +31,42 @@ function nwdcx_arraymap($func, $arr) } } +if (!\function_exists('nwdcx_arraysimilar')) { + function nwdcx_arraysimilar($actual, $expected) + { + if (!\is_array($expected) || !\is_array($actual)) { + return $actual === $expected; + } + foreach ($expected as $key => $value) { + if (!isset($actual[$key]) || !isset($expected[$key])) { + return false; + } + + if (!nwdcx_arraysimilar($actual[$key], $expected[$key])) { + return false; + } + } + foreach ($actual as $key => $value) { + if (!isset($actual[$key]) || !isset($expected[$key])) { + return false; + } + if (!nwdcx_arraysimilar($actual[$key], $expected[$key])) { + return false; + } + } + + return true; + } +} + if (!\function_exists('nwdcx_serialized')) { function nwdcx_serialized($data) { + // short-circuit the checking. + if (!\is_string($data) || false === strpbrk($data, '{:;}')) { + return false; + } + if (!\function_exists('is_serialized')) { // 16072021: rare opcache issue with some hosting ABSPATH not defined. if (\defined('ABSPATH') && \defined('WPINC')) { @@ -56,23 +89,27 @@ function nwdcx_unserialize($data) return $data; } - $ok = true; - - // if the string has object format, check it if has stdClass, - // other than that set it as false and return the original data + // If the string has an object format, check if it has a stdClass, otherwise return the original data. + // The logic here is, if we "unserialize" it, we can't use the cache since the VarExporter can't detect + // the Class instance in objects which may lead to "class not found". if (false !== strpos($data, 'O:') && @preg_match_all('@O:\d+:"([^"]+)"@', $data, $mm)) { if (!empty($mm) && !empty($mm[1])) { foreach ($mm[1] as $v) { if ('stdClass' !== $v) { - $ok = false; - break; + return $data; } } unset($mm); } } - return !$ok ? $data : @unserialize(trim($data)); + // make query-monitor happy. + $nwdcx_suppresserrors = nwdcx_suppresserrors(true); + $data = @unserialize(trim($data)); + + nwdcx_suppresserrors($nwdcx_suppresserrors); + + return $data; } } @@ -154,7 +191,9 @@ function nwdcx_cleanuptransient() $collect = []; - $results = $wpdb->get_results('SELECT `option_id`,`option_name`,`option_value` FROM `'.$wpdb->options.'` WHERE `option_name` LIKE "_transient_%" OR `option_name` LIKE "_site_transient_%" ORDER BY `option_id` ASC LIMIT 1000', ARRAY_A); + // $results = $wpdb->get_results('SELECT `option_id`,`option_name`,`option_value` FROM `'.$wpdb->options.'` WHERE `option_name` LIKE "_transient_%" OR `option_name` LIKE "_site_transient_%" ORDER BY `option_id` ASC LIMIT 1000', ARRAY_A); + $results = $wpdb->get_results('SELECT `option_id`,`option_name`,`option_value` FROM `'.$wpdb->options.'` WHERE `option_name` RLIKE "^(_site)?(_transient)(_timeout)?_.*?" ORDER BY `option_id` ASC LIMIT 5000', ARRAY_A); + if (!empty($results) && \is_array($results)) { while ($row = @array_shift($results)) { $key = @preg_replace('@^(_site)?(_transient)(_timeout)?_@', '', $row['option_name']); @@ -180,7 +219,8 @@ function nwdcx_cleanuptransient() $collect = []; if (is_multisite() && isset($wpdb->sitemeta)) { - $results = $wpdb->get_results('SELECT `meta_id`,`meta_key`,`meta_value` FROM `'.$wpdb->sitemeta.'` WHERE `meta_key` LIKE "_site_transient_%" ORDER BY `meta_id` ASC LIMIT 1000', ARRAY_A); + // $results = $wpdb->get_results('SELECT `meta_id`,`meta_key`,`meta_value` FROM `'.$wpdb->sitemeta.'` WHERE `meta_key` LIKE "_site_transient_%" ORDER BY `meta_id` ASC LIMIT 1000', ARRAY_A); + $results = $wpdb->get_results('SELECT `meta_id`,`meta_key`,`meta_value` FROM `'.$wpdb->sitemeta.'` WHERE `meta_key` RLIKE "^(_site_transient)(_timeout)?_.*?" ORDER BY `meta_id` ASC LIMIT 5000', ARRAY_A); if (!empty($results) && \is_array($results)) { while ($row = @array_shift($results)) { $key = @preg_replace('@^(_site)?(_transient)(_timeout)?_@', '', $row['meta_key']); @@ -233,6 +273,31 @@ function nwdcx_throwable($name, $error) } } +if (!\function_exists('nwdcx_debuglog')) { + function nwdcx_debuglog($text) + { + if (\defined('DOCKET_CACHE_DEV') && DOCKET_CACHE_DEV) { + $logfile = WP_CONTENT_DIR.'/dcdev-debug.log'; + error_log('['.date('Y-m-d H:i:s').'] '.$text."\n", 3, $logfile); + } + } +} + +if (!\function_exists('nwdcx_cliverbose')) { + function nwdcx_cliverbose($text) + { + static $is_verbose = false; + + if (!$is_verbose) { + $is_verbose = 'cli' === \PHP_SAPI && !empty($_SERVER['argv']) && \in_array('--verbose', $_SERVER['argv']) && !\in_array('--quiet', $_SERVER['argv']); + } + + if ($is_verbose) { + fwrite(\STDOUT, $text); + } + } +} + if (!\function_exists('nwdcx_microtimetofloat')) { function nwdcx_microtimetofloat($second) { diff --git a/includes/object-cache.php b/includes/object-cache.php index 48a905f..96efc15 100644 --- a/includes/object-cache.php +++ b/includes/object-cache.php @@ -3,7 +3,7 @@ * @wordpress-plugin * Plugin Name: Docket Cache Drop-in * Plugin URI: https://wordpress.org/plugins/docket-cache/ - * Version: 22.07.02 + * Version: 22.07.03 * Description: A persistent object cache stored as a plain PHP code, accelerates caching with OPcache backend. * Author: Nawawi Jamili * Author URI: https://docketcache.com @@ -31,7 +31,7 @@ } /* - * Bypass if we doing action. + * Bypass if we doing action. It happen on redirection. */ if (!empty($_GET['_wpnonce']) && !empty($_GET['action']) && !empty($_GET['page']) && 'docket-cache' === $_GET['page'] && false === strpos($_GET['action'], 'cronbot') && false === strpos($_GET['action'], 'wpoptaload')) { return; @@ -39,6 +39,7 @@ /* * Bypass if match cache key in $_REQUEST. + * Just for development, it may cause uncertain results. */ if (\defined('DOCKET_CACHE_IGNORE_REQUEST') && \is_array(DOCKET_CACHE_IGNORE_REQUEST) && !empty($_REQUEST)) { if (array_intersect(DOCKET_CACHE_IGNORE_REQUEST, array_keys($_REQUEST))) { diff --git a/includes/src/Becache.php b/includes/src/Becache.php index 8408b7f..7396fa2 100644 --- a/includes/src/Becache.php +++ b/includes/src/Becache.php @@ -111,7 +111,7 @@ private function get_file_path($key, $group) $index = $hash_group.'-'.$hash_key; - if ($this->cf()->is_dcfalse('CHUNKCACHEDIR')) { + if ($this->cf()->is_dcfalse('CHUNKCACHEDIR', true)) { return $this->cache_path.$index.'.php'; } @@ -122,7 +122,18 @@ private function get_file_path($key, $group) private function dump_code($file, $arr) { - $data = $this->fs()->export_var($arr, $error); + // use (object) instead of VarExporter. + if (\PHP_VERSION_ID >= 70433 && \in_array($arr['type'], ['object', 'array'])) { + $arr_data = var_export($arr, true); + if (!empty($arr_data) && false != strpos($arr_data, '(object) array(') && false === strpos($arr_data, '::__set_state')) { + $data = $arr_data; + } + } + + if (!isset($data)) { + $data = $this->fs()->export_var($arr, $error); + } + if (false === $data) { return false; } @@ -143,55 +154,29 @@ private function dump_code($file, $arr) return $stat; } - private function maybe_expire($group, $expire = 0, $key = '') - { - if (empty($expire)) { - $expire = 0; - } - - $expire = $this->fs()->sanitize_timestamp($expire); - $maxttl = $this->cache_maxttl; - - if (0 === $expire && $maxttl < 2419200) { - if ($this->fs()->is_transient($group)) { - if ('site-transient' === $group && \in_array($key, ['update_plugins', 'update_themes', 'update_core', '_woocommerce_helper_updates'])) { - $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d - } elseif ('transient' === $group && 'health-check-site-status-result' === $key) { - $expire = 0; // to check with is_data_uptodate - } else { - $expire = $maxttl < 604800 ? 604800 : $maxttl; // 7d - } - } elseif (\in_array($group, ['terms', 'posts', 'post_meta', 'options', 'site-options', 'comments'])) { - $expire = $maxttl < 1209600 ? 1209600 : $maxttl; // 14d - } - // woocommerce stale cache - // wc_cache_0.72953700 1651592702 - elseif ('wc_cache_' === substr($key, 0, 9) && preg_match('@^wc_cache_([0-9\. ]+)_@', $key)) { - $expire = 86400; // 1d - } - } - - return $expire; - } - private function store_cache($key, $data, $group, $expire = 0) { if (!$this->fs()->mkdir_p($this->cache_path)) { return false; } - $expire = $this->maybe_expire($group, $expire, $key); - $cache_key = $this->cache_key($key, $group); $file = $this->get_file_path($cache_key, $group); // chunk dir - if ($this->cf()->is_dctrue('CHUNKCACHEDIR') && !$this->fs()->mkdir_p(\dirname($file))) { + if ($this->cf()->is_dctrue('CHUNKCACHEDIR', true) && !$this->fs()->mkdir_p(\dirname($file))) { return false; } - $timeout = ($expire > 0 ? time() + $expire : 0); + if ($this->fs()->is_transient($group)) { + // transient timeout already as timestamp in DB. + $timeout = $expire > 0 ? $expire : time() + 3600; + } elseif ('options' === $group) { + // initial set to 1 hour. + $timeout = time() + 3600; + } + $timeout = $timeout > $this->cache_maxttl ? time() + $this->cache_maxttl : $timeout; $type = \gettype($data); if ('NULL' === $type && null === $data) { $data = ''; @@ -227,13 +212,13 @@ private function store_cache($key, $data, $group, $expire = 0) } $final_type = \gettype($data); - if ('array' === $final_type) { - // http remote request - // headers => Requests_Utility_CaseInsensitiveDictionary Object - if (!empty($data['headers']) && \is_object($data['headers']) && false !== strpos(var_export($data['headers'], 1), 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) { + if ('string' === $final_type && nwdcx_serialized($data)) { + $final_type = 'string_serialize'; + } elseif ('array' === $final_type && (!\defined('DOCKET_CACHE_USE_CLASSMAP') || !DOCKET_CACHE_USE_CLASSMAP)) { + if (false !== strpos(var_export($data, 1), 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) { $data = @serialize($data); if (nwdcx_serialized($data)) { - $final_type = 'string'; + $final_type = 'array_serialize'; } } } @@ -261,7 +246,8 @@ public function export_transient() $suppress = $wpdb->suppress_errors(true); $collect = []; - $results = $wpdb->get_results('SELECT `option_id`,`option_name`,`option_value` FROM `'.$wpdb->options.'` WHERE `option_name` LIKE "_transient_%" OR `option_name` LIKE "_site_transient_%" ORDER BY `option_id` ASC LIMIT '.$this->qlimit, ARRAY_A); + // $results = $wpdb->get_results('SELECT `option_id`,`option_name`,`option_value` FROM `'.$wpdb->options.'` WHERE `option_name` LIKE "_transient_%" OR `option_name` LIKE "_site_transient_%" ORDER BY `option_id` ASC LIMIT '.$this->qlimit, ARRAY_A); + $results = $wpdb->get_results('SELECT `option_id`,`option_name`,`option_value` FROM `'.$wpdb->options.'` WHERE `option_name` RLIKE "^(_site)?(_transient)(_timeout)?_.*?" ORDER BY `option_id` ASC LIMIT '.$this->qlimit, ARRAY_A); if (!empty($results) && \is_array($results)) { while ($row = @array_shift($results)) { if ($this->max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $this->max_execution_time) { @@ -286,6 +272,10 @@ public function export_transient() if (false !== strpos($row['option_name'], '_transient_timeout_')) { $collect[$key]['timeout'] = (int) $row['option_value']; + + if (time() > $collect[$key]['timeout']) { + unset($collect[$key]); + } } } @@ -304,7 +294,8 @@ public function export_transient() $collect = []; if ($this->multisite && isset($wpdb->sitemeta)) { - $results = $wpdb->get_results('SELECT `meta_id`,`meta_key`,`meta_value` FROM `'.$wpdb->sitemeta.'` WHERE `meta_key` LIKE "_site_transient_%" ORDER BY `meta_id` ASC LIMIT '.$this->qlimit, ARRAY_A); + // $results = $wpdb->get_results('SELECT `meta_id`,`meta_key`,`meta_value` FROM `'.$wpdb->sitemeta.'` WHERE `meta_key` LIKE "_site_transient_%" ORDER BY `meta_id` ASC LIMIT '.$this->qlimit, ARRAY_A); + $results = $wpdb->get_results('SELECT `meta_id`,`meta_key`,`meta_value` FROM `'.$wpdb->sitemeta.'` WHERE `meta_key` RLIKE "^(_site_transient)(_timeout)?_.*?" ORDER BY `meta_id` ASC LIMIT '.$this->qlimit, ARRAY_A); if (!empty($results) && \is_array($results)) { while ($row = @array_shift($results)) { if ($this->max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $this->max_execution_time) { @@ -317,15 +308,19 @@ public function export_transient() continue; } - $key = @preg_replace('@^(_site)?(_transient)(_timeout)?_@', '', $row['meta_key']); + $key = @preg_replace('@^(_site_transient)(_timeout)?_@', '', $row['meta_key']); if (!isset($collect[$key])) { - $collect[$key] = ['value' => '', 'group' => 'site-transient', 'expire' => 0]; + $collect[$key] = ['value' => '', 'group' => 'site-transient', 'timeout' => 0]; } $collect[$key]['value'] = $row['meta_value']; if (false !== strpos($row['meta_key'], '_site_transient_timeout_')) { $collect[$key]['timeout'] = (int) $row['meta_value']; + + if (time() > $collect[$key]['timeout']) { + unset($collect[$key]); + } } } @@ -361,7 +356,7 @@ public function export_alloptions() if (!empty($alloptions_db) && \is_array($alloptions_db)) { $wp_options = $this->fs()->keys_alloptions(); - $is_filter = $this->cf()->is_dctrue('WPOPTALOAD'); + $is_filter = $this->cf()->is_dctrue('WPOPTALOAD', true); foreach ($alloptions_db as $num => $options) { if ($this->max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $this->max_execution_time) { $alloptions = []; diff --git a/includes/src/Canopt.php b/includes/src/Canopt.php index 89bbad5..0d6cca3 100644 --- a/includes/src/Canopt.php +++ b/includes/src/Canopt.php @@ -93,7 +93,7 @@ public function keys($key = false) 'postmissedschedule' => esc_html__('Post Missed Schedule Tweaks', 'docket-cache'), 'wootweaks' => esc_html__('Misc WooCommerce Tweaks', 'docket-cache'), 'wooadminoff' => esc_html__('Deactivate WooCommerce Admin', 'docket-cache'), - 'woowidgetoff' => esc_html__('Deactivate WooCommerce Widget', 'docket-cache'), + 'woowidgetoff' => esc_html__('Deactivate WooCommerce Classic Widget', 'docket-cache'), 'woowpdashboardoff' => esc_html__('Deactivate WooCommerce WP Dashboard', 'docket-cache'), 'wooextensionpageoff' => esc_html__('Deactivate WooCommerce Extensions Page', 'docket-cache'), 'woocartfragsoff' => esc_html__('Deactivate WooCommerce Cart Fragments', 'docket-cache'), @@ -106,7 +106,8 @@ public function keys($key = false) 'stats' => esc_html__('Object Cache Data Stats', 'docket-cache'), 'gcaction' => esc_html__('Garbage Collector Action Button', 'docket-cache'), 'flushaction' => esc_html__('Additional Flush Cache Action Button', 'docket-cache'), - 'autoupdate' => esc_html__('Docket Cache Auto Update', 'docket-cache'), + /* 'autoupdate' => esc_html__('Docket Cache Auto Update', 'docket-cache'), */ + 'autoupdate_toggle' => esc_html__('Docket Cache Auto Update', 'docket-cache'), 'checkversion' => esc_html__('Critical Version Checking', 'docket-cache'), 'optwpquery' => esc_html__('Optimize WP Query', 'docket-cache'), 'pingback' => esc_html__('Deactivate XML-RPC / Pingbacks', 'docket-cache'), @@ -125,8 +126,11 @@ public function keys($key = false) 'opcshutdown' => esc_html__('Flush OPcache During Deactivation', 'docket-cache'), 'maxsize_disk' => esc_html__('Cache Disk Limit', 'docket-cache'), 'maxfile' => esc_html__('Cache Files Limit', 'docket-cache'), + 'maxfile_livecheck' => esc_html__('Real-time File Limit Checking', 'docket-cache'), 'chunkcachedir' => esc_html__('Chunk Cache Directory', 'docket-cache'), 'flush_stalecache' => esc_html__('Auto Remove Stale Cache', 'docket-cache'), + 'stalecache_ignore' => esc_html__('Exclude Stale Cache', 'docket-cache'), + 'emptycache_ignore' => esc_html__('Exclude Empty Object Data', 'docket-cache'), 'limithttprequest' => esc_html__('Limit WP-Admin HTTP requests', 'docket-cache'), 'httpheadersexpect' => esc_html__('HTTP Request Expect header tweaks', 'docket-cache'), 'rtpostautosave' => esc_html__('Auto Save Interval', 'docket-cache'), @@ -139,6 +143,8 @@ public function keys($key = false) 'rtwpdebugdisplay' => esc_html__('WP Debug Display', 'docket-cache'), 'rtwpdebuglog' => esc_html__('WP Debug Log', 'docket-cache'), 'rtwpcoreupdate' => esc_html__('Disallows WP Auto Update Core', 'docket-cache'), + 'rtconcatenatescripts' => esc_html__('Deactivate Concatenate WP-Admin Scripts', 'docket-cache'), + 'rtdisablewpcron' => esc_html__('Deactivate WP Cron', 'docket-cache'), ]; $data = apply_filters('docketcache/filter/optionkeys', $data); @@ -219,7 +225,7 @@ public function get($name) return false; } - public function save($name, $value) + public function save($name, $value, $do_action = true) { if (!$this->mkdir_p($this->path)) { return false; @@ -241,7 +247,10 @@ public function save($name, $value) } $ret = $this->put_config($config); - do_action('docketcache/action/saveoption', $name, $value, $ret); + + if ($do_action) { + do_action('docketcache/action/saveoption', $name, $value, $ret); + } return $ret; } @@ -284,9 +293,7 @@ public function clear_lock() if (!empty($files) && \is_array($files)) { foreach ($files as $file) { if (@is_file($file) && @is_writable($file)) { - if (\defined('DocketCache_CLI') && DocketCache_CLI) { - @fwrite(\STDOUT, basename($file).\PHP_EOL); - } + nwdcx_cliverbose(basename($file)."\n"); @unlink($file); } } diff --git a/includes/src/Command.php b/includes/src/Command.php index bff084e..60ea490 100644 --- a/includes/src/Command.php +++ b/includes/src/Command.php @@ -24,9 +24,32 @@ class Command extends WP_CLI_Command public function __construct(Plugin $pt) { + if (empty($_SERVER['HTTP_HOST'])) { + $_SERVER['HTTP_HOST'] = wp_parse_url(site_url(), \PHP_URL_HOST); + } + $this->pt = $pt; } + private function print_stdout($text, $nl = true) + { + if (!empty($_SERVER['argv']) && \in_array('--quiet', $_SERVER['argv'])) { + return; + } + fwrite(\STDOUT, $text.($nl ? "\n" : '')); + } + + private function clear_line() + { + $this->print_stdout("\r".str_repeat(' ', 100)."\r", false); + } + + private function halt_warning($warning) + { + WP_CLI::warning($warning); + WP_CLI::halt(2); + } + private function halt_error($error) { WP_CLI::error($error, false); @@ -41,7 +64,7 @@ private function halt_success($success) private function halt_status($text, $status = 0) { - WP_CLI::line($text); + $this->print_stdout($text); WP_CLI::halt($status); } @@ -68,7 +91,7 @@ private function dropino_runtime_status() { $info = (object) $this->pt->get_info(); if (2 === $info->status_code) { - WP_CLI::line($this->title('Cache Status').$this->status_color($info->status_code, $info->status_text)); + $this->print_stdout($this->title('Cache Status').$this->status_color($info->status_code, $info->status_text)); unset($info); WP_CLI::halt(1); } @@ -87,12 +110,14 @@ public function status() $info = (object) $this->pt->get_info(); $halt = $info->status_code ? 0 : 1; - WP_CLI::line($this->title('Cache Status').$this->status_color($info->status_code, $info->status_text)); - WP_CLI::line($this->title('Cache Path').$info->cache_path); + $line = str_repeat('-', 15).':'.str_repeat('-', \strlen($info->cache_path) + 2); + $this->print_stdout($line); + $this->print_stdout($this->title('Cache Status').$this->status_color($info->status_code, $info->status_text)); + $this->print_stdout($this->title('Cache Path').$info->cache_path); if ($this->pt->cf()->is_dctrue('STATS')) { - WP_CLI::line($this->title('Cache Size').$info->cache_size); + $this->print_stdout($this->title('Cache Size').$info->cache_size); } - + $this->print_stdout($line); unset($info); WP_CLI::halt($halt); @@ -190,13 +215,15 @@ public function dropino_update() */ public function flush_cache() { - WP_CLI::line(__('Flushing cache. Please wait..', 'docket-cache')); + $this->print_stdout(__('Flushing cache. Please wait..', 'docket-cache'), false); sleep(1); $is_timeout = false; $total = $this->pt->flush_cache(true, $is_timeout); + $this->clear_line(); + $this->pt->cx()->undelay(); if ($is_timeout) { @@ -242,9 +269,13 @@ public function reset_lock() */ public function reset_cron() { - WP_CLI::line(__('Resetting cron event. Please wait..', 'docket-cache')); + $this->print_stdout(__('Resetting cron event. Please wait..', 'docket-cache'), false); + ( new Event($this->pt) )->reset(); sleep(1); + + $this->clear_line(); + WP_CLI::runcommand('cron event list'); $this->halt_success(__('Cron event has been reset.', 'docket-cache')); } @@ -263,7 +294,7 @@ public function reset_cron() public function runtime_remove() { if (WpConfig::is_bedrock()) { - WP_CLI::line(__('This command does not support Bedrock. Please manually remove the runtime code.', 'docket-cache')); + $this->print_stdout(__('This command does not support Bedrock. Please manually remove the runtime code.', 'docket-cache')); WP_CLI::halt(1); } @@ -287,7 +318,7 @@ public function runtime_remove() public function runtime_install() { if (WpConfig::is_bedrock()) { - WP_CLI::line(__('This command does not support Bedrock. Please manually install the runtime code.', 'docket-cache')); + $this->print_stdout(__('This command does not support Bedrock. Please manually install the runtime code.', 'docket-cache')); WP_CLI::halt(1); } @@ -310,13 +341,15 @@ public function runtime_install() */ public function flush_precache() { - if (!\function_exists('wp_cache_flush_group')) { - $this->halt_error(__('Precache could not be flushed.', 'docket-cache')); + if (!\function_exists('wp_cache_flush_group') || !method_exists('WP_Object_Cache', 'dc_remove_group')) { + $this->halt_error(__('Object Precache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache')); } - WP_CLI::line(__('Flushing precache. Please wait..', 'docket-cache')); + $this->print_stdout(__('Flushing precache. Please wait..', 'docket-cache'), false); sleep(1); - $total = wp_cache_flush_group('docketcache-precache'); + + $total = wp_cache_flush_group(['docketcache-precache', 'docketcache-precache-gc']); + $this->clear_line(); /* translators: %d = count */ $this->halt_success(sprintf(__('The precache was flushed. Total cache flushed: %d', 'docket-cache'), $total)); @@ -335,14 +368,15 @@ public function flush_precache() */ public function flush_transient() { - if (!\function_exists('wp_cache_flush_group')) { - $this->halt_error(__('Transient could not be flushed.', 'docket-cache')); + if (!\function_exists('wp_cache_flush_group') || !method_exists('WP_Object_Cache', 'dc_remove_group')) { + $this->halt_error(__('Transient could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache')); } - WP_CLI::line(__('Flushing transient. Please wait..', 'docket-cache')); + $this->print_stdout(__('Flushing transient. Please wait..', 'docket-cache'), false); sleep(1); $total = wp_cache_flush_group(['transient', 'site-transient']); + $this->clear_line(); /* translators: %d = couint */ $this->halt_success(sprintf(__('The transient was flushed. Total cache flushed: %d', 'docket-cache'), $total)); @@ -361,14 +395,16 @@ public function flush_transient() */ public function flush_advcpost() { - if (!\function_exists('wp_cache_flush_group_match')) { - $this->halt_error(__('Advanced Post Cache could not be flushed.', 'docket-cache')); + if (!\function_exists('wp_cache_flush_group_match') || !method_exists('WP_Object_Cache', 'dc_remove_group')) { + $this->halt_error(__('Advanced Post Cache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache')); } - WP_CLI::line(__('Flushing Advanced Post Cache. Please wait..', 'docket-cache')); + $this->print_stdout(__('Flushing Advanced Post Cache. Please wait..', 'docket-cache'), false); sleep(1); $total = wp_cache_flush_group_match('docketcache-post'); + $this->clear_line(); + /* translators: %d = count */ $this->halt_success(sprintf(__('The Advanced Post Cache was flushed. Total cache flushed: %d', 'docket-cache'), $total)); } @@ -386,14 +422,16 @@ public function flush_advcpost() */ public function flush_menucache() { - if (!\function_exists('wp_cache_flush_group')) { - $this->halt_error(__('Menu Cache could not be flushed.', 'docket-cache')); + if (!\function_exists('wp_cache_flush_group') || !method_exists('WP_Object_Cache', 'dc_remove_group')) { + $this->halt_error(__('Menu Cache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache')); } - WP_CLI::line(__('Flushing Menu Cache. Please wait..', 'docket-cache')); + $this->print_stdout(__('Flushing Menu Cache. Please wait..', 'docket-cache'), false); sleep(1); $total = wp_cache_flush_group('docketcache-menu'); + $this->clear_line(); + /* translators: %d = count */ $this->halt_success(sprintf(__('The Menu Cache was flushed. Total cache flushed: %d', 'docket-cache'), $total)); } @@ -411,14 +449,16 @@ public function flush_menucache() */ public function flush_mocache() { - if (!\function_exists('wp_cache_flush_group')) { - $this->halt_error(__('Translation Cache could not be flushed.', 'docket-cache')); + if (!\function_exists('wp_cache_flush_group') || !method_exists('WP_Object_Cache', 'dc_remove_group')) { + $this->halt_error(__('Translation Cache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache')); } - WP_CLI::line(__('Flushing Translation Cache. Please wait..', 'docket-cache')); + $this->print_stdout(__('Flushing Translation Cache. Please wait..', 'docket-cache'), false); sleep(1); $total = wp_cache_flush_group('docketcache-mo'); + $this->clear_line(); + /* translators: %d = count */ $this->halt_success(sprintf(__('The Translation Cache was flushed. Total cache flushed: %d', 'docket-cache'), $total)); } @@ -436,9 +476,13 @@ public function flush_mocache() */ public function run_cron() { - WP_CLI::line(__('Executing the cron event. Please wait..', 'docket-cache')); + $this->print_stdout(__('Executing the cron event. Please wait..', 'docket-cache'), false); sleep(1); + + $this->clear_line(); + WP_CLI::runcommand('cron event run --all'); + WP_CLI::runcommand('cron event list'); } /** @@ -454,16 +498,51 @@ public function run_cron() */ public function run_stats() { - WP_CLI::line(__('Executing the cache stats. Please wait..', 'docket-cache')); + $this->print_stdout(__('Executing the cache stats. Please wait..', 'docket-cache'), false); sleep(1); + $pad = 15; $stats = $this->pt->get_cache_stats(true); - WP_CLI::line($this->title(__('Object size', 'docket-cache'), $pad).$this->pt->normalize_size($stats->size)); - WP_CLI::line($this->title(__('File size', 'docket-cache'), $pad).$this->pt->normalize_size($stats->filesize)); - WP_CLI::line($this->title(__('Total file', 'docket-cache'), $pad).$stats->files); + + $this->clear_line(); + + $padr = 10; + if (\strlen($stats->files) > 10) { + $padr = \strlen($stats->files); + } + + $line = str_repeat('-', $pad).':'.str_repeat('-', $padr); + $this->print_stdout($line); + $this->print_stdout($this->title(__('Object size', 'docket-cache'), $pad).$this->pt->normalize_size($stats->size)); + $this->print_stdout($this->title(__('File size', 'docket-cache'), $pad).$this->pt->normalize_size($stats->filesize)); + $this->print_stdout($this->title(__('Total file', 'docket-cache'), $pad).$stats->files); + $this->print_stdout($line); $this->halt_success(__('Executing the cache stats completed.', 'docket-cache')); } + /** + * Runs the Docket Cache Optimizedb. + * + * Optimize DB. + * + * ## EXAMPLES + * + * wp cache run:optimizedb + * + * @subcommand run:stats + */ + public function run_optimizedb() + { + $this->print_stdout(__('Executing the optimizedb. Please wait..', 'docket-cache'), false); + sleep(1); + + ( new Event($this->pt) )->optimizedb(); + + $this->clear_line(); + + $this->halt_success(__('Executing the optimizedb completed.', 'docket-cache')); + } + /** * Runs the Docket Cache garbage collector (GC). * @@ -477,42 +556,45 @@ public function run_stats() */ public function run_gc() { - if (!has_filter('docketcache/filter/garbagecollector')) { - $this->halt_error(__('Garbage collector not available.', 'docket-cache')); - } - - WP_CLI::line(__('Executing the garbage collector. Please wait..', 'docket-cache')); + $this->print_stdout(__('Executing the garbage collector. Please wait..', 'docket-cache'), false); sleep(1); $pad = 35; - $collect = apply_filters('docketcache/filter/garbagecollector', true); + $collect = ( new Event($this->pt) )->garbage_collector(true); + + $this->clear_line(); + + if ($collect->is_locked) { + $this->halt_warning(__('Process locked. The garbage collector is in process. Try again in a few seconds.', 'docket-cache')); + } - WP_CLI::line(str_repeat('-', $pad).':'.str_repeat('-', 10)); - WP_CLI::line($this->title(__('Cache MaxTTL', 'docket-cache'), $pad).$collect->cache_maxttl); - WP_CLI::line($this->title(__('Cache File Limit', 'docket-cache'), $pad).$collect->cache_maxfile); - WP_CLI::line($this->title(__('Cache Disk Limit', 'docket-cache'), $pad).$this->pt->normalize_size($collect->cache_maxdisk)); - WP_CLI::line(str_repeat('-', $pad).':'.str_repeat('-', 10)); - WP_CLI::line($this->title(__('Cleanup Cache MaxTTL', 'docket-cache'), $pad).$collect->cleanup_maxttl); - WP_CLI::line($this->title(__('Cleanup Cache File Limit', 'docket-cache'), $pad).$collect->cleanup_maxfile); - WP_CLI::line($this->title(__('Cleanup Cache Disk Limit', 'docket-cache'), $pad).$collect->cleanup_maxdisk); + $line = str_repeat('-', $pad).':'.str_repeat('-', 10); + $this->print_stdout($line); + $this->print_stdout($this->title(__('Cache MaxTTL', 'docket-cache'), $pad).$collect->cache_maxttl); + $this->print_stdout($this->title(__('Cache File Limit', 'docket-cache'), $pad).$collect->cache_maxfile); + $this->print_stdout($this->title(__('Cache Disk Limit', 'docket-cache'), $pad).$this->pt->normalize_size($collect->cache_maxdisk)); + $this->print_stdout($line); + $this->print_stdout($this->title(__('Cleanup Cache MaxTTL', 'docket-cache'), $pad).$collect->cleanup_maxttl); + $this->print_stdout($this->title(__('Cleanup Cache File Limit', 'docket-cache'), $pad).$collect->cleanup_maxfile); + $this->print_stdout($this->title(__('Cleanup Cache Disk Limit', 'docket-cache'), $pad).$collect->cleanup_maxdisk); if ($collect->cleanup_expire > 0) { - WP_CLI::line($this->title(__('Cleanup Cache Expire', 'docket-cache'), $pad).$collect->cleanup_expire); + $this->print_stdout($this->title(__('Cleanup Cache Expire', 'docket-cache'), $pad).$collect->cleanup_expire); } if ($this->pt->get_precache_maxfile() > 0 && $collect->cleanup_precache_maxfile > 0) { - WP_CLI::line($this->title(__('Cleanup Precache Limit', 'docket-cache'), $pad).$collect->cleanup_precache_maxfile); + $this->print_stdout($this->title(__('Cleanup Precache Limit', 'docket-cache'), $pad).$collect->cleanup_precache_maxfile); } if ($this->pt->cf()->is_dctrue('FLUSH_STALECACHE') && $collect->cleanup_stalecache > 0) { - WP_CLI::line($this->title(__('Cleanup Stale Cache', 'docket-cache'), $pad).$collect->cleanup_stalecache); + $this->print_stdout($this->title(__('Cleanup Stale Cache', 'docket-cache'), $pad).$collect->cleanup_stalecache); } - WP_CLI::line(str_repeat('-', $pad).':'.str_repeat('-', 10)); - WP_CLI::line($this->title(__('Total Cache Cleanup', 'docket-cache'), $pad).$collect->cache_cleanup); - WP_CLI::line($this->title(__('Total Cache Ignored', 'docket-cache'), $pad).$collect->cache_ignore); - WP_CLI::line($this->title(__('Total Cache File', 'docket-cache'), $pad).$collect->cache_file); - WP_CLI::line(str_repeat('-', $pad).':'.str_repeat('-', 10)); + $this->print_stdout($line); + $this->print_stdout($this->title(__('Total Cache Cleanup', 'docket-cache'), $pad).$collect->cache_cleanup); + $this->print_stdout($this->title(__('Total Cache Ignored', 'docket-cache'), $pad).$collect->cache_ignore); + $this->print_stdout($this->title(__('Total Cache File', 'docket-cache'), $pad).$collect->cache_file); + $this->print_stdout($line); $this->halt_success(__('Executing the garbage collector completed.', 'docket-cache')); } diff --git a/includes/src/Constans.php b/includes/src/Constans.php index 164a2af..8710cc9 100644 --- a/includes/src/Constans.php +++ b/includes/src/Constans.php @@ -149,6 +149,12 @@ public function maybe_define($name, $value, $user_config = true) return @\define($name, $value); } + // mark defined constants + if (empty($GLOBALS['DOCKET_CACHE_RUNTIME'])) { + $GLOBALS['DOCKET_CACHE_RUNTIME'] = []; + } + $GLOBAL['DOCKET_CACHE_RUNTIME'][$this->px($name.'_FALSE')] = 1; + return false; } @@ -165,7 +171,7 @@ public function register_default() // cache dir $this->maybe_define($this->px('PATH'), DOCKET_CACHE_CONTENT_PATH.'/cache/docket-cache/', false); - // cache file max size: 3MB, 1MB = 1048576 bytes (binary) = 1000000 bytes (decimal) + // object max size: 3MB, 1MB = 1048576 bytes (binary) = 1000000 bytes (decimal) // Only numbers between 1000000 and 10485760 are accepted $this->maybe_define($this->px('MAXSIZE'), 3145728); @@ -176,6 +182,9 @@ public function register_default() // cache file max accelerated files: Only numbers between 200 and 200000 are accepted $this->maybe_define($this->px('MAXFILE'), 50000); + // check cache file limit in real-time + $this->maybe_define($this->px('MAXFILE_LIVECHECK'), false); + // cache maxttl: cache lifespan. Only seconds between 86400 and 2419200 are accepted $this->maybe_define($this->px('MAXTTL'), 345600); // 4d @@ -203,19 +212,25 @@ public function register_default() // flush cache when deactivate/uninstall $this->maybe_define($this->px('FLUSH_SHUTDOWN'), true); - // flush wc_cache / advanced post cache stale cache + // flush wc_cache / advanced post cache / wp stale cache $this->maybe_define($this->px('FLUSH_STALECACHE'), false); // split a cache file into smaller directory $this->maybe_define($this->px('CHUNKCACHEDIR'), false); + // ignore stale cache + $this->maybe_define($this->px('STALECACHE_IGNORE'), false); + + // ignore empty cache + $this->maybe_define($this->px('EMPTYCACHE_IGNORE'), false); + // optimize db $this->maybe_define($this->px('CRONOPTMZDB'), 'never'); // option autoload $this->maybe_define($this->px('WPOPTALOAD'), false); - // global cache group + // global cache group for multisite $this->maybe_define( $this->px('GLOBAL_GROUPS'), [ @@ -254,6 +269,10 @@ public function register_default() // @note: dnh_dismissed_notices -> https://github.com/julien731/WP-Dismissible-Notices-Handler $this->maybe_define($this->px('IGNORED_KEYS'), ['dnh_dismissed_notices']); + // @private + // cache ignored group => key, group => [key1, key2] + $this->maybe_define($this->px('IGNORED_GROUPKEY'), []); + // @private // this option private for right now $this->maybe_define( @@ -266,30 +285,36 @@ public function register_default() ] ); - // @private - // cache ignored group:key - $this->maybe_define($this->px('IGNORED_GROUPKEY'), []); - // precache $this->maybe_define($this->px('PRECACHE'), false); - // precache maxfile: < 1, false, null = unlimited - $this->maybe_define($this->px('PRECACHE_MAXFILE'), 1000); + // precache maxfile + $this->maybe_define($this->px('PRECACHE_MAXFILE'), 100); + + // precache max key + $this->maybe_define($this->px('PRECACHE_MAXKEY'), 20); - // precache maxlist - $this->maybe_define($this->px('PRECACHE_MAXLIST'), 1000); + // precache max group + $this->maybe_define($this->px('PRECACHE_MAXGROUP'), 20); // @private // cache ignored precache $this->maybe_define( $this->px('IGNORED_PRECACHE'), [ - 'freemius:fs_accounts', - 'options:uninstall_plugins', - 'options:active_plugins', - 'options:cron', - 'options:litespeed_messages', - 'options:litespeed.admin_display.messages', + 'freemius' => 'fs_accounts', + 'options' => [ + 'uninstall_plugins', + 'auto_update_plugins', + 'active_plugins', + 'cron', + 'litespeed_messages', + 'litespeed.admin_display.messages', + ], + 'site-options' => [ + '1:auto_update_plugins', + '1:active_sitewide_plugins', + ], ] ); @@ -324,7 +349,25 @@ public function register_default() $this->maybe_define($this->px('ADVCPOST'), false); // advanced post cache allow post type - $this->maybe_define($this->px('ADVCPOST_POSTTYPE'), ['post', 'page', 'attachment']); + $this->maybe_define( + $this->px('ADVCPOST_POSTTYPE'), + [ + 'post', + 'page', + 'attachment', + 'revision', + 'nav_menu_item', + 'custom_css', + 'customize_changeset', + 'oembed_cache', + 'user_request', + 'wp_block', + 'wp_template', + 'wp_template_part', + 'wp_global_styles', + 'wp_navigation', + ] + ); // advanced post cache allow all post type $this->maybe_define($this->px('ADVCPOST_POSTTYPE_ALL'), false); @@ -375,8 +418,11 @@ public function register_default() // check version $this->maybe_define($this->px('CHECKVERSION'), false); - // auto update - $this->maybe_define($this->px('AUTOUPDATE'), false); + // / @private: auto update + // 28012023: DOCKET_CACHE_AUTOUPDATE only to force WP auto_update_plugin filter. + // DOCKET_CACHE_AUTOUPDATE_TOGGLE will sync with WP auto_update_plugins option. + // $this->maybe_define($this->px('AUTOUPDATE'), false); + $this->maybe_define($this->px('AUTOUPDATE_TOGGLE'), false); // flush opcache when deactivate $this->maybe_define($this->px('OPCSHUTDOWN'), false); @@ -468,6 +514,12 @@ public function register_default() // @private: deactivate wp auto update core. $this->maybe_define($this->px('RTWPCOREUPDATE'), \defined('WP_AUTO_UPDATE_CORE') && WP_AUTO_UPDATE_CORE ? 'off' : 'on'); + // @private: deactivate concatenate wp-admin scripts. + $this->maybe_define($this->px('RTCONCATENATESCRIPTS'), \defined('CONCATENATE_SCRIPTS') && !(bool) CONCATENATE_SCRIPTS ? 'on' : 'off'); + + // @private: deactivate wp cron. + $this->maybe_define($this->px('RTDISABLEWPCRON'), \defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ? 'on' : 'off'); + // @private // capture fatal error rarely incase non-throwable // set true for debugging only diff --git a/includes/src/Crawler.php b/includes/src/Crawler.php index 3ed5d9c..4120e69 100644 --- a/includes/src/Crawler.php +++ b/includes/src/Crawler.php @@ -14,7 +14,7 @@ final class Crawler { - private static $version = '22.07.02'; + private static $version = '22.07.03'; public static $send_cookie = false; private static function default_args($param = []) diff --git a/includes/src/Event.php b/includes/src/Event.php index cba3c9a..d610f6e 100644 --- a/includes/src/Event.php +++ b/includes/src/Event.php @@ -75,23 +75,25 @@ function ($schedules) { add_action( 'plugins_loaded', function () { - // 19092020: standardize. rename hooks - foreach (['docket_cache_gc', 'docket_cache_optimizedb', 'docket_cache_monitor'] as $hx) { + // 19092020: standardize. rename hooks. + // 19012023: remove docketcache_watchproc. + foreach (['docket_cache_gc', 'docket_cache_optimizedb', 'docket_cache_monitor', 'docketcache_watchproc'] as $hx) { if (false !== wp_get_scheduled_event($hx)) { wp_clear_scheduled_hook($hx); } } - // gc: always enable - add_action('docketcache_gc', [$this, 'garbage_collector']); - if (!wp_next_scheduled('docketcache_gc')) { - wp_schedule_event(time(), 'docketcache_gc_schedule', 'docketcache_gc'); - } - - // monitor: always enable - add_action('docketcache_watchproc', [$this, 'watchproc']); - if (!wp_next_scheduled('docketcache_watchproc')) { - wp_schedule_event(time(), 'hourly', 'docketcache_watchproc'); + // garbage collector + // 27012023: added disable constant + if ($this->pt->cf()->is_dcfalse('GCRON_DISABLED')) { + add_action('docketcache_gc', [$this, 'garbage_collector']); + if (!wp_next_scheduled('docketcache_gc')) { + wp_schedule_event(time(), 'docketcache_gc_schedule', 'docketcache_gc'); + } + } else { + if (wp_get_schedule('docketcache_gc')) { + wp_clear_scheduled_hook('docketcache_gc'); + } } // optimize db @@ -127,7 +129,7 @@ function () { } // check version - if ($this->pt->cf()->is_dctrue('CHECKVERSION')) { + if ($this->pt->cf()->is_dctrue('CHECKVERSION', true)) { // 06102020: reset old schedule $check = wp_get_scheduled_event('docketcache_checkversion'); if (\is_object($check) && 'docketcache_checkversion_schedule' !== $check->schedule) { @@ -145,6 +147,11 @@ function () { wp_clear_scheduled_hook('docketcache_checkversion'); } } + + // expired transient in DB + if (has_action('delete_expired_transients') && wp_using_ext_object_cache()) { + add_action('delete_expired_transients', [$this, 'delete_expired_transients_db']); + } } ); } @@ -160,7 +167,7 @@ public function unregister() } /** - * reset,. + * reset. */ public function reset() { @@ -168,27 +175,6 @@ public function reset() $this->register(); } - /** - * monitor. - */ - public function watchproc() - { - if ($this->pt->co()->lockproc('watchproc', time() + 3600)) { - return false; - } - - if (!$this->is_optimizedb) { - $this->delete_expired_transients_db(); - } - - // $this->clear_unknown_cron(); - - $this->pt->get_cache_stats(true); - $this->pt->co()->lockreset('watchproc'); - - return true; - } - /** * garbage_collector. */ @@ -196,29 +182,29 @@ public function garbage_collector($force = false) { static $is_done = false; - $maxfileo = (int) $this->pt->get_cache_maxfile(); - $maxfile = $maxfileo; + $maxfile_default = (int) $this->pt->get_cache_maxfile(); + $maxfile = $maxfile_default; - if ($maxfileo > 10000) { - $maxfile = $maxfileo - 1000; + if ($maxfile_default > 10000) { + $maxfile = $maxfile_default - 1000; } - $maxfileo_pre = (int) $this->pt->get_precache_maxfile(); - $maxfile_pre = $maxfileo_pre; + $maxfile_precache_default = (int) $this->pt->get_precache_maxfile(); + $maxfile_precache = $maxfile_precache_default; - if ($maxfileo_pre > 10000) { - $maxfile_pre = $maxfileo_pre - 1000; + if ($maxfile_precache_default > 10000) { + $maxfile_precache = $maxfile_precache_default - 1000; } - $maxttl0 = (int) $this->pt->get_cache_maxttl(); - $maxttl = $maxttl0; + $maxttl_default = (int) $this->pt->get_cache_maxttl(); + $maxttl = $maxttl_default; if (!empty($maxttl)) { $maxttl = time() - $maxttl; } $chkmaxdisk = false; - $maxsizedisk0 = (int) $this->pt->get_cache_maxsize_disk(); - $maxsizedisk = $maxsizedisk0; + $maxsizedisk_default = (int) $this->pt->get_cache_maxsize_disk(); + $maxsizedisk = $maxsizedisk_default; if (!empty($maxsizedisk)) { $maxsizedisk = $maxsizedisk - 1048576; @@ -228,9 +214,10 @@ public function garbage_collector($force = false) } $collect = (object) [ - 'cache_maxttl' => $maxttl0, - 'cache_maxfile' => $maxfileo, - 'cache_maxdisk' => $maxsizedisk0, + 'is_locked' => false, + 'cache_maxttl' => $maxttl_default, + 'cache_maxfile' => $maxfile_default, + 'cache_maxdisk' => $maxsizedisk_default, 'cleanup_maxfile' => 0, 'cleanup_precache_maxfile' => 0, 'cleanup_maxttl' => 0, @@ -245,29 +232,63 @@ public function garbage_collector($force = false) clearstatcache(); if (!$this->pt->is_docketcachedir($this->pt->cache_path) || @is_file(DOCKET_CACHE_CONTENT_PATH.'/.object-cache-flush.txt')) { + $collect->is_locked = true; + return $collect; } - if ($is_done || $this->pt->co()->lockproc('garbage_collector', time() + $this->max_execution_time + 10)) { + // try to set max execution time to 3 minutes if not 0 or lower than 180 seconds. + $max_execution_time = $this->pt->get_max_execution_time(180); + + // lock process. + $lock_expiry = $max_execution_time > 0 ? $max_execution_time : 180; + $lock_expiry = time() + $lock_expiry; + if ($is_done || $this->pt->co()->lockproc('garbage_collector', $lock_expiry)) { + $collect->is_locked = true; + return $collect; } - $stalecache_list = []; - if ($this->pt->cf()->is_dctrue('FLUSH_STALECACHE')) { - $stalecache_list = wp_cache_get('items', 'docketcache-stalecache'); - wp_cache_delete('items', 'docketcache-stalecache'); + // Stalecache + $is_flush_stalecache = $this->pt->cf()->is_dctrue('FLUSH_STALECACHE', true); + $is_ignore_stalecache = $this->pt->cf()->is_dctrue('STALECACHE_IGNORE', true); + + $wp_cache_last_changed = []; + $wp_cache_last_changed_match = [ + 'posts' => 'wp_query', + 'terms' => 'get_terms', + 'comment' => 'get_comments', + 'comment_feed' => 'comment_feed', + 'sites' => 'get_sites', + 'networks' => 'get_network_ids', + ]; + foreach ($wp_cache_last_changed_match as $grp => $kk) { + $wp_cache_last_changed[$grp] = wp_cache_get_last_changed($grp); } + $wp_cache_last_changed['advpost'] = wp_cache_get('cache_incr', 'docketcache-post'); + $wc_has_cache_helper = method_exists('WC_Cache_Helper', 'get_cache_prefix'); + $wc_session_cache_group = \defined('WC_SESSION_CACHE_GROUP') ? WC_SESSION_CACHE_GROUP : 'wc_session_id'; + $delay = $force ? 650 : 5000; - wp_suspend_cache_addition(true); + if ('cli' === \PHP_SAPI) { + $delay = 100; + } + + // hold cache write + $this->pt->suspend_cache_write(true); - $fsizetotal = 0; - $fcnt = 0; - $pcnt = 0; + $filesize_total = 0; + $file_cache_count = 0; + $file_precache_count = 0; + $bytes_total = 0; $slowdown = 0; + $gcisrun_lock = $this->pt->cache_path.'/.gc-is-run.txt'; + $this->pt->touch($gcisrun_lock); + foreach ($this->pt->scanfiles($this->pt->cache_path) as $object) { - if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) { + if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) { break; } @@ -296,9 +317,7 @@ public function garbage_collector($force = false) continue; } - if ($this->pt->cf()->is_dctrue('DEV') && 'cli' === \PHP_SAPI) { - echo 'run-gc: '.$fx."\n"; - } + nwdcx_cliverbose('run-gc: '.$fx."\n"); if ($fm >= $ft && (0 === $fs || 'dump_' === substr($fn, 0, 5))) { $this->pt->unlink($fx, true); @@ -309,11 +328,24 @@ public function garbage_collector($force = false) continue; } + // 03022023: timeout 0 was set to maxtll, see WP_Object_Cache::maybe_expire + // cleanup first to reduce memory usage + if ($maxttl > 0 && $maxttl > $ft) { + $this->pt->unlink($fx, true); + + if ($force && @is_file($fx)) { + ++$collect->cleanup_failed; + } + + ++$collect->cleanup_maxttl; + continue; + } + // 032e9f2c5b60- = docketcache-precache- - if ($maxfile_pre > 0 && '032e9f2c5b60-' === substr($fn, 0, 13)) { - ++$pcnt; + if ($maxfile_precache > 0 && '032e9f2c5b60-' === substr($fn, 0, 13)) { + ++$file_precache_count; - if ($pcnt > $maxfile_pre) { + if ($file_precache_count > $maxfile_precache) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { @@ -325,10 +357,7 @@ public function garbage_collector($force = false) } } - if ($fcnt >= $maxfile) { - // trigger WP_Object_Cache - wp_cache_set('numfile', $fcnt, 'docketcache-gc', 60); - + if ($file_cache_count >= $maxfile) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { @@ -339,8 +368,7 @@ public function garbage_collector($force = false) continue; } - $fsizetotal += $fs; - if ($chkmaxdisk && $fsizetotal > $maxsizedisk) { + if ($chkmaxdisk && $filesize_total > $maxsizedisk) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { @@ -382,131 +410,190 @@ public function garbage_collector($force = false) continue; } } - } - // no timeout data or 0 - if (false === $is_timeout && $maxttl > 0 && $maxttl > $ft) { - $this->pt->unlink($fx, true); + if ($is_flush_stalecache) { + // wp stale cache + if (!empty($wp_cache_last_changed_match[$data['group']]) && preg_match('@^(wp_query|get_terms|get_comments|comment_feed|get_sites|get_network_ids|get_page_by_path):([0-9a-f]{32}):([0-9\. ]{21})([0-9\. ]+)?$@', $data['key'], $mm)) { + if ('get_page_by_path' === $mm[1]) { + $mm[1] = 'wp_query'; + } - if ($force && @is_file($fx)) { - ++$collect->cleanup_failed; - } + $km = $wp_cache_last_changed_match[$data['group']]; - ++$collect->cleanup_maxttl; - continue; - } + if (($km === $mm[1] && $wp_cache_last_changed[$data['group']] !== $mm[3]) || $is_ignore_stalecache) { + if (@unlink($fx)) { + clearstatcache(true, $fx); - // stalecache - if ((!empty($stalecache_list) && \is_array($stalecache_list)) && !empty($data) && !empty($data['key']) && !empty($data['group']) && 'docketcache-stalecache' !== $data['group']) { - $collect->cleanup_stalecache += $this->flush_stalecache($fx, $data, $stalecache_list); - if ($collect->cleanup_stalecache > 0) { - continue; - } - } - unset($data); + nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); - ++$fcnt; - ++$collect->cache_file; - } // foreach1 + ++$collect->cleanup_stalecache; + continue; + } + } + } - $collect->cache_cleanup = $collect->cleanup_maxttl + $collect->cleanup_expire + $collect->cleanup_maxfile + $collect->cleanup_maxdisk + $collect->cleanup_precache_maxfile + $collect->cleanup_stalecache; + // advpost stale cache + if (false !== strpos($data['group'], 'docketcache-post-') && preg_match('@^docketcache-post-(\d+)$@', $data['group'], $mm)) { + if ((int) $wp_cache_last_changed['advpost'] !== (int) $mm[1] || $is_ignore_stalecache) { + if (@unlink($fx)) { + clearstatcache(true, $fx); - wp_suspend_cache_addition(false); + nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); - $this->pt->co()->lockreset('garbage_collector'); - $this->pt->cx()->delay_expire(); + ++$collect->cleanup_stalecache; + continue; + } + } + } - $is_done = true; + // wc stale cache + if (false !== strpos($data['key'], 'wc_cache_') && preg_match('@^(wc_cache_[0-9\. ]+_)@', $data['key'], $mm)) { + if (!$wc_has_cache_helper || $is_ignore_stalecache) { + if (@unlink($fx)) { + clearstatcache(true, $fx); - // reset - wp_cache_delete('numfile', 'docketcache-gc'); + nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); - return $collect; - } + ++$collect->cleanup_stalecache; + } + continue; + } - /** - * flush_stalecache. - */ - public function flush_stalecache($file, $data, $stalecache_list) - { - $total = 0; + $current_prefix = $mm[1]; + static $cache_prefix_cached = []; - if (!is_file($file) || empty($data) || empty($data['key']) || empty($data['group']) || empty($stalecache_list) || !\is_array($stalecache_list)) { - return $total; - } + // wc product + if ('products' === $data['group'] && preg_match('@.*?_type_(\d+)$@', $data['key'], $nn)) { + $grp = 'product_'.$nn[1]; - $slowdown = 0; - foreach ($stalecache_list as $id => $key) { - $do_flush = false; + if (!empty($cache_prefix_cached[$grp])) { + $cache_prefix = $cache_prefix_cached; + } else { + $cache_prefix = \WC_Cache_Helper::get_cache_prefix($grp); + $cache_prefix_cached[$grp] = $cache_prefix; + } - if (false !== strpos($data['key'], 'wc_cache_') && 'wc_cache:' === substr($key, 0, 9) && preg_match('@^wc_cache_([0-9\. ]+)_.*@', $data['key'], $mm)) { - list($prefix, $group, $usec) = explode(':', $key); - if ($usec === $mm[1]) { - $do_flush = true; - } else { - $usec1 = nwdcx_microtimetofloat($usec); - $usec2 = nwdcx_microtimetofloat($mm[1]); - if ($usec1 > $usec2) { - $do_flush = true; - } - } - } - // group = from cache file, key = list key - elseif (false !== strpos($data['group'], 'docketcache-post-') && false !== strpos($key, 'docketcache-post-')) { - if ($key === $data['group']) { - $do_flush = true; - } else { - $usec1 = str_replace('docketcache-post-', '', $key); - $usec2 = str_replace('docketcache-post-', '', $data['group']); - if ($usec1 > $usec2) { - $do_flush = true; - } - } - } elseif (false !== strpos($key, 'last_changed:') && @preg_match('@(.*):([a-z0-9]{32}):([0-9\. ]+)$@', $data['key'], $mm)) { - list($prefix, $group, $usec) = explode(':', $key); - if ($group === $data['group']) { - $usec1 = nwdcx_microtimetofloat($usec); - $usec2 = nwdcx_microtimetofloat($mm[3]); - if ($usec1 > $usec2) { - $do_flush = true; + if ($cache_prefix !== $current_prefix) { + if (@unlink($fx)) { + clearstatcache(true, $fx); + + nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); + + ++$collect->cleanup_stalecache; + continue; + } + } + } + + if (\in_array($data['group'], + [ + 'products', 'coupons', 'orders', 'webhooks', 'taxes', 'shipping_zones', + 'woocommerce-attributes', 'store_api_rate_limit', $wc_session_cache_group, + ])) { + $grp = $data['group']; + + if (!empty($cache_prefix_cached[$grp])) { + $cache_prefix = $cache_prefix_cached; + } else { + $cache_prefix = \WC_Cache_Helper::get_cache_prefix($grp); + $cache_prefix_cached[$grp] = $cache_prefix; + } + + if ($cache_prefix !== $current_prefix) { + if (@unlink($fx)) { + clearstatcache(true, $fx); + + nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); + + ++$collect->cleanup_stalecache; + continue; + } + } + } + + if ('wc_rate_limit' === $data['group'] && 'rate_limit' === substr($data['key'], 0, 10)) { + $grp = $data['group']; + + if (!empty($cache_prefix_cached[$grp])) { + $cache_prefix = $cache_prefix_cached; + } else { + $cache_prefix = \WC_Cache_Helper::get_cache_prefix($grp); + $cache_prefix_cached[$grp] = $cache_prefix; + } + + if ($cache_prefix !== $current_prefix) { + if (@unlink($fx)) { + clearstatcache(true, $fx); + + nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); + + ++$collect->cleanup_stalecache; + continue; + } + } + } } } - } elseif (false !== strpos($key, 'after:') && @preg_match('@(.*):([a-z0-9]{32}):([0-9\. ]+)$@', $data['key'], $mm)) { - list($prefix, $group, $usec, $abc) = explode(':', $key); - if ($group === $data['group'] && $abc === $mm[1]) { - $usec1 = nwdcx_microtimetofloat($usec); - $usec2 = nwdcx_microtimetofloat($mm[3]); - if ($usec1 > $usec2) { - $do_flush = true; - } + $bytes_total += \strlen(serialize($data)); + } // data + + // no timeout data or 0 + // 03022023: timeout 0 was set to maxtll, see WP_Object_Cache::maybe_expire + /*if (false === $is_timeout && $maxttl > 0 && $maxttl > $ft) { + $this->pt->unlink($fx, true); + + if ($force && @is_file($fx)) { + ++$collect->cleanup_failed; } - } - if ($do_flush) { - $nwdcx_suppresserrors = nwdcx_suppresserrors(true); - // use native unlink since it is a junk file. - if (@unlink($file)) { - unset($stalecache_list[$id]); + ++$collect->cleanup_maxttl; + continue; + }*/ - if ($this->pt->cf()->is_dctrue('DEV') && 'cli' === \PHP_SAPI) { - echo 'run-gc:stale-cache: '.$file."\n"; - } + unset($data); - ++$total; - } - nwdcx_suppresserrors($nwdcx_suppresserrors); - break; // found and break foreach - } + $filesize_total += $fs; + ++$file_cache_count; + } // foreach1 - if ($slowdown > 10) { - $slowdown = 0; - usleep(5000); - } + @unlink($gcisrun_lock); - ++$slowdown; + $collect->cache_file = $file_cache_count; + + $collect->cache_cleanup = $collect->cleanup_maxttl + $collect->cleanup_expire + $collect->cleanup_maxfile + $collect->cleanup_maxdisk + $collect->cleanup_precache_maxfile + $collect->cleanup_stalecache; + + // release + $this->pt->suspend_cache_write(false); + + if (\function_exists('nwdcx_cleanuptransient')) { + nwdcx_cliverbose("run-gc: cleanup expired transients in DB\n"); + nwdcx_cleanuptransient(); } - return $total; + // reset gc + $count_file = $collect->cache_file; + $count_file = $count_file < 0 ? 0 : $count_file; + wp_cache_set('count_file', $count_file, 'docketcache-gc', 86400); + + // reset precache + $count_file = $file_precache_count - $collect->cleanup_precache_maxfile; + $count_file = $count_file < 0 ? 0 : $count_file; + wp_cache_set('count_file', $count_file, 'docketcache-precache-gc', 86400); + + // stats + $this->pt->co()->save_part([ + 'timestamp' => time(), + 'size' => $bytes_total, + 'filesize' => $filesize_total, + 'files' => $collect->cache_file, + ], 'cachestats'); + + // done + $this->pt->co()->lockreset('garbage_collector'); + $this->pt->cx()->delay_expire(); + $is_done = true; + + return $collect; } /** @@ -525,21 +612,20 @@ public function optimizedb() $suppress = $wpdb->suppress_errors(true); @set_time_limit(300); + $max_execution_time = $this->pt->get_max_execution_time(); $this->delete_expired_transients_db(); if (is_main_site() && is_main_network()) { $dbname = $wpdb->dbname; $tables = $wpdb->get_results('SHOW TABLES FROM '.$dbname, ARRAY_A); if (!empty($tables) && \is_array($tables)) { - $max_execution_time = $this->max_execution_time; - if ($max_execution_time < 300) { - $max_execution_time = 300; - } foreach ($tables as $table) { $tbl = $table['Tables_in_'.$dbname]; - $wpdb->query('OPTIMIZE TABLE `'.$tbl.'`'); + $sql = 'OPTIMIZE TABLE `'.$tbl.'`'; + $ret = $wpdb->query($sql); - if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) { + nwdcx_cliverbose(str_replace('`', '', $sql)."\n"); + if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) { break; } } @@ -549,6 +635,8 @@ public function optimizedb() $wpdb->suppress_errors($suppress); + $this->pt->co()->lockreset('optimizedb'); + return true; } @@ -557,10 +645,6 @@ public function optimizedb() */ public function delete_expired_transients_db() { - if (!wp_using_ext_object_cache()) { - return false; - } - if (!nwdcx_wpdb($wpdb)) { return false; } @@ -575,33 +659,8 @@ public function delete_expired_transients_db() } /** - * clear_unknown_cron. + * checkversion. */ - public function clear_unknown_cron() - { - // let's wp handles it. - return; - - if (!wp_using_ext_object_cache()) { - return; - } - - if (!\function_exists('_get_cron_array')) { - return; - } - $crons = _get_cron_array(); - if (!empty($crons) && \is_array($crons)) { - foreach ($crons as $time => $cron) { - foreach ($cron as $hook => $dings) { - if (!has_action($hook)) { - wp_clear_scheduled_hook($hook); - } - } - } - } - unset($crons); - } - public function checkversion() { if (!is_main_site()) { @@ -688,6 +747,7 @@ public function checkversion() } $this->pt->co()->save_part($output, $part); + $this->pt->co()->lockreset($part); return true; } diff --git a/includes/src/Filesystem.php b/includes/src/Filesystem.php index ffc30cd..16de391 100644 --- a/includes/src/Filesystem.php +++ b/includes/src/Filesystem.php @@ -125,9 +125,9 @@ public function is_docketcachefile($file) /** * is_docketcachegroup. */ - public function is_docketcachegroup($group) + public function is_docketcachegroup($group, $key = '') { - return 'docketcache' === substr($group, 0, 11); + return 'docketcache' === substr($group, 0, 11) || !empty($key) && 'docketcache' === substr($key, 0, 11); } /** @@ -157,9 +157,16 @@ public function is_dirempty($dir) /** * get_max_execution_time. */ - public function get_max_execution_time() + public function get_max_execution_time($second = 0) { $max_execution_time = (int) \ini_get('max_execution_time'); + + $second = (int) $second; + if ($second > 0 && $max_execution_time > 0 && $second > $max_execution_time) { + set_time_limit($second); + $max_execution_time = (int) \ini_get('max_execution_time'); + } + if ($max_execution_time > 10) { --$max_execution_time; } @@ -418,9 +425,12 @@ public function shutdown_cleanup($file, $seq = 10) add_action( 'shutdown', function () use ($file) { + $nwdcx_suppresserrors = nwdcx_suppresserrors(true); + clearstatcache(true, $file); if (@is_file($file)) { @unlink($file); } + nwdcx_suppresserrors($nwdcx_suppresserrors); }, $seq ); @@ -431,6 +441,8 @@ function () use ($file) { */ public function unlink($file, $is_delete = false, $is_block = false) { + clearstatcache(true, $file); + // skip if not exist if (!@is_file($file)) { return true; @@ -457,16 +469,18 @@ public function unlink($file, $is_delete = false, $is_block = false) $ok = true; } - clearstatcache(); - // cleanup if ftruncate() failed if (false === $ok) { + clearstatcache(true, $file); if (@is_file($file) && !@unlink($file)) { // try cleanup at shutdown $this->shutdown_cleanup($file); } } + // reset all + clearstatcache(true); + // always true return true; } @@ -492,7 +506,6 @@ public function put($file, $data, $flag = 'cb', $is_block = false) } } @fclose($handle); - clearstatcache(); if (false === $ok) { $this->unlink($file, true); @@ -500,6 +513,8 @@ public function put($file, $data, $flag = 'cb', $is_block = false) return -1; } + clearstatcache(true); + $this->opcache_flush($file); $this->chmod($file); @@ -520,10 +535,13 @@ public function dump($file, $data, $is_validate = false) // truncate reason $this->opcache_flush($file); + // make query-monitor happy. + $nwdcx_suppresserrors = nwdcx_suppresserrors(true); + $ok = $this->put($tmpfile, $data, 'cb', true); if (true === $ok) { try { - clearstatcache(); + clearstatcache(true, $tmpfile); if (@is_file($tmpfile) && @rename($tmpfile, $file)) { if (isset($nwdcx_suppresserrors)) { nwdcx_suppresserrors($nwdcx_suppresserrors); @@ -549,10 +567,15 @@ public function dump($file, $data, $is_validate = false) } // cleanup if not bool true + clearstatcache(true, $tmpfile); if (@is_file($tmpfile)) { @unlink($tmpfile); } + nwdcx_suppresserrors($nwdcx_suppresserrors); + + clearstatcache(true); + // maybe -1, >= 1, false: return from put() return $ok; } @@ -777,7 +800,10 @@ public function opcache_filecache_reset() if (!$is_done && !empty($fcdata)) { $dir = $fcdata['file_cache']; $cnt = 0; - $max_execution_time = $this->get_max_execution_time(); + + $this->suspend_cache_write(true); + + $max_execution_time = $this->get_max_execution_time(180); $slowdown = 0; foreach ($this->opcache_filecache_scanfiles($dir) as $object) { try { @@ -804,8 +830,12 @@ public function opcache_filecache_reset() } } + clearstatcache(true); + $is_done = true; + $this->suspend_cache_write(false); + return $cnt; } @@ -979,12 +1009,15 @@ public function cachedir_flush($dir, $cleanup = false, &$is_timeout = false) return true; } - wp_suspend_cache_addition(true); + $this->suspend_cache_write(true); + + $gcisrun_lock = $dir.'/.gc-is-run.txt'; - $max_execution_time = $this->get_max_execution_time(); + $max_execution_time = $this->get_max_execution_time(180); $flush_lock = DOCKET_CACHE_CONTENT_PATH.'/.object-cache-flush.txt'; - if ($this->put($flush_lock, time())) { - $this->touch($flush_lock, time() + $max_execution_time); + $flush_lock_expiry = time() + $max_execution_time + 10; + if ($this->put($flush_lock, $flush_lock_expiry)) { + $this->touch($flush_lock, $flush_lock_expiry); } $slowdown = 0; @@ -1006,6 +1039,7 @@ public function cachedir_flush($dir, $cleanup = false, &$is_timeout = false) } ++$slowdown; + if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) { $is_timeout = true; break; @@ -1020,11 +1054,13 @@ public function cachedir_flush($dir, $cleanup = false, &$is_timeout = false) $this->unlink($dir.'/index.html', true); } + $this->unlink($gcisrun_lock, true); + if (@is_file($flush_lock)) { @unlink($flush_lock); } - wp_suspend_cache_addition(false); + $this->suspend_cache_write(false); $is_done = true; return $cnt; @@ -1042,14 +1078,16 @@ public function cache_size($dir) $filestotal = 0; clearstatcache(); - if (!$is_done && $this->is_docketcachedir($dir) && !@is_file(DOCKET_CACHE_CONTENT_PATH.'/.object-cache-flush.txt')) { + $gcisrun_lock = $dir.'/.gc-is-run.txt'; + + if (!$is_done && $this->is_docketcachedir($dir) && !@is_file(DOCKET_CACHE_CONTENT_PATH.'/.object-cache-flush.txt') && !@is_file($gcisrun_lock)) { // hardmax $maxfile = 999000; // 1000000 - 1000; $cnt = 0; $slowdown = 0; ignore_user_abort(true); - $max_execution_time = $this->get_max_execution_time(); + $max_execution_time = $this->get_max_execution_time(180); $pattern = '@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@'; foreach ($this->scanfiles($dir, null, $pattern) as $object) { @@ -1094,12 +1132,16 @@ public function cache_size($dir) if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) { break; } + + if (@is_file($gcisrun_lock)) { + break; + } } } $is_done = true; - wp_cache_set('numfile', $filestotal, 'docketcache-gc', 60); + wp_cache_set('count_file', $filestotal, 'docketcache-gc', 86400); return [ 'timestamp' => time(), @@ -1192,6 +1234,20 @@ function () { ); } + /** + * suspend_cache_write. + */ + public function suspend_cache_write($suspend = null) + { + static $_suspend = false; + + if (\is_bool($suspend)) { + $_suspend = $suspend; + } + + return $_suspend; + } + /** * cache_get. */ @@ -1260,6 +1316,16 @@ public function code_stub($data = '') { $is_debug = \defined('WP_DEBUG') && WP_DEBUG; $is_data = !empty($data); + + $class_map = [ + 'Requests_Utility_CaseInsensitiveDictionary' => 'Requests/Utility/CaseInsensitiveDictionary.php', + 'Requests_Response' => ' Requests/Response.php', + 'Requests_Response_Headers' => 'Requests/Response/Headers.php', + 'Requests_Cookie_Jar' => 'Requests/Cookie/Jar.php', + 'WP_HTTP_Requests_Response' => 'class-wp-http-requests-response.php', + 'WP_Post' => 'class-wp-post.php', + ]; + $ucode = ''; if ($is_data && false !== strpos($data, 'Registry::p(')) { if (@preg_match_all('@Registry::p\(\'([a-zA-Z_]+)\'\)@', $data, $mm)) { @@ -1267,13 +1333,26 @@ public function code_stub($data = '') $cls = $mm[1]; foreach ($cls as $clsname) { if ('stdClass' !== $clsname) { - if ($is_debug) { - $reflector = new \ReflectionClass($clsname); - $clsfname = $reflector->getFileName(); - if (false !== $clsfname) { - $ucode .= '/* f: '.str_replace(ABSPATH, '', $clsfname).' */'.\PHP_EOL; + if (\defined('DOCKET_CACHE_USE_CLASSMAP') && DOCKET_CACHE_USE_CLASSMAP) { + $clspath = ''; + if (\defined('DOCKET_CACHE_USE_REFLECTIONCLASS') && DOCKET_CACHE_USE_REFLECTIONCLASS) { + $reflector = new \ReflectionClass($clsname); + $clsfname = $reflector->getFileName(); + if (false !== $clsfname) { + $clspath = str_replace(ABSPATH, '', $clsfname); + } + } + + if (empty($clspath) && !empty($class_map[$clsname])) { + $clspath = WPINC.'/'.$class_map[$clsname]; + } + + if (!empty($clspath)) { + $clsfname = $clspath; + $ucode .= "if ( !@class_exists('".$clsname."', false) && @file_exists(ABSPATH.'".$clsfname."') ) { @include_once(ABSPATH.'".$clsfname."'); }".\PHP_EOL; } } + $ucode .= "if ( !@class_exists('".$clsname."', false) ) { return false; }".\PHP_EOL; } } @@ -1284,8 +1363,10 @@ public function code_stub($data = '') } $code = 'sanitize_maxfile($maxfile); + return $this->sanitize_maxfile($maxfile, $default); } /** @@ -1460,6 +1542,7 @@ public function valid_timestamp($timestamp) return $timestamp > 0; } + // put here because use in Becache. public function keys_alloptions() { // reference: wp-admin/includes/schema.php diff --git a/includes/src/MoCache.php b/includes/src/MoCache.php index 0b9edb6..b07bfbe 100644 --- a/includes/src/MoCache.php +++ b/includes/src/MoCache.php @@ -12,6 +12,7 @@ \defined('ABSPATH') || exit; +#[AllowDynamicProperties] final class MoCache { private $domain = null; diff --git a/includes/src/Plugin.php b/includes/src/Plugin.php index 6a8a8b6..49efeb2 100644 --- a/includes/src/Plugin.php +++ b/includes/src/Plugin.php @@ -546,7 +546,7 @@ public function get_opcache_status($is_raw = false) */ public function get_cache_maxfile() { - $maxfile = $this->cf()->dcvalue('MAXFILE'); + $maxfile = $this->cf()->dcvalue('MAXFILE', true); return $this->sanitize_maxfile($maxfile); } @@ -580,7 +580,7 @@ public function get_precache_maxfile() return 0; } - $maxfile = $this->cf()->dcvalue('PRECACHE_MAXFILE'); + $maxfile = $this->cf()->dcvalue('PRECACHE_MAXFILE', true); return $this->sanitize_precache_maxfile($maxfile); } @@ -680,7 +680,11 @@ public function flush_cache($cleanup = false, &$is_timeout = false) $this->co()->clear_part('cachestats'); $this->cx()->delay(); - delete_expired_transients(true); + if (\function_exists('nwdcx_cleanuptransient')) { + nwdcx_cleanuptransient(); + } else { + delete_expired_transients(true); + } $cnt = $this->cachedir_flush($this->cache_path, $cleanup, $is_timeout); if (false === $cnt) { @@ -689,6 +693,8 @@ public function flush_cache($cleanup = false, &$is_timeout = false) return $cnt; } + $this->co()->clear_lock(); + return $cnt; } @@ -971,6 +977,8 @@ private function deactivate_cleanup($is_uninstall = false) if ($this->cf()->is_dctrue('OPCSHUTDOWN', true)) { $this->opcache_cleanup(); } + + $this->co()->clear_lock(); } /** @@ -1073,7 +1081,7 @@ private function plugin_upgrade() add_action( 'shutdown', function () { - $this->close_buffer(); + // $this->close_buffer(); if ($this->cf()->is_dcfalse('OBJECTCACHEOFF', true)) { $this->cx()->install(true); @@ -1089,6 +1097,18 @@ function () { ); } + private function toggle_auto_update($toggle = false) + { + $auto_updates = (array) get_site_option('auto_update_plugins', []); + if (true === $toggle) { + $auto_updates[] = $this->hook; + + return update_site_option('auto_update_plugins', array_unique($auto_updates)); + } + + return update_site_option('auto_update_plugins', array_diff($auto_updates, [$this->hook])); + } + /** * register_plugin_hooks. */ @@ -1184,15 +1204,16 @@ function () { @header('Cache-Control: no-cache, no-store, must-revalidate, max-age=0'); @header('Content-Type: text/plain; charset=UTF-8'); if (@is_file($file) && @is_readable($file)) { - @readfile($file); - $this->close_exit(); + if (@readfile($file) > 0) { + $this->close_exit(); + } } - $this->close_exit(__('No data available', 'docket-cache')); + $this->close_exit(__('The log is empty. No data is available.', 'docket-cache')); } } - if (\defined('WP_DEBUG') && WP_DEBUG && \defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) { + if ($this->cf()->is_true('WP_DEBUG') && $this->cf()->is_true('WP_DEBUG_LOG')) { $req = $_SERVER['REQUEST_URI']; if ((false !== strpos($req, '?page=docket-cache&idx=config&wplog=0') || false !== strpos($req, '?page=docket-cache-config&idx=config&wplog=0')) && preg_match('@config\&wplog=\d+(\&dd=\d+)?$@', $req)) { @@ -1209,11 +1230,12 @@ function () { $this->close_exit($log); } - @readfile($file); - $this->close_exit(); + if (@readfile($file) > 0) { + $this->close_exit(); + } } - $this->close_exit(__('No data available', 'docket-cache')); + $this->close_exit(__('The log is empty. No data is available.', 'docket-cache')); } } } @@ -1235,8 +1257,8 @@ function () { if (empty($ok)) { $wpdb->query('SET SESSION SQL_BIG_SELECTS=1'); } else { - // already big select, lock 24h - $locktime = time() + 86400; + // already big select, lock 28d + $locktime = time() + 2419200; $this->co()->setlock('sqlbigselect', $locktime); } @@ -1246,11 +1268,67 @@ function () { \PHP_INT_MIN ); + add_action( + 'plugins_loaded', + function () { + $dc_option = $this->co()->get('DOCKET_CACHE_AUTOUPDATE'); + if (!empty($dc_option)) { + $cache_hash_group = substr(md5('options'), 0, 12); + $cache_hash_key = substr(md5('auto_update_plugins'), 0, 12); + if (is_multisite()) { + $cache_hash_group = substr(md5('site-options'), 0, 12); + $cache_hash_key = substr(md5(get_current_network_id().':auto_update_plugins'), 0, 12); + } + + $file_cache = $this->cache_path.$cache_hash_group.'-'.$cache_hash_key.'.php'; + if (is_file($file_cache)) { + @unlink($file_cache); + } + + clearstatcache(); + + if ('enable' === $dc_option) { + $this->co()->save('autoupdate_toggle', 'enable'); + } elseif ('disable' === $dc_option) { + $this->co()->save('autoupdate_toggle', 'disable'); + } + + $this->co()->save('AUTOUPDATE', false); + } + }, + \PHP_INT_MAX + ); + + add_action( + 'update_site_option', + function ($option, $auto_updates, $old_auto_updates, $network_id) { + if ('auto_update_plugins' === $option) { + if (\in_array($this->hook, (array) $auto_updates, true) && !\in_array($this->hook, (array) $old_auto_updates, true)) { + $this->co()->save('autoupdate_toggle', 'enable', false); + + return; + } + + if (\in_array($this->hook, (array) $old_auto_updates, true) && !\in_array($this->hook, (array) $auto_updates, true)) { + $this->co()->save('autoupdate_toggle', 'disable', false); + + return; + } + + return; + } + }, + \PHP_INT_MAX, + 4 + ); + add_filter( 'auto_update_plugin', function ($update, $item) { if ('docket-cache' === $item->slug) { - return $this->cf()->is_dctrue('AUTOUPDATE'); + if (\defined('DOCKET_CACHE_AUTOUPDATE') && \is_bool(DOCKET_CACHE_AUTOUPDATE)) { + return DOCKET_CACHE_AUTOUPDATE; + } } return $update; @@ -1624,14 +1702,10 @@ function ($hook) { function () { add_action( 'shutdown', - function () { - $this->close_buffer(); - $user = wp_get_current_user(); - if (\is_object($user) && isset($user->ID)) { - wp_cache_delete($user->ID, 'user_meta'); - $this->delete_cron_siteid($user->ID); - $this->delete_current_select_siteid($user->ID); - } + function ($user_id) { + wp_cache_delete($user_id, 'user_meta'); + $this->delete_cron_siteid($user_id); + $this->delete_current_select_siteid($user_id); }, \PHP_INT_MAX ); @@ -1765,7 +1839,7 @@ function ($plugin_meta, $plugin_file) { 2 ); - add_filter( + /*add_filter( 'plugin_auto_update_setting_html', function ($html, $plugin_file, $plugin_data) { if ($plugin_file === $this->hook) { @@ -1776,7 +1850,8 @@ function ($html, $plugin_file, $plugin_data) { }, 10, 3 - ); + );*/ + // reference: Canopt::save() add_action( 'docketcache/action/saveoption', @@ -1794,10 +1869,10 @@ function ($name, $value, $status = true) { add_action( 'shutdown', function () { - $this->close_buffer(); + // $this->close_buffer(); wp_cache_delete('alloptions', 'options'); - if (\function_exists('wp_cache_flush_group')) { + if (\function_exists('wp_cache_flush_group') && method_exists('WP_Object_Cache', 'dc_remove_group')) { wp_cache_flush_group('options'); wp_cache_flush_group('docketcache-precache'); } @@ -1815,7 +1890,7 @@ function () { 'shutdown', function () use ($action) { if (!$action) { - $this->close_buffer(); + // $this->close_buffer(); apply_filters('docketcache/filter/active/cronbot', $action); } @@ -1828,10 +1903,20 @@ function () use ($action) { $error_log = \ini_get('error_log'); if ('off' === $value && @is_file($error_log) && @is_writable($error_log)) { @unlink($error_log); + clearstatcache(); } break; case 'menucache': - wp_cache_flush_group('docketcache-menu'); + if (\function_exists('wp_cache_flush_group') && method_exists('WP_Object_Cache', 'dc_remove_group')) { + wp_cache_flush_group('docketcache-menu'); + } + break; + case 'autoupdate_toggle': + if ('enable' === $value) { + $this->toggle_auto_update(true); + } else { + $this->toggle_auto_update(false); + } break; } @@ -1853,8 +1938,8 @@ function () { // lock opcache reset $this->co()->setlock('preload_lock_opcache_reset', time() + 20); - // warmup: see after_delay - if ($this->cf()->is_dcfalse('PRELOAD')) { + // warmup: see Dropino::after_delay + if ($this->cf()->is_dcfalse('PRELOAD', true)) { add_action( 'shutdown', function () { @@ -1960,6 +2045,11 @@ function () { add_action( 'docketcache/action/countcachesize', function () { + // gc is running + if ($this->co()->locked('garbage_collector')) { + return; + } + if ($this->co()->lockproc('doing_countcachesize', time() + $this->get_max_execution_time())) { return; } @@ -2100,7 +2190,7 @@ function () use ($tweaks) { if ($this->co()->lockproc('post_missed_schedule', time() + 180)) { return false; } - $this->close_buffer(); + // $this->close_buffer(); $tweaks->post_missed_schedule(); $this->co()->lockreset('post_missed_schedule'); }, @@ -2183,25 +2273,13 @@ private function register_cli() \WP_CLI::add_command('cache run:gc', [$cli, 'run_gc']); \WP_CLI::add_command('cache run:cron', [$cli, 'run_cron']); \WP_CLI::add_command('cache run:stats', [$cli, 'run_stats']); + \WP_CLI::add_command('cache run:optimizedb', [$cli, 'run_optimizedb']); \WP_CLI::add_command('cache reset:lock', [$cli, 'reset_lock']); \WP_CLI::add_command('cache reset:cron', [$cli, 'reset_cron']); - - if ($this->cf()->is_dctrue('ADVCPOST')) { - \WP_CLI::add_command('cache flush:advcpost', [$cli, 'flush_advcpost']); - } - - if ($this->cf()->is_dctrue('PRECACHE')) { - \WP_CLI::add_command('cache flush:precache', [$cli, 'flush_precache']); - } - - if ($this->cf()->is_dctrue('MENUCACHE')) { - \WP_CLI::add_command('cache flush:menucache', [$cli, 'flush_menucache']); - } - - if ($this->cf()->is_dctrue('MOCACHE')) { - \WP_CLI::add_command('cache flush:mocache', [$cli, 'flush_mocache']); - } - + \WP_CLI::add_command('cache flush:advcpost', [$cli, 'flush_advcpost']); + \WP_CLI::add_command('cache flush:precache', [$cli, 'flush_precache']); + \WP_CLI::add_command('cache flush:menucache', [$cli, 'flush_menucache']); + \WP_CLI::add_command('cache flush:mocache', [$cli, 'flush_mocache']); \WP_CLI::add_command('cache flush:transient', [$cli, 'flush_transient']); \WP_CLI::add_command('cache flush', [$cli, 'flush_cache']); \WP_CLI::add_command('cache runtime:install', [$cli, 'runtime_install']); diff --git a/includes/src/PostCache.php b/includes/src/PostCache.php index 0e8a468..ee1be9e 100644 --- a/includes/src/PostCache.php +++ b/includes/src/PostCache.php @@ -12,6 +12,7 @@ \defined('ABSPATH') || exit; +#[AllowDynamicProperties] final class PostCache { public $prefix = 'docketcache-post'; @@ -24,15 +25,32 @@ final class PostCache public $cached_posts = []; public $found_posts = false; public $cache_func = 'wp_cache_add'; - public $cache_func_expiry = 0; // let WP_Object_Cache::maybe_expire handles it - public $stalecache_list = []; - public $allow_posttype = ['post', 'page', 'attachment']; - public $blacklist_posttype = ['scheduled-action']; + public $cache_func_expiry = 86400; // 1d + public $allow_posttype = []; + public $blacklist_posttype = []; public $allow_posttype_all = false; public function __construct() { $this->group_prefix = $this->prefix.'-'; + $this->allow_posttype = [ + 'post', + 'page', + 'attachment', + 'revision', + 'nav_menu_item', + 'custom_css', + 'customize_changeset', + 'oembed_cache', + 'user_request', + 'wp_block', + 'wp_template', + 'wp_template_part', + 'wp_global_styles', + 'wp_navigation', + ]; + + $this->blacklist_posttype = ['scheduled-action']; } public function register() @@ -103,14 +121,14 @@ function ($counts = false, $post_id = 0) { $stats = get_comment_count(0); $stats['moderated'] = $stats['awaiting_moderation']; unset($stats['awaiting_moderation']); - $stats_object = (object) $stats; + $stats_object = $stats; wp_cache_set($cache_key, $stats_object, $this->prefix, 1800); // 1800 = 30min } - return $stats_object; + return (object) $stats_object; }, - 10, + \PHP_INT_MAX, 2 ); @@ -193,10 +211,6 @@ function ($post_id) { } } ); - - if (nwdcx_construe('FLUSH_STALECACHE')) { - add_action('shutdown', [$this, 'stalecache_set'], \PHP_INT_MAX); - } } public function setup_for_blog($new_blog_id = false, $previous_blog_id = false) @@ -224,11 +238,6 @@ public function invalidate_cache() return; } - // 03052022: flush - if (nwdcx_construe('FLUSH_STALECACHE')) { - $this->stalecache_list[md5($this->cache_group)] = $this->cache_group; - } - $this->cache_incr = wp_cache_incr('cache_incr', 1, $this->prefix); if (10 < \strlen($this->cache_incr)) { @@ -262,11 +271,12 @@ public function posts_request($sql, $query) return $sql; } - if (!$this->allow_post_type($query->get('post_type'))) { + $post_type = $query->get('post_type'); + if (!$this->allow_post_type($post_type)) { return $sql; } - $this->cache_key = 'query-'.md5($sql); + $this->cache_key = 'query-'.$post_type.'-'.md5($sql); $this->all_post_ids = wp_cache_get($this->cache_key, $this->cache_group); if ('NA' !== $this->found_posts) { @@ -393,14 +403,4 @@ public function found_posts($found_posts, $query) return $found_posts; } - - // send cache list to WP_Object_Cache and flush it at wp_cache_close right after "shutdown" hook. - public function stalecache_set() - { - if (!empty($this->stalecache_list) && \function_exists('wp_cache_add_stalecache')) { - $key_last = array_key_last($this->stalecache_list); - $list = [$key_last => $this->stalecache_list[$key_last]]; - wp_cache_add_stalecache($list); - } - } } diff --git a/includes/src/ReqAction.php b/includes/src/ReqAction.php index 11cdf19..a957052 100644 --- a/includes/src/ReqAction.php +++ b/includes/src/ReqAction.php @@ -173,7 +173,7 @@ private function run_action($action, $param, &$option_name = '', &$option_value case 'docket-flush-menucache': $result = 0; $ok = true; - if (\function_exists('wp_cache_flush_group')) { + if (\function_exists('wp_cache_flush_group') && method_exists('WP_Object_Cache', 'dc_remove_group')) { $result = wp_cache_flush_group('docketcache-menu'); $this->pt->co()->lookup_set('menucacheflushed', $result); $response = 'docket-menucache-flushed'; @@ -187,7 +187,7 @@ private function run_action($action, $param, &$option_name = '', &$option_value case 'docket-flush-mocache': $result = 0; $ok = true; - if (\function_exists('wp_cache_flush_group')) { + if (\function_exists('wp_cache_flush_group') && method_exists('WP_Object_Cache', 'dc_remove_group')) { $result = wp_cache_flush_group('docketcache-mo'); $this->pt->co()->lookup_set('mocacheflushed', $result); $response = 'docket-mocache-flushed'; @@ -201,7 +201,7 @@ private function run_action($action, $param, &$option_name = '', &$option_value case 'docket-flush-ocprecache': $result = 0; $ok = true; - if (\function_exists('wp_cache_flush_group')) { + if (\function_exists('wp_cache_flush_group') && method_exists('WP_Object_Cache', 'dc_remove_group')) { $result = wp_cache_flush_group('docketcache-precache'); $this->pt->co()->lookup_set('ocprecacheflushed', $result); $response = 'docket-ocprecache-flushed'; @@ -215,7 +215,7 @@ private function run_action($action, $param, &$option_name = '', &$option_value case 'docket-flush-advcpost': $result = 0; $ok = true; - if (\function_exists('wp_cache_flush_group_match')) { + if (\function_exists('wp_cache_flush_group_match') && method_exists('WP_Object_Cache', 'dc_remove_group')) { $result = wp_cache_flush_group_match('docketcache-post'); $this->pt->co()->lookup_set('advcpostflushed', $result); $response = 'docket-advcpost-flushed'; @@ -229,7 +229,7 @@ private function run_action($action, $param, &$option_name = '', &$option_value case 'docket-flush-transient': $result = 0; $ok = true; - if (\function_exists('wp_cache_flush_group')) { + if (\function_exists('wp_cache_flush_group') && method_exists('WP_Object_Cache', 'dc_remove_group')) { $result = wp_cache_flush_group(['transient', 'site-transient']); $this->pt->co()->lookup_set('transientflushed', $result); $response = 'docket-transient-flushed'; @@ -313,8 +313,13 @@ private function run_action($action, $param, &$option_name = '', &$option_value $response = 'docket-gcrun-failed'; $result = apply_filters('docketcache/filter/garbagecollector', true); if (!empty($result) && \is_object($result)) { - $this->pt->co()->lookup_set('gcrun', (array) $result); - $response = 'docket-gcrun'; + if ($result->is_locked) { + $response = 'docket-gcrun-warn'; + } else { + $this->pt->co()->lookup_set('gcrun', (array) $result); + $response = 'docket-gcrun'; + } + $ok = true; } @@ -399,26 +404,38 @@ private function run_action($action, $param, &$option_name = '', &$option_value } } - $ok = $this->pt->co()->save($nx, $nv); - if (isset($nvo)) { - $nv = $nvo; - } + if (WpConfig::has($nx)) { + $response = 'docket-option-wpf-warn'; + $this->pt->co()->save($nx, 'default'); + } else { + $ok = $this->pt->co()->save($nx, $nv); + if (isset($nvo)) { + $nv = $nvo; + } - $response = $ok ? 'docket-option-save' : 'docket-option-failed'; + $response = $ok ? 'docket-option-save' : 'docket-option-failed'; + } } + $option_value = $nv; } else { - $okmsg = 'default' === $nk ? 'docket-option-default' : 'docket-option-'.$nk; - $ok = $this->pt->co()->save($nx, $nk); - $response = $ok ? $okmsg : 'docket-option-failed'; + if (WpConfig::has($nx)) { + $response = 'docket-option-wpf-warn'; + $this->pt->co()->save($nx, 'default'); + } else { + $okmsg = 'default' === $nk ? 'docket-option-default' : 'docket-option-'.$nk; + $ok = $this->pt->co()->save($nx, $nk); + $response = $ok ? $okmsg : 'docket-option-failed'; + } } if ($ok) { - if ('wooaddtochartcrawling' === $nx && 'enable' == $nk) { + if ('wooaddtochartcrawling' === $nx && 'enable' == $nk && 'docket-option-wpf-warn' !== $response) { $robotsfile = wp_normalize_path(ABSPATH).'robots.txt'; if (@is_file($robotsfile)) { $response = 'docket-option-rbt-warn'; } + clearstatcache(); } } @@ -430,6 +447,7 @@ private function run_action($action, $param, &$option_name = '', &$option_value 'value' => $nv, 'name' => $nk, 'action' => $action, + 'response' => $response, ] ); } @@ -776,6 +794,11 @@ private function screen_notice() break; case 'docket-gcrun-failed': $this->pt->notice = esc_html__('Failed to run the garbage collector.', 'docket-cache'); + $this->pt->co()->lookup_delete('gcrun'); + break; + case 'docket-gcrun-warn': + $this->pt->notice = esc_html__('Process locked. The garbage collector is in process. Try again in a few seconds.', 'docket-cache'); + $this->pt->co()->lookup_delete('gcrun'); break; case 'docket-runtimeok': $this->pt->notice = esc_html__('Updating wp-config.php file successful.', 'docket-cache'); @@ -834,7 +857,7 @@ private function screen_notice() unset($total, $clmsg); break; case 'docket-menucache-flushed-failed': - $this->pt->notice = esc_html__('Menu cache could not be flushed.', 'docket-cache'); + $this->pt->notice = esc_html__('Menu cache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache'); break; case 'docket-mocache-flushed': $this->pt->notice = esc_html__('Translation cache was flushed.', 'docket-cache'); @@ -852,7 +875,7 @@ private function screen_notice() unset($total, $clmsg); break; case 'docket-mocache-flushed-failed': - $this->pt->notice = esc_html__('Translation cache could not be flushed.', 'docket-cache'); + $this->pt->notice = esc_html__('Translation cache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache'); break; case 'docket-ocprecache-flushed': $this->pt->notice = esc_html__('Object Precache was flushed.', 'docket-cache'); @@ -870,7 +893,7 @@ private function screen_notice() unset($total, $clmsg); break; case 'docket-ocprecache-flushed-failed': - $this->pt->notice = esc_html__('Object Precache could not be flushed.', 'docket-cache'); + $this->pt->notice = esc_html__('Object Precache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache'); break; case 'docket-advcpost-flushed': $this->pt->notice = esc_html__('Advanced Post Cache was flushed.', 'docket-cache'); @@ -888,7 +911,7 @@ private function screen_notice() unset($total, $clmsg); break; case 'docket-advcpost-flushed-failed': - $this->pt->notice = esc_html__('Advanced Post Cache could not be flushed.', 'docket-cache'); + $this->pt->notice = esc_html__('Advanced Post Cache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache'); break; case 'docket-transient-flushed': $this->pt->notice = esc_html__('Transient cache was flushed.', 'docket-cache'); @@ -906,7 +929,7 @@ private function screen_notice() unset($total, $clmsg); break; case 'docket-transient-flushed-failed': - $this->pt->notice = esc_html__('Transient cache could not be flushed.', 'docket-cache'); + $this->pt->notice = esc_html__('Transient cache could not be flushed. This action require Docket Cache object-cache.php Drop-in.', 'docket-cache'); break; } } diff --git a/includes/src/Resc.php b/includes/src/Resc.php index 1facf07..1fa2902 100644 --- a/includes/src/Resc.php +++ b/includes/src/Resc.php @@ -203,7 +203,7 @@ public static function runtimenotice($action, $is_adr = false) $message .= '