slug = 'docket-cache'; $this->file = nwdcx_constval('FILE'); $this->hook = plugin_basename($this->file); $this->path = realpath(plugin_dir_path($this->file)); $this->register_init(); } /** * Dropino(). */ public function cx() { static $inst; if (!\is_object($inst)) { $inst = new Dropino($this->path); } return $inst; } /** * Canopt(). */ public function co() { static $inst; if (!\is_object($inst)) { $inst = new Canopt(); } return $inst; } /** * Constans(). */ public function cf() { static $inst; if (!\is_object($inst)) { $inst = new Constans(); } return $inst; } /** * get_version. */ public function version() { static $version = false; if (!empty($version)) { return $version; } $meta = $this->plugin_meta($this->file); $version = !empty($meta) ? $meta['Version'] : ''; return $version; } /** * get_info. */ public function get_info() { $status_code = [ 0 => esc_html__('Disabled', 'docket-cache'), 1 => esc_html__('Enabled', 'docket-cache'), 2 => esc_html__('Not Available', 'docket-cache'), 3 => esc_html__('Unknown', 'docket-cache'), ]; $yesno = [ 0 => esc_html__('No', 'docket-cache'), 1 => esc_html__('Yes', 'docket-cache'), ]; $force_stats = $this->cf()->is_dctrue('WPCLI'); $cache_stats = $this->get_cache_stats($force_stats); $status = $this->get_status(); $status_text_stats = ''; $status_text = ''; switch ($status) { case 1: if ($this->cf()->is_dctrue('STATS')) { /* translators: %1$s = size, %2$s number of file */ $status_text_stats = sprintf(esc_html__(_n('%1$s object of %2$s file', '%1$s object of %2$s files', $cache_stats->files < 1 ? 1 : $cache_stats->files, 'docket-cache')), $this->normalize_size($cache_stats->size), $cache_stats->files); } $status_text = $status_code[1]; break; case 2: $status_text = esc_html__('Disabled at runtime.', 'docket-cache'); break; default: $status_text = $status_code[$status]; } $opcache = $this->get_opcache_status(); $opcache_text_stats = ''; $opcache_text = ''; $opcache_dc_stats = ''; $opcache_wp_stats = ''; switch ($opcache->status) { case 1: /* translators: %1$s = size, %2$s number of file */ $opcache_text_stats = sprintf(esc_html__(_n('%1$s memory of %2$s file', '%1$s memory of %2$s files', $opcache->files, 'docket-cache')), $this->normalize_size($opcache->size), $opcache->files); if ($opcache->dcfiles > 1) { /* translators: %1$s = size, %2$s number of file */ $opcache_dc_stats = sprintf(esc_html__(_n('%1$s memory of %2$s file', '%1$s memory of %2$s files', $opcache->dcfiles, 'docket-cache')), $this->normalize_size($opcache->dcsize), $opcache->dcfiles); /* translators: %1$s = size, %2$s number of file */ $opcache_wp_stats = sprintf(esc_html__(_n('%1$s memory of %2$s file', '%1$s memory of %2$s files', $opcache->wpfiles, 'docket-cache')), $this->normalize_size($opcache->wpsize), $opcache->wpfiles); } break; case 2: $opcache_text = $status_code[1]; break; case 3: $opcache_text = $status_code[1].' ('.esc_html__('File cache only', 'docket-cache').')'; break; case 4: if ($opcache->files > 0) { /* translators: %1$s = size, %2$s number of file */ $opcache_text_stats = sprintf(esc_html__(_n('%1$s size of %2$s file', '%1$s size of %2$s files', $opcache->files, 'docket-cache')), $this->normalize_size($opcache->size), $opcache->files); if ($opcache->dcfiles > 1) { /* translators: %1$s = size, %2$s number of file */ $opcache_dc_stats = sprintf(esc_html__(_n('%1$s size of %2$s file', '%1$s size of %2$s files', $opcache->dcfiles, 'docket-cache')), $this->normalize_size($opcache->dcsize), $opcache->dcfiles); /* translators: %1$s = size, %2$s number of file */ $opcache_wp_stats = sprintf(esc_html__(_n('%1$s size of %2$s file', '%1$s size of %2$s files', $opcache->wpfiles, 'docket-cache')), $this->normalize_size($opcache->wpsize), $opcache->wpfiles); } } else { $opcache_text = $status_code[1].' ('.esc_html__('File cache only', 'docket-cache').')'; } break; default: $opcache_text = $status_code[2]; } $log_enable = $this->cf()->is_dctrue('LOG') ? 1 : 0; $log_file = $this->cf()->dcvalue('LOG_FILE'); if (is_multisite()) { $log_file = nwdcx_network_filepath($log_file); } $multisite_text = $status_code[3]; $multinets_lock = ''; if (is_multisite()) { $netcount = get_networks(['count' => 1]); $this->get_network_sites($sitecount, true); /* translators: %s = sites */ $multisite_text = sprintf(esc_html__(_n('%s Site', '%s Sites', $sitecount, 'docket-cache')), $sitecount); if ($netcount > 1) { /* translators: %s = networks */ $multisite_text = $multisite_text.' '.sprintf(esc_html__(_n('of %s Network', 'of %s Networks', $netcount, 'docket-cache')), $netcount); $multinets_lock = $this->sanitize_rootpath($this->cx()->multinet_tag()); } } $file_dropin = $this->cx()->resc()->dst; if (@is_file($file_dropin)) { $write_dropin = @is_writable($file_dropin); } else { $write_dropin = @is_writable($this->cx()->condir.'/'); } $file_dropin_wp = $this->cx()->resc()->wpdst; $dropin_wp_exist = false; $is_dropin_alternative = $this->cx()->is_alternative(); if ($is_dropin_alternative) { $dropin_wp_exist = @is_file($file_dropin_wp) || @is_link($file_dropin_wp); } $file_max = $this->get_cache_maxfile(); $disk_max = $this->normalize_size($this->get_cache_maxsize_disk()); $file_stats = $file_max; $disk_stats = $disk_max; if (isset($cache_stats->files) && !empty($cache_stats->files)) { $file_stats = $cache_stats->files.' / '.$file_max; } if (isset($cache_stats->filesize) && !empty($cache_stats->filesize)) { $disk_stats = $this->normalize_size($cache_stats->filesize).' / '.$disk_max; } return [ 'status_code' => $status, 'status_text' => $status_text, 'status_text_stats' => $status_text_stats, 'opcache_code' => $opcache->status, 'opcache_text' => $opcache_text, 'opcache_text_stats' => $opcache_text_stats, 'opcache_dc_stats' => $opcache_dc_stats, 'opcache_wp_stats' => $opcache_wp_stats, 'php_memory_limit' => $this->normalize_size(@\ini_get('memory_limit')), 'wp_memory_limit' => $this->normalize_size(WP_MEMORY_LIMIT), 'wp_max_memory_limit' => $this->normalize_size(WP_MAX_MEMORY_LIMIT), 'write_dropin' => $yesno[$write_dropin], 'dropin_path' => $this->sanitize_rootpath($file_dropin), 'dropin_isalt' => $is_dropin_alternative, 'dropin_alt' => $yesno[$is_dropin_alternative], 'dropin_wp' => $this->sanitize_rootpath($file_dropin_wp), 'dropin_wp_isexist' => $dropin_wp_exist, 'dropin_wp_exist' => $yesno[$dropin_wp_exist], 'write_cache' => $yesno[is_writable($this->cache_path)], 'cache_chunkdir' => $yesno[$this->cf()->dcvalue('CHUNKCACHEDIR')], 'cache_size' => $this->normalize_size($cache_stats->size), 'cache_path_real' => $this->cache_path, 'cache_path' => $this->sanitize_rootpath($this->cache_path), 'cache_maxfile' => $file_max, 'cache_file_stats' => $file_stats, 'cache_maxsize_disk' => $this->normalize_size($this->get_cache_maxsize_disk()), 'cache_disk_stats' => $disk_stats, 'log_file_real' => $log_file, 'log_file' => $this->sanitize_rootpath($log_file), 'log_enable' => $log_enable, 'log_enable_text' => $status_code[$log_enable], 'config_path' => $this->sanitize_rootpath($this->co()->path), 'write_config' => $yesno[$this->co()->is_options_writable()], 'wp_multisite' => $multisite_text, 'wp_multinetlock' => $multinets_lock, 'wp_multinetmain' => $yesno[is_main_network()], ]; } /** * sanitize_rootpath. */ public function sanitize_rootpath($path) { $wp_content_dir = wp_normalize_path(WP_CONTENT_DIR); $abspath = wp_normalize_path(ABSPATH); return rtrim(str_replace([$wp_content_dir, $abspath], ['/'.basename($wp_content_dir), '/'], $path), '/'); } /** * get_status. */ public function get_status() { if ($this->cf()->is_dctrue('DISABLED')) { return 2; } if (!$this->cx()->exists()) { return 0; } if ($this->cx()->validate() && $this->cx()->multinet_me()) { if ($this->cf()->is_dctrue('OBJECTCACHEOFF', true)) { $this->co()->save('objectcacheoff', 'default'); } return 1; } if (!$this->cx()->multinet_me()) { return 0; } return 3; } /** * get_logsize. */ public function get_logsize() { if ($this->has_log($logfile)) { return $this->normalize_size($this->filesize($logfile)); } return 0; } /** * get_opcache_status. */ public function get_opcache_status($is_raw = false) { $total_bytes = 0; $total_files = 0; $status = 0; $dcfiles = 0; $dcbytes = 0; $wpfiles = 0; $wpbytes = 0; $stale = 0; $data = []; if ($this->is_opcache_enable()) { if (!$this->opcache_function_exists('opcache_get_status')) { $status = 2; } else { $nwdcx_suppresserrors = nwdcx_suppresserrors(true); $data = opcache_get_status(); nwdcx_suppresserrors($nwdcx_suppresserrors); if (!empty($data) && \is_array($data)) { if (!empty($data['opcache_enabled'])) { if ($is_raw) { return $data; } $status = 1; if (!empty($data['memory_usage']['used_memory'])) { $total_bytes = $data['memory_usage']['used_memory']; } if (!empty($data['opcache_statistics']['num_cached_scripts'])) { $total_files = $data['opcache_statistics']['num_cached_scripts']; } if (!empty($data['scripts']) && \is_array($data['scripts'])) { $abspath = nwdcx_normalizepath(ABSPATH); foreach ($data['scripts'] as $script => $arr) { // Only count scripts belonging to this site. if (false === strpos($arr['full_path'], $abspath)) { continue; } $cfile = basename($arr['full_path']); $cdir = \dirname($arr['full_path']); if (false !== strpos($script, $this->cache_path) && $this->is_docketcachedir($cdir) && @preg_match('@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@', $cfile)) { ++$dcfiles; if (isset($arr['memory_consumption'])) { $dcbytes += $arr['memory_consumption']; } } else { ++$wpfiles; if (isset($arr['memory_consumption'])) { $wpbytes += $arr['memory_consumption']; } } } } } elseif (!empty($data['file_cache_only'])) { $status = 3; if (!empty($data['file_cache']) && is_dir($data['file_cache']) && is_readable($data['file_cache'])) { $dir = nwdcx_normalizepath(realpath($data['file_cache'])); $cnt = 0; $abspath = nwdcx_normalizepath(ABSPATH); $cdata = $is_raw ? [ 'opcache_statistics' => [ 'num_cached_scripts' => 0, ], 'scripts' => [], ] : null; // Cap scripts array to prevent memory exhaustion. // Timeout guard to prevent hitting max_execution_time on shared hosting. $scripts_limit = 50000; $scan_start = microtime(true); $scan_timeout = (int) \ini_get('max_execution_time'); if ($scan_timeout > 0) { // Use half the max execution time to leave room for rendering. $scan_timeout = max(5, (int) ($scan_timeout / 2)); } else { $scan_timeout = 30; } foreach ($this->opcache_filecache_scanfiles($dir) as $object) { try { if (!$object->isFile()) { continue; } $cpath = nwdcx_normalizepath($object->getPathName()); if (false === strpos($cpath, $abspath)) { continue; } $cfile = basename($cpath); $cdir = \dirname($cpath); $fs = $object->getSize(); if (false !== strpos($cpath, $this->cache_path) && $this->is_docketcachedir($cdir) && @preg_match('@^([a-z0-9]{12})\-([a-z0-9]{12})\.php\.bin$@', $cfile)) { ++$dcfiles; $dcbytes += $fs; } else { ++$wpfiles; $wpbytes += $fs; } ++$cnt; $total_bytes += $fs; // Only build scripts array when raw data is requested (OPcacheView). if ($is_raw && $cnt <= $scripts_limit) { $cdata['scripts'][$cpath] = [ 'full_path' => $cpath, 'memory_consumption' => $fs, 'last_used_timestamp' => $object->getATime(), 'hits' => 1, ]; } // Check timeout every 500 files. if (0 === $cnt % 500 && (microtime(true) - $scan_start) > $scan_timeout) { break; } } catch (\Throwable $e) { nwdcx_throwable(__METHOD__, $e); continue; } } if ($cnt > 0) { $status = 4; $total_files = $cnt; } } if ($is_raw) { $cdata['opcache_statistics']['num_cached_scripts'] = $cnt; $data = array_merge($data, $cdata); $data['_numfile'] = $cnt; $data['_sizefile'] = $total_bytes; return $data; } } } } } $arr = [ 'status' => (int) $status, 'size' => $total_bytes, 'files' => (int) $total_files, 'wpfiles' => (int) $wpfiles, 'wpsize' => (int) $wpbytes, 'dcfiles' => (int) $dcfiles, 'dcsize' => (int) $dcbytes, 'stale' => (int) $stale, ]; return (object) $arr; } /** * get_cache_maxfile. */ public function get_cache_maxfile() { $maxfile = $this->cf()->dcvalue('MAXFILE', true); return $this->sanitize_maxfile($maxfile); } /** * get_cache_maxttl. */ public function get_cache_maxttl() { $maxttl = $this->cf()->dcvalue('MAXTTL'); return $this->sanitize_maxttl($maxttl); } /** * get_cache_maxsize_disk. */ public function get_cache_maxsize_disk() { $maxsizedisk = $this->cf()->dcvalue('MAXSIZE_DISK'); return $this->sanitize_maxsizedisk($maxsizedisk); } /** * get_precache_maxfile. */ public function get_precache_maxfile() { if ($this->cf()->is_dcfalse('PRECACHE')) { return 0; } $maxfile = $this->cf()->dcvalue('PRECACHE_MAXFILE', true); return $this->sanitize_precache_maxfile($maxfile); } /** * get_cache_stats. */ public function get_cache_stats($force = false) { $cache_stats = false; if ($this->cf()->is_dctrue('STATS')) { if ($force) { $cache_stats = $this->cache_size($this->cache_path); $this->co()->save_part($cache_stats, 'cachestats'); } else { $cache_stats = $this->co()->get_part('cachestats'); } } if (empty($cache_stats) || !\is_array($cache_stats)) { $cache_stats = [ 'size' => 0, 'filesize' => 0, 'files' => 0, ]; } return (object) $cache_stats; } /** * normalize_size. */ public function normalize_size($size, $showb = true) { $size = wp_convert_hr_to_bytes($size); $size = str_replace([',', ' ', 'B'], '', size_format($size)); if ($showb && is_numeric($size)) { $size = $size.'B'; } return $size; } public function site_url_scheme($site_url) { $site_url = trim($site_url); if ('https://' !== substr($site_url, 0, 8) && $this->is_ssl()) { $site_url = nwdcx_fixscheme($site_url, 'https://'); } elseif (!@preg_match('@^(https?:)?//@', $site_url)) { $site_url = nwdcx_fixscheme($site_url, 'http://'); } return rtrim($site_url, '/\\'); } public function site_url($current = false, $is_home = false) { $option = $is_home ? 'home' : 'siteurl'; $site_url = get_option($option); if (!$current && is_multisite()) { $blog_id = get_main_site_id(); switch_to_blog($blog_id); $site_url = get_option($option); restore_current_blog(); } return $this->site_url_scheme($site_url); } public function site_meta($short = false) { $m = '0,0'; if (is_multisite()) { $n = get_networks(['count' => 1]); $s = get_sites(['count' => 1]); $m = $n.','.$s; } if ($short) { $meta = $m.','.$this->version(); $meta = str_replace('0,0,', '', $meta); $meta = str_replace('0', '', $meta); } else { $meta = $m.','.$this->version().','.$GLOBALS['wp_version']; } return str_replace('.', '', $meta); } /** * flush_cache. */ public function flush_cache($cleanup = false, &$is_timeout = false) { $this->co()->clear_part('cachestats'); $this->cx()->delay(); 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) { $this->cx()->undelay(); return $cnt; } $this->co()->clear_lock(); return $cnt; } /** * flush_fcache. */ public function flush_fcache(&$file = '') { if (!empty($_GET['idxv'])) { $fx = sanitize_text_field($_GET['idxv']); $file = $this->cache_path.$fx.'.php'; if (@is_file($file)) { $this->co()->clear_part('cachestats'); return $this->unlink($file, true); } } return true; } /** * has_log. */ public function has_log(&$logfile = '') { $logfile = $this->cf()->dcvalue('LOG_FILE'); if (is_multisite()) { $logfile = nwdcx_network_filepath($logfile); } return @is_file($logfile) && is_readable($logfile); } /** * flush_log. */ public function flush_log() { if ($this->has_log($logfile)) { return @unlink($logfile); } return false; } public function switch_cron_site() { if (is_multisite()) { $cronbot_siteid = $this->get_cron_siteid(); if (!empty($cronbot_siteid) && (int) $cronbot_siteid > 0) { switch_to_blog($cronbot_siteid); return true; } } return false; } public function delete_cron_siteid($userid = false) { if (empty($userid)) { $userid = get_current_user_id(); } $key = 'cronbot-siteid-'.get_current_user_id(); return $this->co()->lookup_delete($key); } public function set_cron_siteid($id) { $key = 'cronbot-siteid-'.get_current_user_id(); return $this->co()->lookup_set($key, $id); } public function get_cron_siteid() { $key = 'cronbot-siteid-'.get_current_user_id(); $siteid = $this->co()->lookup_get($key); if (empty($siteid)) { $siteid = is_multisite() ? get_main_site_id() : get_current_blog_id(); } return $siteid; } public function cleanuppost() { if (!nwdcx_wpdb($wpdb)) { return false; } $sites = $this->get_network_sites(); $is_multisite = is_multisite(); $collect = [ 'revision' => 0, 'autodraft' => 0, 'trashbin' => 0, ]; $siteid = 0; if ($is_multisite) { $collect['site'] = \count($sites); if (isset($_GET['siteid'])) { $siteid = (int) sanitize_text_field($_GET['siteid']); $this->set_current_select_siteid($siteid); } } $max_execution_time = $this->get_max_execution_time(); $suppress = $wpdb->suppress_errors(true); $doflush = false; foreach ($sites as $num => $site) { if ($is_multisite) { if (!empty($siteid) && $siteid !== (int) $site['id']) { continue; } switch_to_blog($site['id']); } $query = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_type = %s ORDER BY ID ASC LIMIT 1000", 'revision')); if ($query) { foreach ($query as $id) { $id = (int) $id; wp_delete_post_revision($id); $doflush = true; } $collect['revision'] += \count($query); } $query = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_status = %s ORDER BY ID ASC LIMIT 1000", 'auto-draft')); if ($query) { foreach ($query as $id) { $id = (int) $id; wp_delete_post($id, true); $doflush = true; } $collect['autodraft'] += \count($query); } $query = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_status = %s ORDER BY ID ASC LIMIT 1000", 'trash')); if ($query) { foreach ($query as $id) { $id = (int) $id; wp_delete_post($id, true); $doflush = true; } $collect['trashbin'] += \count($query); } if ($is_multisite) { restore_current_blog(); } if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) { $is_timeout = true; break; } } $wpdb->suppress_errors($suppress); if ($doflush) { $this->flush_cache(false); } return (object) $collect; } public function delete_current_select_siteid($userid = false) { if (empty($userid)) { $userid = get_current_user_id(); } $key = 'current-select-siteid-'.get_current_user_id(); return $this->co()->lookup_delete($key); } public function get_current_select_siteid() { $key = 'current-select-siteid-'.get_current_user_id(); return (int) $this->co()->lookup_get($key); } public function set_current_select_siteid($id) { $key = 'current-select-siteid-'.get_current_user_id(); return $this->co()->lookup_set($key, $id); } /** * compat_notice. */ private function compat_notice() { if (!(\PHP_VERSION_ID >= 70205)) { add_action( 'all_admin_notices', function () { $text = __('Docket Cache plugin requires PHP 7.2.5 or greater.', 'docket-cache'); echo '
'.$text.'