pt = $pt; $this->is_optimizedb = false; $this->wp_start_timestamp = \defined('WP_START_TIMESTAMP') ? WP_START_TIMESTAMP : microtime(true); $this->max_execution_time = $this->pt->get_max_execution_time(); } /** * register. */ public function register() { // global add_filter('docketcache/filter/garbagecollector', [$this, 'garbage_collector']); add_filter( 'cron_schedules', function ($schedules) { $schedules['halfhour'] = [ 'interval' => 30 * MINUTE_IN_SECONDS, 'display' => esc_html__('Every 30 Minutes', 'docket-cache'), ]; if (empty($schedules['hourly'])) { $schedules['hourly'] = [ 'interval' => HOUR_IN_SECONDS, 'display' => esc_html__('Once Hourly', 'docket-cache'), ]; } if (empty($schedules['monthly'])) { $schedules['monthly'] = [ 'interval' => MONTH_IN_SECONDS, 'display' => esc_html__('Once Monthly', 'docket-cache'), ]; } $schedules['docketcache_gc_schedule'] = [ 'interval' => 5 * MINUTE_IN_SECONDS, 'display' => esc_html__('Every 5 Minutes', 'docket-cache'), ]; $schedules['docketcache_checkversion_schedule'] = [ 'interval' => 15 * DAY_IN_SECONDS, 'display' => esc_html__('Every 15 Days', 'docket-cache'), ]; return $schedules; }, \PHP_INT_MAX ); add_action( 'plugins_loaded', function () { // 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); } } // 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 $cronoptmzdb = $this->pt->cf()->dcvalue('CRONOPTMZDB'); if (!empty($cronoptmzdb) && 'never' !== $cronoptmzdb && is_main_site()) { $recurrence = ''; switch ($cronoptmzdb) { case 'daily': $recurrence = 'daily'; break; case 'weekly': $recurrence = 'weekly'; break; case 'monthly': $recurrence = 'monthly'; break; } if (empty($recurrence)) { wp_clear_scheduled_hook('docketcache_optimizedb'); } else { $this->is_optimizedb = true; add_action('docketcache_optimizedb', [$this, 'optimizedb']); if (!wp_next_scheduled('docketcache_optimizedb')) { wp_schedule_event(time(), $recurrence, 'docketcache_optimizedb'); } } } else { if (wp_get_schedule('docketcache_optimizedb')) { wp_clear_scheduled_hook('docketcache_optimizedb'); } } // check version 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) { wp_clear_scheduled_hook('docketcache_checkversion'); } if (is_main_site() && is_main_network()) { add_action('docketcache_checkversion', [$this, 'checkversion']); if (!wp_next_scheduled('docketcache_checkversion')) { wp_schedule_event(time(), 'docketcache_checkversion_schedule', 'docketcache_checkversion'); } } } else { if (wp_get_schedule('docketcache_checkversion')) { 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']); } } ); } /** * unregister. */ public function unregister() { foreach (['docketcache_gc', 'docketcache_optimizedb', 'docketcache_watchproc', 'docketcache_checkversion'] as $hx) { wp_clear_scheduled_hook($hx); } } /** * reset. */ public function reset() { $this->unregister(); $this->register(); } /** * garbage_collector. */ public function garbage_collector($force = false) { static $is_done = false; $maxfile_default = (int) $this->pt->get_cache_maxfile(); $maxfile = $maxfile_default; if ($maxfile_default > 10000) { $maxfile = $maxfile_default - 1000; } $maxfile_precache_default = (int) $this->pt->get_precache_maxfile(); $maxfile_precache = $maxfile_precache_default; if ($maxfile_precache_default > 10000) { $maxfile_precache = $maxfile_precache_default - 1000; } $maxttl_default = (int) $this->pt->get_cache_maxttl(); $maxttl = $maxttl_default; if (!empty($maxttl)) { $maxttl = time() - $maxttl; } $chkmaxdisk = false; $maxsizedisk_default = (int) $this->pt->get_cache_maxsize_disk(); $maxsizedisk = $maxsizedisk_default; if (!empty($maxsizedisk)) { $maxsizedisk = $maxsizedisk - 1048576; if ($maxsizedisk > 1048576) { $chkmaxdisk = true; } } $collect = (object) [ '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, 'cleanup_expire' => 0, 'cleanup_maxdisk' => 0, 'cache_file' => 0, 'cache_cleanup' => 0, 'cache_ignore' => 0, 'cleanup_failed' => 0, 'cleanup_stalecache' => 0, ]; 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; } // 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 $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', '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 >= 6.3 // remove ending 's' -> posts = post-queries. $grpq = strtok($grp, 's').'-queries'; $wp_cache_last_changed[$grpq] = $wp_cache_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; if ('cli' === \PHP_SAPI) { $delay = 100; } // hold cache write $this->pt->suspend_cache_write(true); $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 ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) { break; } if ($slowdown > 10) { $slowdown = 0; usleep($delay); } ++$slowdown; try { if (!$object->isFile()) { ++$collect->cache_ignore; continue; } $fx = $object->getPathName(); $fn = $object->getFileName(); $fs = $object->getSize(); $fm = time() + 300; $ft = filemtime($fx); $this->pt->remove_non_chunk_cache($this->pt->cache_path, $fx); } catch (\Throwable $e) { nwdcx_throwable(__METHOD__, $e); continue; } nwdcx_cliverbose('run-gc: '.$fx."\n"); if ($fm >= $ft && (0 === $fs || 'dump_' === substr($fn, 0, 5))) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { ++$collect->cleanup_failed; } 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_precache > 0 && '032e9f2c5b60-' === substr($fn, 0, 13)) { ++$file_precache_count; if ($file_precache_count > $maxfile_precache) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { ++$collect->cleanup_failed; } ++$collect->cleanup_precache_maxfile; continue; } } if ($file_cache_count >= $maxfile) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { ++$collect->cleanup_failed; } ++$collect->cleanup_maxfile; continue; } if ($chkmaxdisk && $filesize_total > $maxsizedisk) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { ++$collect->cleanup_failed; } ++$collect->cleanup_maxdisk; continue; } $data = $this->pt->cache_get($fx); $is_timeout = false; if (false !== $data) { unset($data['data']); $is_timeout = !empty($data['timeout']) && $this->pt->valid_timestamp($data['timeout']) ? true : false; if ($is_timeout) { if ($fm >= (int) $data['timeout']) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { ++$collect->cleanup_failed; } unset($data); ++$collect->cleanup_expire; continue; } } else { if (!empty($data['timestamp']) && $this->pt->valid_timestamp($data['timestamp']) && $maxttl > $data['timestamp']) { $this->pt->unlink($fx, true); if ($force && @is_file($fx)) { ++$collect->cleanup_failed; } unset($data); ++$collect->cleanup_maxttl; continue; } } if ($is_flush_stalecache) { // wp stale cache // wp >= 6.3 if ($is_ignore_stalecache && $this->pt->is_wp_cache_group_queries($data['group'])) { if (@unlink($fx)) { clearstatcache(true, $fx); nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); ++$collect->cleanup_stalecache; continue; } } // wp stale cache // prefix:hash:timestamp timestamp 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|adjacent_post|wp_get_archives):([0-9a-f]{32}):([0-9\. ]{21})([0-9\. ]+)?$@', $data['key'], $mm)) { // main type switch ($mm[1]) { case 'get_page_by_path': case 'adjacent_post': $mm[1] = 'wp_query'; break; case 'comment_feed': $mm[1] = 'get_comments'; break; } $km = $wp_cache_last_changed_match[$data['group']]; if (($km === $mm[1] && $wp_cache_last_changed[$data['group']] !== $mm[3]) || $is_ignore_stalecache) { if (@unlink($fx)) { clearstatcache(true, $fx); nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); ++$collect->cleanup_stalecache; continue; } } } // wp stale cache // get_comment_child_ids:int:hash:timestamp timestamp // get_comment_child_ids:1654:5247020d1a40e3b2e9a40de1139bc5c9:0.48761900 1678553206 if (!empty($wp_cache_last_changed_match[$data['group']]) && preg_match('@^(get_comment_child_ids):(\d+):([0-9a-f]{32}):([0-9\. ]{21})([0-9\. ]+)?$@', $data['key'], $mm)) { $mm[1] = 'get_comments'; $km = $wp_cache_last_changed_match[$data['group']]; if (($km === $mm[1] && $wp_cache_last_changed[$data['group']] !== $mm[4]) || $is_ignore_stalecache) { if (@unlink($fx)) { clearstatcache(true, $fx); nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); ++$collect->cleanup_stalecache; continue; } } } // 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); nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); ++$collect->cleanup_stalecache; continue; } } } // 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); nwdcx_cliverbose('run-gc:stale-cache: '.$fx."\n"); ++$collect->cleanup_stalecache; } continue; } $current_prefix = $mm[1]; static $cache_prefix_cached = []; // wc product if ('products' === $data['group'] && preg_match('@.*?_type_(\d+)$@', $data['key'], $nn)) { $grp = 'product_'.$nn[1]; 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 (\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; } } } } } $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; } ++$collect->cleanup_maxttl; continue; }*/ unset($data); $filesize_total += $fs; ++$file_cache_count; } // foreach1 @unlink($gcisrun_lock); $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 ($this->pt->cf()->is_dcfalse('TRANSIENTDB') && \function_exists('nwdcx_cleanuptransient')) { nwdcx_cliverbose("run-gc: cleanup expired transients in DB\n"); nwdcx_cleanuptransient(); } // 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; } /** * optimizedb. */ public function optimizedb() { if (!nwdcx_wpdb($wpdb)) { return false; } if ($this->pt->co()->lockproc('optimizedb', time() + 3600)) { return false; } $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)) { foreach ($tables as $table) { $tbl = $table['Tables_in_'.$dbname]; $sql = 'OPTIMIZE TABLE `'.$tbl.'`'; $ret = $wpdb->query($sql); nwdcx_cliverbose(str_replace('`', '', $sql)."\n"); if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) { break; } } } unset($tables); } $wpdb->suppress_errors($suppress); $this->pt->co()->lockreset('optimizedb'); return true; } /** * delete_expired_transients_db. */ public function delete_expired_transients_db() { if (!nwdcx_wpdb($wpdb)) { return false; } if ($this->pt->cf()->is_dcfalse('TRANSIENTDB') && \function_exists('nwdcx_cleanuptransient')) { nwdcx_cleanuptransient(); } elseif (\function_exists('delete_expired_transients')) { delete_expired_transients(true); } return true; } /** * checkversion. */ public function checkversion() { if (!is_main_site()) { return false; } $part = 'checkversion'; if ($this->pt->co()->lockproc($part, time() + 3600)) { return false; } $checkdata = $this->pt->co()->get_part($part, true); if (!empty($checkdata) && \is_array($checkdata) && !empty($checkdata['selfcheck'])) { $selfcheck = $checkdata['selfcheck']; if (0 === $this->pt->sanitize_timestamp($selfcheck)) { return false; } if ($selfcheck > 0 && $selfcheck > time()) { return false; } } $main_site_url = $this->pt->site_url(); $site_url = $this->pt->site_url(true); $home_url = $this->pt->site_url(true, true); $stmp = time() + 120; $api_endpoint = $this->pt->api_endpoint.'/'.$part.'?v='.$stmp; $args = [ 'blocking' => true, 'body' => [ 'timestamp' => date('Y-m-d H:i:s T'), 'timezone' => wp_timezone_string(), 'site' => $site_url, 'token' => $this->pt->nw_encrypt($main_site_url, md5($site_url)), 'meta' => $this->pt->site_meta(), ], 'headers' => [ 'REFERER' => $home_url, 'Cache-Control' => 'no-cache', ], ]; $results = Crawler::post($api_endpoint, $args); $output = [ 'timestamp' => time(), 'endpoint' => $api_endpoint, 'request' => [ 'headers' => $args['headers'], 'content' => $args['body'], ], 'selfcheck' => time() + 86400, ]; if (is_wp_error($results)) { $output['error'] = $results->get_error_message(); $this->pt->co()->save_part($output, $part); return false; } $output['response'] = wp_remote_retrieve_body($results); if (!empty($output['response'])) { $output['response'] = json_decode($output['response'], true); if (\JSON_ERROR_NONE === json_last_error()) { if (!empty($output['response']['error'])) { $output['error'] = $output['response']['error']; $this->pt->co()->save_part($output, $part); return false; } } } $code = (int) wp_remote_retrieve_response_code($results); if ($code > 400) { $output['error'] = $code; $this->pt->co()->save_part($output, $part); return false; } $this->pt->co()->save_part($output, $part); $this->pt->co()->lockreset($part); return true; } }