define_path(); } private function define_path() { $path = ''; $constfx = nwdcx_constfx('DATA_PATH'); if (\defined($constfx)) { $path = \constant($constfx); } if (!empty($path)) { if ('docket-cache-data' !== basename($path)) { $path = rtrim($path, '/\\').'/docket-cache-data'; } } else { $path = rtrim(DOCKET_CACHE_CONTENT_PATH, '/\\').'/docket-cache-data'; } $path = wp_normalize_path($path); if (is_multisite()) { $path = nwdcx_network_dirpath($path); } $this->path = rtrim($path, '/\\'); $this->file = $this->path.'/options.php'; $this->path_lock = $this->path.'/lock'; } public static function init() { if (!isset(self::$inst)) { self::$inst = new self(); } return self::$inst; } public function is_options_writable() { if (@is_file($this->file)) { return @is_writable($this->file); } if (@is_dir($this->path)) { return @is_writable($this->path); } return @is_writable(\dirname($this->path)); } private function key_names() { $names = [ 'log', 'log_time', 'log_all', 'preload', 'menucache', 'optermcount', 'precache', 'mocache', 'misc_tweaks', 'postmissedschedule', 'wootweaks', 'wooadminoff', 'woowidgetoff', 'woowpdashboardoff', 'wooextensionpageoff', 'woocartfragsoff', 'wooaddtochartcrawling', 'pageloader', 'wpoptaload', 'cronoptmzdb', 'cronbot', 'opcviewer', 'stats', 'gcaction', 'flushaction', 'configaction', 'autoupdate_toggle', 'checkversion', 'optwpquery', 'limitbulkedit', 'pingback', 'headerjunk', 'wpemoji', 'wpembed', 'wpfeed', 'wplazyload', 'wpsitemap', 'wpapppassword', 'wpdashboardnews', 'wpbrowsehappy', 'wpservehappy', 'postviaemail', 'objectcacheoff', 'flush_shutdown', 'opcshutdown', 'maxsize_disk', 'maxfile', 'maxfile_livecheck', 'chunkcachedir', 'flush_stalecache', 'stalecache_ignore', 'emptycache_ignore', 'transientdb', 'limithttprequest', 'rtpostautosave', 'rtpostrevision', 'rtpostemptytrash', 'rtpluginthemeeditor', 'rtpluginthemeinstall', 'rtimageoverwrite', 'rtwpdebug', 'rtwpdebugdisplay', 'rtwpdebuglog', 'rtwpcoreupdate', 'rtconcatenatescripts', 'rtdisablewpcron', ]; if (version_compare($GLOBALS['wp_version'], '6.1', '<')) { $names[] = 'advcpost'; $names[] = 'advpost_posttype_all'; } if (version_compare($GLOBALS['wp_version'], '5.8', '<')) { $names[] = 'httpheadersexpect'; } return $names; } public function keys($key = false) { // key = title $data = [ 'log' => esc_html__('Cache Log', 'docket-cache'), 'log_time' => esc_html__('Log Timestamp', 'docket-cache'), 'log_all' => esc_html__('Log All', 'docket-cache'), 'preload' => esc_html__('Admin Page Cache Preloading', 'docket-cache'), 'menucache' => esc_html__('WordPress Menu Caching', 'docket-cache'), 'optermcount' => esc_html__('Optimize Term Count Queries', 'docket-cache'), 'precache' => esc_html__('Object Cache Precaching', 'docket-cache'), 'mocache' => esc_html__('WordPress Translation Caching', 'docket-cache'), 'misc_tweaks' => esc_html__('Misc Performance Tweaks', 'docket-cache'), '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 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'), 'wooaddtochartcrawling' => esc_html__('Prevent robots crawling add-to-cart links', 'docket-cache'), 'pageloader' => esc_html__('Admin Page Loader', 'docket-cache'), 'wpoptaload' => esc_html__('Suspend WP Options Autoload', 'docket-cache'), 'cronoptmzdb' => esc_html__('Optimize Database Tables', 'docket-cache'), 'cronbot' => esc_html__('Cronbot Service', 'docket-cache'), 'opcviewer' => esc_html__('OPcache Viewer', 'docket-cache'), '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'), 'configaction' => esc_html__('Export/Import Settings Action Button', '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'), 'limitbulkedit' => esc_html__('Limit Bulk Edit Actions', 'docket-cache'), 'pingback' => esc_html__('Deactivate XML-RPC / Pingbacks', 'docket-cache'), 'headerjunk' => esc_html__('Deactivate WP Header Junk', 'docket-cache'), 'wpemoji' => esc_html__('Deactivate WP Emoji', 'docket-cache'), 'wpembed' => esc_html__('Deactivate WP Embed', 'docket-cache'), 'wpfeed' => esc_html__('Deactivate WP Feed', 'docket-cache'), 'wplazyload' => esc_html__('Deactivate WP Lazy Load', 'docket-cache'), 'wpsitemap' => esc_html__('Deactivate WP Sitemap', 'docket-cache'), 'wpapppassword' => esc_html__('Deactivate WP Application Passwords', 'docket-cache'), 'wpdashboardnews' => esc_html__('Deactivate WP Events & News Feed Dashboard', 'docket-cache'), 'wpbrowsehappy' => esc_html__('Deactivate Browse Happy Checking', 'docket-cache'), 'wpservehappy' => esc_html__('Deactivate Serve Happy Checking', 'docket-cache'), 'postviaemail' => esc_html__('Deactivate Post Via Email', 'docket-cache'), 'objectcacheoff' => esc_html__('Suspend Object Cache', 'docket-cache'), 'flush_shutdown' => esc_html__('Flush Object Cache During Deactivation', 'docket-cache'), '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'), 'transientdb' => esc_html__('Retain Transients in Db', 'docket-cache'), 'limithttprequest' => esc_html__('Limit WP-Admin HTTP requests', 'docket-cache'), 'rtpostautosave' => esc_html__('Auto Save Interval', 'docket-cache'), 'rtpostrevision' => esc_html__('Post Revisions', 'docket-cache'), 'rtpostemptytrash' => esc_html__('Trash Bin', 'docket-cache'), 'rtpluginthemeeditor' => esc_html__('Disallows Plugin / Theme Editor', 'docket-cache'), 'rtpluginthemeinstall' => esc_html__('Disallows Plugin / Theme Update and Installation', 'docket-cache'), 'rtimageoverwrite' => esc_html__('Cleanup Image Edits', 'docket-cache'), 'rtwpdebug' => esc_html__('WP Debug', 'docket-cache'), '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'), ]; if (version_compare($GLOBALS['wp_version'], '6.1', '<')) { $data['advcpost'] = esc_html__('Advanced Post Caching', 'docket-cache'); $data['advpost_posttype_all'] = esc_html__('Post Caching Any Post Type', 'docket-cache'); } if (version_compare($GLOBALS['wp_version'], '5.8', '<')) { $data['httpheadersexpect'] = esc_html__('HTTP Request Expect header tweaks', 'docket-cache'); } $data = apply_filters('docketcache/filter/optionkeys', $data); if (false !== $key) { if (!empty($data[$key])) { return $data[$key]; } return false; } return array_keys($data); } public function read_config($file = '', $force = false) { $file = empty($file) ? $this->file : $file; $config = []; if (@is_file($file) && is_readable($file)) { // fresh if ($force) { $this->opcache_flush($file); } try { $config = @include $file; } catch (\Throwable $e) { nwdcx_throwable(__METHOD__, $e); } } return $config; } public function put_config($config, $file = '') { $file = empty($file) ? $this->file : $file; if (empty($config) || !\is_array($config)) { if (@is_file($file) && @is_writable($file)) { @unlink($file); } clearstatcache(); return false; } $data = $this->export_var($config); $code = $this->code_stub($data); return $this->dump($file, $code); } public function config_cleanup($config) { if (!empty($config) && \is_array($config)) { $names = $this->key_names(); foreach ($config as $name => $value) { $nx = strtolower(nwdcx_constfx($name, true)); if (!\in_array($nx, $names) || (\is_string($value) && 'default' === $value)) { unset($config[$name]); } } } return $config; } public function get($name) { $config = $this->read_config(); if (!empty($config) && !empty($config[$name])) { return $config[$name]; } return false; } public function save($name, $value, $do_action = true) { if (!$this->mkdir_p($this->path)) { return false; } $this->placeholder($this->path); $config = $this->read_config(); $config = $this->config_cleanup($config); if (\in_array($name, $this->key_names())) { $nx = nwdcx_constfx($name); if ('default' === $value) { unset($config[$nx]); } else { $config[$nx] = $value; } } $ret = $this->put_config($config); if ($do_action) { do_action('docketcache/action/saveoption', $name, $value, $ret); } return $ret; } public function save_part($data, $file = 'part') { $file = $this->path.'/'.$file.'.php'; return $this->put_config($data, $file); } public function get_part($file = 'part', $force = false) { $file = $this->path.'/'.$file.'.php'; return $this->read_config($file, $force); } public function clear_part($file) { $file = $this->path.'/'.$file.'.php'; if (!@is_file($file)) { return true; } $ret = @unlink($file); clearstatcache(); return $ret; } public function clear_lock() { $path = $this->path_lock; if (!@is_dir($path)) { return false; } $files = @glob($path.'/lock-*.txt', \GLOB_MARK | \GLOB_NOSORT); if (!empty($files) && \is_array($files)) { foreach ($files as $file) { if (@is_file($file) && @is_writable($file)) { nwdcx_cliverbose(basename($file)."\n"); @unlink($file); } } } $files = @glob($path.'/dump_*.txt', \GLOB_MARK | \GLOB_NOSORT); if (!empty($files) && \is_array($files)) { foreach ($files as $file) { if (@is_file($file) && @is_writable($file)) { @unlink($file); } } } clearstatcache(); return true; } private function lock_file($key) { $key = substr(md5($key), 0, 12); $path = $this->path_lock; if (!$this->mkdir_p($path.'/')) { return false; } $this->placeholder($path); return $path.'/lock-'.$key.'.txt'; } public function setlock($key, $value) { $file = $this->lock_file($key); if (!$file) { return false; } if (true === $this->dump($file, $value)) { return true; } return false; } public function unlock($key) { $file = $this->lock_file($key); if (!$file || !@is_file($file)) { return true; } $ret = @unlink($file); clearstatcache(); return $ret; } public function locked($key, &$value = '') { clearstatcache(); $file = $this->lock_file($key); if (!$file || !@is_file($file)) { return false; } $value = @file_get_contents($file); return true; } // locked and not expire public function lockexp($key) { if ($this->locked($key, $locked)) { if (!empty($locked) && (int) $locked > time()) { return true; } } return false; } // lock process public function lockproc($key, $value) { // if locked and not expire if ($this->lockexp($key)) { return true; } // if expire set new lock $this->setlock($key, $value); return false; } public function lockreset($key) { return $this->setlock($key, 0); } // lookup public function lookup_set($key, $value) { $fkey = 'lockup-'.$key; return $this->setlock($fkey, maybe_serialize($value)); } public function lookup_get($key, $forget = false) { $fkey = 'lockup-'.$key; if ($this->locked($fkey, $value)) { if (!empty($value)) { if ($forget) { $this->lookup_delete($key); } return maybe_unserialize($value); } } if ($forget) { $this->lookup_delete($key); } return false; } public function lookup_delete($key) { $fkey = 'lockup-'.$key; return $this->unlock($fkey); } public function export_config() { $config = $this->read_config(); $config = $this->config_cleanup($config); if (empty($config) || !\is_array($config)) { return false; } // Strip DOCKET_CACHE_ prefix for cleaner export. $export = []; foreach ($config as $name => $value) { $key = strtolower(nwdcx_constfx($name, true)); $export[$key] = $value; } return $export; } public function import_config($data) { if (empty($data) || !\is_array($data)) { return false; } if (!$this->mkdir_p($this->path)) { return false; } $this->placeholder($this->path); $valid_keys = $this->key_names(); $config = []; foreach ($data as $name => $value) { $name = strtolower($name); if (!\in_array($name, $valid_keys)) { continue; } // Only accept scalar values. if (!\is_scalar($value)) { continue; } $value = (string) $value; // Cap value length to prevent abuse. if (\strlen($value) > 256) { continue; } if ('default' !== $value) { $nx = nwdcx_constfx($name); $config[$nx] = $value; } } if (empty($config)) { return false; } return $this->put_config($config); } public function reset() { $this->clear_lock(); $parts = [ 'options', 'runtime', 'pings', 'cronbot', 'checkversion', 'cachestats', 'gc', ]; $ok = true; foreach ($parts as $part) { $file = $this->path.'/'.$part.'.php'; if (@is_file($file) && is_writable($file)) { if (!@unlink($file)) { $ok = false; } } } clearstatcache(true); return $ok; } }