string,
* 'status' => 'executed'|'warning'|'not_detected'|'failed'|'skipped',
* 'ok' => bool, // convenience mirror: status in executed|warning.
* 'detail' => string,
* ]
*/
class OVKIT_Cache_Manager {
const NONCE_ACTION = 'ovkit_cache_manager';
const AJAX_CLEAR = 'ovkit_cache_clear';
const AJAX_LOG = 'ovkit_cache_log';
const AJAX_ENV = 'ovkit_cache_env';
const CAPABILITY = 'manage_options';
const BUST_OPTION = 'ovkit_cache_bust_version';
const LOG_OPTION = 'ovkit_cache_log';
const LOG_LIMIT = 20;
const STATUS_EXECUTED = 'executed';
const STATUS_WARNING = 'warning';
const STATUS_NOT_DETECTED = 'not_detected';
const STATUS_FAILED = 'failed';
const STATUS_SKIPPED = 'skipped';
/**
* Singleton instance.
*
* @var self|null
*/
private static $instance = null;
/**
* Whether boot() already registered the hooks.
*
* @var bool
*/
private $booted = false;
/**
* Cached browser cache-bust version (null until read).
*
* @var int|null
*/
private $bust_version = null;
/**
* Instance.
*
* @return self
*/
public static function instance(): self {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Boot.
*/
public function boot(): void {
if ( $this->booted ) {
return;
}
$this->booted = true;
add_action( 'admin_bar_menu', array( $this, 'render_admin_bar' ), 110 );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'wp_ajax_' . self::AJAX_CLEAR, array( $this, 'ajax_clear' ) );
add_action( 'wp_ajax_' . self::AJAX_LOG, array( $this, 'ajax_log' ) );
add_action( 'wp_ajax_' . self::AJAX_ENV, array( $this, 'ajax_env' ) );
add_filter( 'style_loader_src', array( $this, 'filter_asset_src' ), 9999 );
add_filter( 'script_loader_src', array( $this, 'filter_asset_src' ), 9999 );
}
// Row builder.
/**
* Row.
*
* @param string $label Label.
* @param string $status Status.
* @param string $detail Detail.
* @return array
*/
private function row( string $label, string $status, string $detail ): array {
$ok = in_array( $status, array( self::STATUS_EXECUTED, self::STATUS_WARNING ), true );
return array(
'label' => $label,
'status' => $status,
'ok' => $ok,
'detail' => $detail,
);
}
// Admin bar.
/**
* Render admin bar.
*
* @param mixed $bar Bar.
*/
public function render_admin_bar( $bar ): void {
if ( ! current_user_can( self::CAPABILITY ) ) {
return;
}
if ( ! is_admin_bar_showing() ) {
return;
}
if ( ! $bar->get_node( 'ovkit' ) ) {
$bar->add_node(
array(
'id' => 'ovkit',
'title' => 'Toolkit',
'href' => admin_url( 'admin.php?page=ovkit' ),
)
);
}
$bar->add_node(
array(
'id' => 'ovkit-cache',
'parent' => 'ovkit',
'title' => __( 'Cache', 'ovanap-admin-toolkit' ),
'href' => '#',
'meta' => array( 'class' => 'ovkit-cache-root' ),
)
);
$systems = $this->detect_systems();
$env = $this->get_environment();
$bust_label = $this->bust_label( $env['bust_version'], $env['bust_human'] );
$items = array(
array(
'id' => 'ovkit-cache-env',
'label' => __( 'Systems & environment…', 'ovanap-admin-toolkit' ),
),
array(
'id' => 'ovkit-cache-preview',
'label' => __( 'Preview (dry-run all safe)', 'ovanap-admin-toolkit' ),
),
array(
'id' => 'ovkit-cache-all-safe',
'label' => __( 'Clear All — Safe', 'ovanap-admin-toolkit' ),
),
array(
'id' => 'ovkit-cache-all-aggressive',
'label' => __( 'Clear All — Aggressive…', 'ovanap-admin-toolkit' ),
'aggressive' => true,
),
array(
'id' => 'ovkit-cache-object-runtime',
'label' => __( 'Object cache — runtime only', 'ovanap-admin-toolkit' ),
),
array(
'id' => 'ovkit-cache-object-full',
'label' => __( 'Object cache — full flush…', 'ovanap-admin-toolkit' ),
'aggressive' => true,
),
array(
'id' => 'ovkit-cache-transients',
'label' => __( 'Clear Transients', 'ovanap-admin-toolkit' ),
),
array(
'id' => 'ovkit-cache-wp-core',
'label' => __( 'Clear WordPress Core Caches', 'ovanap-admin-toolkit' ),
),
array(
'id' => 'ovkit-cache-page',
'label' => $this->label_for( 'page', $systems, __( 'Clear Page Cache', 'ovanap-admin-toolkit' ) ),
),
array(
'id' => 'ovkit-cache-builder',
'label' => $this->label_for( 'builder', $systems, __( 'Clear Builder / Asset Cache', 'ovanap-admin-toolkit' ) ),
),
);
if ( class_exists( 'WooCommerce' ) ) {
$items[] = array(
'id' => 'ovkit-cache-woo',
'label' => __( 'Invalidate WooCommerce transient versions', 'ovanap-admin-toolkit' ),
);
}
$items[] = array(
'id' => 'ovkit-cache-opcache',
'label' => __( 'Reset OPcache', 'ovanap-admin-toolkit' ),
);
$items[] = array(
'id' => 'ovkit-cache-rewrite',
'label' => __( 'Flush Rewrite Rules (expensive)', 'ovanap-admin-toolkit' ),
'aggressive' => true,
);
$items[] = array(
'id' => 'ovkit-cache-browser',
'label' => $bust_label,
);
$items[] = array(
'id' => 'ovkit-cache-log',
'label' => __( 'Recent activity…', 'ovanap-admin-toolkit' ),
);
foreach ( $items as $item ) {
$classes = array( 'ovkit-cache-action' );
if ( ! empty( $item['aggressive'] ) ) {
$classes[] = 'ovkit-cache-aggressive';
}
$bar->add_node(
array(
'id' => $item['id'],
'parent' => 'ovkit-cache',
'title' => esc_html( $item['label'] ),
'href' => '#',
'meta' => array(
'class' => implode( ' ', $classes ),
'onclick' => 'return false;',
'tabindex' => '0',
),
)
);
}
// Read-only footer: multisite / persistent cache hints.
$hints = array();
if ( $env['multisite'] ) {
$hints[] = __( 'Multisite network', 'ovanap-admin-toolkit' );
}
if ( $env['persistent_object_cache'] ) {
/* translators: %s: object cache backend name */
$hints[] = sprintf( __( 'Persistent object cache: %s', 'ovanap-admin-toolkit' ), $env['object_cache_backend'] );
}
if ( $hints ) {
$bar->add_node(
array(
'id' => 'ovkit-cache-hint',
'parent' => 'ovkit-cache',
'title' => '' . esc_html( implode( ' · ', $hints ) ) . '',
'href' => false,
'meta' => array( 'class' => 'ovkit-cache-hint' ),
)
);
}
}
/**
* Bust label.
*
* @param int $version Version.
* @param string $human Human.
* @return string
*/
private function bust_label( int $version, string $human ): string {
if ( $version <= 0 ) {
return __( 'Bump Browser Cache (assets) — not yet set', 'ovanap-admin-toolkit' );
}
/* translators: 1: version integer, 2: human-readable time-ago */
return sprintf( __( 'Bump Browser Cache — v=%1$d (%2$s)', 'ovanap-admin-toolkit' ), $version, $human );
}
/**
* Label for.
*
* @param string $scope Scope.
* @param array $systems Systems.
* @param string $fallback Fallback.
* @return string
*/
private function label_for( string $scope, array $systems, string $fallback ): string {
$matching = array();
foreach ( $systems as $sys ) {
if ( in_array( $scope, $sys['scopes'], true ) ) {
$matching[] = $sys['label'];
}
}
if ( empty( $matching ) ) {
return $fallback;
}
/* translators: 1: base action label, 2: detected plugin name(s) */
return sprintf( __( '%1$s (%2$s)', 'ovanap-admin-toolkit' ), $fallback, implode( ', ', $matching ) );
}
// Assets.
/**
* Enqueue assets.
*/
public function enqueue_assets(): void {
if ( ! current_user_can( self::CAPABILITY ) ) {
return;
}
if ( ! is_admin_bar_showing() ) {
return;
}
wp_enqueue_style( 'ovkit-cache-manager', OVKIT_URL . 'assets/cache-manager/cache-manager.css', array(), OVKIT_VERSION );
wp_enqueue_script( 'ovkit-cache-manager', OVKIT_URL . 'assets/cache-manager/cache-manager.js', array(), OVKIT_VERSION, true );
$env = $this->get_environment();
wp_localize_script(
'ovkit-cache-manager',
'ovkitCacheManager',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'actions' => array(
'clear' => self::AJAX_CLEAR,
'log' => self::AJAX_LOG,
'env' => self::AJAX_ENV,
),
'nonce' => wp_create_nonce( self::NONCE_ACTION ),
'env' => $env,
'i18n' => array(
'working' => __( 'Clearing…', 'ovanap-admin-toolkit' ),
'previewing' => __( 'Previewing…', 'ovanap-admin-toolkit' ),
'loading' => __( 'Loading…', 'ovanap-admin-toolkit' ),
'done' => __( 'Cache cleared', 'ovanap-admin-toolkit' ),
'partial' => __( 'Finished with warnings', 'ovanap-admin-toolkit' ),
'failed' => __( 'Cache clear failed', 'ovanap-admin-toolkit' ),
'nothing' => __( 'Nothing to clear', 'ovanap-admin-toolkit' ),
'previewTitle' => __( 'Preview — would clear:', 'ovanap-admin-toolkit' ),
'previewEmpty' => __( 'Preview — nothing would be cleared', 'ovanap-admin-toolkit' ),
'runNow' => __( 'Clear now', 'ovanap-admin-toolkit' ),
'cancel' => __( 'Cancel', 'ovanap-admin-toolkit' ),
'netError' => __( 'Network error — check connection', 'ovanap-admin-toolkit' ),
'forbidden' => __( 'Not allowed', 'ovanap-admin-toolkit' ),
'confirmTitle' => __( 'This is an aggressive cache action', 'ovanap-admin-toolkit' ),
'confirmAggressiveAll' => __( 'Clear All — Aggressive also flushes the full object cache, rewrite rules, and OPcache. This is heavier than the safe run.', 'ovanap-admin-toolkit' ),
'confirmObjectFull' => __( 'Full object cache flush removes every entry from the configured backend.', 'ovanap-admin-toolkit' ),
'confirmRewrite' => __( 'Rewrite rules flush is an expensive operation on large sites.', 'ovanap-admin-toolkit' ),
/* translators: %s: object cache backend name */
'warnPersistent' => __( 'Persistent object cache detected (%s). A full flush may affect other sites sharing this backend.', 'ovanap-admin-toolkit' ),
'warnMultisite' => __( 'This is a multisite network — the flush affects every site.', 'ovanap-admin-toolkit' ),
'envTitle' => __( 'Detected environment', 'ovanap-admin-toolkit' ),
'logTitle' => __( 'Recent cache activity', 'ovanap-admin-toolkit' ),
'logEmpty' => __( 'No recorded activity yet', 'ovanap-admin-toolkit' ),
'status' => array(
'executed' => __( 'executed', 'ovanap-admin-toolkit' ),
'warning' => __( 'warning', 'ovanap-admin-toolkit' ),
'not_detected' => __( 'not detected', 'ovanap-admin-toolkit' ),
'failed' => __( 'failed', 'ovanap-admin-toolkit' ),
'skipped' => __( 'skipped', 'ovanap-admin-toolkit' ),
),
/* translators: 1: executed count, 2: warning count, 3: skipped count, 4: failed count */
'summaryFmt' => __( '%1$d executed · %2$d warnings · %3$d skipped · %4$d failed', 'ovanap-admin-toolkit' ),
'safeSkippedNote' => __( 'Aggressive items not run in safe mode: full object flush, rewrite rules, OPcache.', 'ovanap-admin-toolkit' ),
),
)
);
}
// Browser bust filter.
/**
* Filter asset src.
*
* @param mixed $src Src.
*/
public function filter_asset_src( $src ) {
if ( ! is_string( $src ) || '' === $src ) {
return $src;
}
if ( null === $this->bust_version ) {
$this->bust_version = (int) get_option( self::BUST_OPTION, 0 );
}
if ( $this->bust_version <= 0 ) {
return $src;
}
$home = home_url();
$site = site_url();
if ( strpos( $src, $home ) !== 0 && strpos( $src, $site ) !== 0 && strpos( $src, '/' ) !== 0 ) {
return $src;
}
return add_query_arg( 'ovkbust', $this->bust_version, $src );
}
// AJAX: clear.
/**
* Ajax clear.
*/
public function ajax_clear(): void {
if ( ! current_user_can( self::CAPABILITY ) ) {
wp_send_json_error( array( 'message' => __( 'Forbidden', 'ovanap-admin-toolkit' ) ), 403 );
}
check_ajax_referer( self::NONCE_ACTION, 'nonce' );
$scope = isset( $_POST['scope'] ) ? sanitize_key( wp_unslash( $_POST['scope'] ) ) : '';
$mode = isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( $_POST['mode'] ) ) : 'run';
$preview = ( 'preview' === $mode );
$allowed = array(
'all_safe',
'all_aggressive',
'object_runtime',
'object_full',
'transients',
'rewrite',
'opcache',
'page',
'builder',
'wordpress_core',
'woocommerce',
'browser_bust',
);
if ( ! in_array( $scope, $allowed, true ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid scope', 'ovanap-admin-toolkit' ) ), 400 );
}
try {
$results = $this->run( $scope, $preview );
} catch ( \Throwable $e ) {
if ( function_exists( 'ovkit_log' ) ) {
ovkit_log(
'Cache manager error: ' . $e->getMessage(),
array(
'scope' => $scope,
'mode' => $mode,
),
'error'
);
}
wp_send_json_error( array( 'message' => __( 'Cache clear failed', 'ovanap-admin-toolkit' ) ), 500 );
}
$summary = $this->summarize( $results );
// Notes communicate skipped-in-safe and aggressive warnings.
$notes = array();
if ( ! $preview && 'all_safe' === $scope ) {
$notes[] = __( 'Safe mode: full object flush, rewrite rules, and OPcache were not run.', 'ovanap-admin-toolkit' );
}
if ( ! $preview ) {
$this->append_log( $scope, $mode, $results, $summary );
}
wp_send_json_success(
array(
'scope' => $scope,
'mode' => $preview ? 'preview' : 'run',
'results' => $results,
'summary' => $summary,
'notes' => $notes,
)
);
}
// AJAX: log.
/**
* Ajax log.
*/
public function ajax_log(): void {
if ( ! current_user_can( self::CAPABILITY ) ) {
wp_send_json_error( array( 'message' => __( 'Forbidden', 'ovanap-admin-toolkit' ) ), 403 );
}
check_ajax_referer( self::NONCE_ACTION, 'nonce' );
$log = (array) get_option( self::LOG_OPTION, array() );
$log = array_reverse( $log ); // newest first.
foreach ( $log as &$entry ) {
if ( ! empty( $entry['time'] ) ) {
$entry['human'] = human_time_diff( (int) $entry['time'], time() );
}
}
wp_send_json_success( array( 'entries' => $log ) );
}
// AJAX: env.
/**
* Ajax env.
*/
public function ajax_env(): void {
if ( ! current_user_can( self::CAPABILITY ) ) {
wp_send_json_error( array( 'message' => __( 'Forbidden', 'ovanap-admin-toolkit' ) ), 403 );
}
check_ajax_referer( self::NONCE_ACTION, 'nonce' );
wp_send_json_success(
array(
'env' => $this->get_environment(),
'systems' => $this->detect_systems(),
)
);
}
// Environment.
/**
* Get environment.
*
* @return array
*/
public function get_environment(): array {
$persistent = function_exists( 'wp_using_ext_object_cache' ) && wp_using_ext_object_cache();
$backend = $persistent ? $this->detect_object_cache_backend() : 'none';
$bust = (int) get_option( self::BUST_OPTION, 0 );
return array(
'multisite' => is_multisite(),
'persistent_object_cache' => $persistent,
'object_cache_backend' => $backend,
'has_runtime_flush' => function_exists( 'wp_cache_flush_runtime' ),
'has_opcache' => function_exists( 'opcache_reset' ),
'server_software' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '',
'woocommerce_active' => class_exists( 'WooCommerce' ),
'bust_version' => $bust,
'bust_human' => $bust > 0 ? human_time_diff( $bust, time() ) . ' ' . __( 'ago', 'ovanap-admin-toolkit' ) : '',
);
}
/**
* Detect object cache backend.
*
* @return string
*/
private function detect_object_cache_backend(): string {
global $wp_object_cache;
if ( is_object( $wp_object_cache ) ) {
$class = get_class( $wp_object_cache );
if ( stripos( $class, 'redis' ) !== false ) {
return 'Redis';
}
if ( stripos( $class, 'memcached' ) !== false ) {
return 'Memcached';
}
if ( stripos( $class, 'memcache' ) !== false ) {
return 'Memcache';
}
if ( stripos( $class, 'apcu' ) !== false ) {
return 'APCu';
}
if ( stripos( $class, 'w3' ) !== false ) {
return 'W3 Total Cache';
}
return $class;
}
return 'Unknown';
}
// Orchestration.
/**
* Run.
*
* @param string $scope Scope.
* @param bool $preview Preview.
* @return array
*/
private function run( string $scope, bool $preview = false ): array {
$results = array();
$append = function ( array $rows ) use ( &$results ) {
foreach ( $rows as $r ) {
$results[] = $r;
}
};
$do = function ( string $which ) use ( $preview ) {
switch ( $which ) {
case 'object_runtime':
return array( $this->clear_object_runtime( $preview ) );
case 'object_full':
return $this->clear_object_full( $preview );
case 'transients':
return array( $this->clear_transients( $preview ) );
case 'rewrite':
return array( $this->clear_rewrite( $preview ) );
case 'opcache':
return array( $this->clear_opcache( $preview ) );
case 'page':
return $this->clear_page_cache( $preview );
case 'builder':
return $this->clear_builder_cache( $preview );
case 'wordpress_core':
return $this->clear_wordpress_core( $preview );
case 'woocommerce':
return $this->clear_woocommerce( $preview );
case 'browser_bust':
return array( $this->clear_browser_bust( $preview ) );
}
return array();
};
if ( 'all_safe' === $scope ) {
$append( $do( 'object_runtime' ) );
$append( $do( 'transients' ) );
$append( $do( 'wordpress_core' ) );
if ( class_exists( 'WooCommerce' ) ) {
$append( $do( 'woocommerce' ) );
}
$append( $do( 'page' ) );
$append( $do( 'builder' ) );
$append( $do( 'browser_bust' ) );
return $results;
}
if ( 'all_aggressive' === $scope ) {
$append( $do( 'object_runtime' ) );
$append( $do( 'object_full' ) );
$append( $do( 'transients' ) );
$append( $do( 'wordpress_core' ) );
if ( class_exists( 'WooCommerce' ) ) {
$append( $do( 'woocommerce' ) );
}
$append( $do( 'page' ) );
$append( $do( 'builder' ) );
$append( $do( 'rewrite' ) );
$append( $do( 'opcache' ) );
$append( $do( 'browser_bust' ) );
return $results;
}
$append( $do( $scope ) );
return $results;
}
/**
* Summarize.
*
* @param array $results Results.
* @return array
*/
private function summarize( array $results ): array {
$c = array(
'executed' => 0,
'warning' => 0,
'not_detected' => 0,
'failed' => 0,
'skipped' => 0,
);
foreach ( $results as $r ) {
$s = $r['status'] ?? '';
if ( isset( $c[ $s ] ) ) {
++$c[ $s ];
}
}
$c['total'] = count( $results );
return $c;
}
// Log.
/**
* Append log.
*
* @param string $scope Scope.
* @param string $mode Mode.
* @param array $results Results.
* @param array $summary Summary.
*/
private function append_log( string $scope, string $mode, array $results, array $summary ): void {
$user = wp_get_current_user();
$trimmed = array_map(
function ( $r ) {
return array(
'label' => isset( $r['label'] ) ? (string) $r['label'] : '',
'status' => isset( $r['status'] ) ? (string) $r['status'] : '',
'detail' => isset( $r['detail'] ) ? (string) $r['detail'] : '',
);
},
$results
);
$entry = array(
'time' => time(),
'user_id' => $user ? (int) $user->ID : 0,
'user_login' => $user ? (string) $user->user_login : '',
'scope' => $scope,
'mode' => $mode,
'summary' => $summary,
'items' => $trimmed,
);
$log = (array) get_option( self::LOG_OPTION, array() );
$log[] = $entry;
if ( count( $log ) > self::LOG_LIMIT ) {
$log = array_slice( $log, -self::LOG_LIMIT );
}
update_option( self::LOG_OPTION, $log, false );
}
// Object cache.
/**
* Clear object runtime.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_object_runtime( bool $preview = false ): array {
if ( ! function_exists( 'wp_cache_flush_runtime' ) ) {
return $this->row(
__( 'Object cache (runtime)', 'ovanap-admin-toolkit' ),
self::STATUS_NOT_DETECTED,
__( 'wp_cache_flush_runtime() unavailable (requires WP 6.1+ or supporting drop-in)', 'ovanap-admin-toolkit' )
);
}
if ( $preview ) {
return $this->row( __( 'Object cache (runtime)', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Would call wp_cache_flush_runtime()', 'ovanap-admin-toolkit' ) );
}
$ok = false;
try {
$ok = (bool) wp_cache_flush_runtime();
} catch ( \Throwable $e ) {
$ok = false; }
return $ok
? $this->row( __( 'Object cache (runtime)', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'In-request cache flushed', 'ovanap-admin-toolkit' ) )
: $this->row( __( 'Object cache (runtime)', 'ovanap-admin-toolkit' ), self::STATUS_FAILED, __( 'wp_cache_flush_runtime() refused', 'ovanap-admin-toolkit' ) );
}
/**
* Full flush — returns multiple rows so the persistent/multisite warning
* is surfaced alongside the actual flush result.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_object_full( bool $preview = false ): array {
$rows = array();
$env = $this->get_environment();
if ( $env['persistent_object_cache'] ) {
/* translators: %s: object cache backend name */
$rows[] = $this->row(
__( 'Shared backend warning', 'ovanap-admin-toolkit' ),
self::STATUS_WARNING,
/* translators: %s: object cache backend name */
sprintf( __( 'Persistent object cache (%s) — flush may affect other sites sharing this backend', 'ovanap-admin-toolkit' ), $env['object_cache_backend'] )
);
}
if ( $env['multisite'] ) {
$rows[] = $this->row(
__( 'Multisite warning', 'ovanap-admin-toolkit' ),
self::STATUS_WARNING,
__( 'Network-wide flush affects every site on this install', 'ovanap-admin-toolkit' )
);
}
if ( ! function_exists( 'wp_cache_flush' ) ) {
$rows[] = $this->row( __( 'Object cache (full)', 'ovanap-admin-toolkit' ), self::STATUS_FAILED, __( 'wp_cache_flush() unavailable', 'ovanap-admin-toolkit' ) );
return $rows;
}
if ( $preview ) {
$rows[] = $this->row( __( 'Object cache (full)', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Would call wp_cache_flush()', 'ovanap-admin-toolkit' ) );
return $rows;
}
$ok = false;
try {
$ok = (bool) wp_cache_flush();
} catch ( \Throwable $e ) {
$ok = false; }
$rows[] = $ok
? $this->row( __( 'Object cache (full)', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'All entries removed from configured backend', 'ovanap-admin-toolkit' ) )
: $this->row( __( 'Object cache (full)', 'ovanap-admin-toolkit' ), self::STATUS_FAILED, __( 'wp_cache_flush() refused', 'ovanap-admin-toolkit' ) );
return $rows;
}
// Transients.
/**
* Clear transients.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_transients( bool $preview = false ): array {
global $wpdb;
// An administrator explicitly asked to clear every transient. There is no
// core API for "all transients", and caching these counts would defeat the purpose.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->options}
WHERE option_name LIKE '\\_transient\\_%'
OR option_name LIKE '\\_site\\_transient\\_%'"
);
if ( is_multisite() && isset( $wpdb->sitemeta ) ) {
$count += (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->sitemeta}
WHERE meta_key LIKE '\\_site\\_transient\\_%'"
);
}
if ( $preview ) {
return $this->row(
__( 'Transients', 'ovanap-admin-toolkit' ),
self::STATUS_EXECUTED,
/* translators: %d: rows that would be removed */
sprintf( _n( '%d row would be removed', '%d rows would be removed', $count, 'ovanap-admin-toolkit' ), $count )
);
}
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '\\_transient\\_%'
OR option_name LIKE '\\_site\\_transient\\_%'"
);
if ( is_multisite() && isset( $wpdb->sitemeta ) ) {
$wpdb->query(
"DELETE FROM {$wpdb->sitemeta}
WHERE meta_key LIKE '\\_site\\_transient\\_%'"
);
}
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
if ( function_exists( 'wp_cache_delete' ) ) {
wp_cache_delete( 'alloptions', 'options' );
wp_cache_delete( 'notoptions', 'options' );
}
return $this->row(
__( 'Transients', 'ovanap-admin-toolkit' ),
self::STATUS_EXECUTED,
/* translators: %d: number of transient rows removed */
sprintf( _n( '%d row removed', '%d rows removed', $count, 'ovanap-admin-toolkit' ), $count )
);
}
// Rewrite / OPcache.
/**
* Clear rewrite.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_rewrite( bool $preview = false ): array {
if ( $preview ) {
return $this->row( __( 'Rewrite rules', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Would soft-flush (no .htaccess write)', 'ovanap-admin-toolkit' ) );
}
flush_rewrite_rules( false );
return $this->row( __( 'Rewrite rules', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Flushed (soft)', 'ovanap-admin-toolkit' ) );
}
/**
* Clear opcache.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_opcache( bool $preview = false ): array {
if ( ! function_exists( 'opcache_reset' ) ) {
return $this->row( __( 'OPcache', 'ovanap-admin-toolkit' ), self::STATUS_NOT_DETECTED, __( 'opcache_reset() unavailable on this server', 'ovanap-admin-toolkit' ) );
}
if ( $preview ) {
return $this->row( __( 'OPcache', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Would call opcache_reset()', 'ovanap-admin-toolkit' ) );
}
$ok = @opcache_reset(); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- opcache.restrict_api makes this emit a warning; failure is reported in the result row.
return $ok
? $this->row( __( 'OPcache', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Reset', 'ovanap-admin-toolkit' ) )
: $this->row( __( 'OPcache', 'ovanap-admin-toolkit' ), self::STATUS_FAILED, __( 'Refused (opcache.restrict_api?)', 'ovanap-admin-toolkit' ) );
}
// WordPress core extras.
/**
* Clear WordPress core.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_wordpress_core( bool $preview = false ): array {
$out = array();
$mf = ABSPATH . '.maintenance';
if ( file_exists( $mf ) ) {
if ( $preview ) {
$out[] = $this->row( __( '.maintenance file', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Would remove stuck maintenance file', 'ovanap-admin-toolkit' ) );
} else {
$removed = false;
try {
wp_delete_file( $mf );
$removed = ! file_exists( $mf );
} catch ( \Throwable $e ) {
$removed = false; }
$out[] = $removed
? $this->row( __( '.maintenance file', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Removed', 'ovanap-admin-toolkit' ) )
: $this->row( __( '.maintenance file', 'ovanap-admin-toolkit' ), self::STATUS_FAILED, __( 'Could not remove (filesystem permissions?)', 'ovanap-admin-toolkit' ) );
}
}
if ( get_site_transient( 'core_updater.lock' ) ) {
if ( ! $preview ) {
delete_site_transient( 'core_updater.lock' );
}
$out[] = $this->row( __( 'Core updater lock', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, $preview ? __( 'Would release', 'ovanap-admin-toolkit' ) : __( 'Released', 'ovanap-admin-toolkit' ) );
}
$update_keys = array( 'update_core', 'update_plugins', 'update_themes' );
$released = array();
foreach ( $update_keys as $k ) {
if ( get_site_transient( $k ) !== false ) {
if ( ! $preview ) {
delete_site_transient( $k );
}
$released[] = $k;
}
}
if ( ! empty( $released ) ) {
$out[] = $this->row(
__( 'Update check cache', 'ovanap-admin-toolkit' ),
self::STATUS_EXECUTED,
( $preview ? __( 'Would invalidate: ', 'ovanap-admin-toolkit' ) : __( 'Invalidated: ', 'ovanap-admin-toolkit' ) ) . implode( ', ', $released )
);
}
if ( get_transient( 'health-check-site-status-result' ) !== false ) {
if ( ! $preview ) {
delete_transient( 'health-check-site-status-result' );
}
$out[] = $this->row( __( 'Site Health status', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, $preview ? __( 'Would invalidate', 'ovanap-admin-toolkit' ) : __( 'Invalidated', 'ovanap-admin-toolkit' ) );
}
if ( class_exists( 'SimplePie_Cache' ) && method_exists( 'SimplePie_Cache', 'flush' ) ) {
if ( ! $preview ) {
try {
\SimplePie_Cache::flush();
} catch ( \Throwable $e ) {
// Best effort: the row below still reports the attempt.
unset( $e );
}
}
$out[] = $this->row( __( 'SimplePie feed cache', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, $preview ? __( 'Would flush', 'ovanap-admin-toolkit' ) : __( 'Flushed', 'ovanap-admin-toolkit' ) );
}
$cutoff = gmdate( 'Y-m-d H:i:s', strtotime( '-7 days' ) );
$orphan_ids = get_posts(
array(
'post_type' => 'customize_changeset',
'post_status' => array( 'auto-draft', 'trash' ),
'posts_per_page' => 50,
'fields' => 'ids',
'date_query' => array(
array(
'before' => $cutoff,
'column' => 'post_modified_gmt',
),
),
'no_found_rows' => true,
)
);
$orphan_count = is_array( $orphan_ids ) ? count( $orphan_ids ) : 0;
if ( $orphan_count > 0 ) {
if ( ! $preview ) {
foreach ( $orphan_ids as $id ) {
wp_delete_post( (int) $id, true );
}
}
$out[] = $this->row(
__( 'Customizer drafts', 'ovanap-admin-toolkit' ),
self::STATUS_EXECUTED,
sprintf(
$preview
/* translators: %d: number of stale customizer changesets */
? _n( '%d stale changeset would be removed', '%d stale changesets would be removed', $orphan_count, 'ovanap-admin-toolkit' )
/* translators: %d: number of stale customizer changesets */
: _n( '%d stale changeset removed', '%d stale changesets removed', $orphan_count, 'ovanap-admin-toolkit' ),
$orphan_count
)
);
}
if ( empty( $out ) ) {
$out[] = $this->row( __( 'WordPress core caches', 'ovanap-admin-toolkit' ), self::STATUS_NOT_DETECTED, __( 'No stale core state detected', 'ovanap-admin-toolkit' ) );
}
return apply_filters( 'ovkit_cache_manager_wordpress_core_results', $out, $preview );
}
// WooCommerce.
/**
* Clear woocommerce.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_woocommerce( bool $preview = false ): array {
$out = array();
if ( ! class_exists( 'WooCommerce' ) ) {
$out[] = $this->row( 'WooCommerce', self::STATUS_NOT_DETECTED, __( 'Not active', 'ovanap-admin-toolkit' ) );
return $out;
}
if ( class_exists( 'WC_Cache_Helper' ) && method_exists( 'WC_Cache_Helper', 'get_transient_version' ) ) {
if ( $preview ) {
$out[] = $this->row( __( 'WC product transient version', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Would bump', 'ovanap-admin-toolkit' ) );
$out[] = $this->row( __( 'WC order transient version', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Would bump', 'ovanap-admin-toolkit' ) );
} else {
try {
\WC_Cache_Helper::get_transient_version( 'product', true );
$out[] = $this->row( __( 'WC product transient version', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Bumped (compatibility layer)', 'ovanap-admin-toolkit' ) );
} catch ( \Throwable $e ) {
$out[] = $this->row( __( 'WC product transient version', 'ovanap-admin-toolkit' ), self::STATUS_FAILED, $e->getMessage() );
}
try {
\WC_Cache_Helper::get_transient_version( 'orders', true );
$out[] = $this->row( __( 'WC order transient version', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Bumped (compatibility layer)', 'ovanap-admin-toolkit' ) );
} catch ( \Throwable $e ) {
$out[] = $this->row( __( 'WC order transient version', 'ovanap-admin-toolkit' ), self::STATUS_FAILED, $e->getMessage() );
}
}
}
if ( get_transient( 'wc_count_comments' ) !== false ) {
if ( ! $preview ) {
delete_transient( 'wc_count_comments' );
}
$out[] = $this->row( __( 'WC review counts', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, $preview ? __( 'Would invalidate', 'ovanap-admin-toolkit' ) : __( 'Invalidated', 'ovanap-admin-toolkit' ) );
}
if ( function_exists( 'wc_delete_expired_transients' ) ) {
if ( $preview ) {
$out[] = $this->row( __( 'WC expired transients', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Would run expired-transient cleanup', 'ovanap-admin-toolkit' ) );
} else {
try {
wc_delete_expired_transients();
$out[] = $this->row( __( 'WC expired transients', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, __( 'Cleanup ran', 'ovanap-admin-toolkit' ) );
} catch ( \Throwable $e ) {
$out[] = $this->row( __( 'WC expired transients', 'ovanap-admin-toolkit' ), self::STATUS_FAILED, $e->getMessage() );
}
}
}
return apply_filters( 'ovkit_cache_manager_woocommerce_results', $out, $preview );
}
// Browser bust.
/**
* Clear browser bust.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_browser_bust( bool $preview = false ): array {
$current = (int) get_option( self::BUST_OPTION, 0 );
if ( $preview ) {
return $this->row(
__( 'Browser asset bust', 'ovanap-admin-toolkit' ),
self::STATUS_EXECUTED,
$current
? __( 'Would bump ?ovkbust version on same-origin assets', 'ovanap-admin-toolkit' )
: __( 'Would initialise ?ovkbust version', 'ovanap-admin-toolkit' )
);
}
$new = time();
update_option( self::BUST_OPTION, $new, false );
$this->bust_version = $new;
/* translators: %s: version timestamp */
return $this->row( __( 'Browser asset bust', 'ovanap-admin-toolkit' ), self::STATUS_EXECUTED, sprintf( __( 'Version set to %s', 'ovanap-admin-toolkit' ), (string) $new ) );
}
/**
* Purge page caches after the toolkit's settings change, so front-end switches
* (announcement bar, tracking, head cleanup) show up at once on cached sites.
* Runs without booting the admin-bar tool and is not written to its log.
*
* @return string[] Names of the page caches that were purged.
*/
public function purge_pages_after_settings_change(): array {
$names = array();
foreach ( $this->clear_page_cache( false ) as $row ) {
if ( self::STATUS_EXECUTED === ( $row['status'] ?? '' ) ) {
$names[] = (string) $row['label'];
}
}
return $names;
}
// Page cache vendors.
/**
* Clear page cache.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_page_cache( bool $preview = false ): array {
$out = array();
$try = function ( string $label, callable $probe, callable $action, string $detail_ok ) use ( &$out, $preview ) {
if ( ! $probe() ) {
return;
}
if ( $preview ) {
$out[] = $this->row( $label, self::STATUS_EXECUTED, __( 'Would purge', 'ovanap-admin-toolkit' ) );
return;
}
try {
$action();
$out[] = $this->row( $label, self::STATUS_EXECUTED, $detail_ok );
} catch ( \Throwable $e ) {
$out[] = $this->row( $label, self::STATUS_FAILED, $e->getMessage() );
}
};
$try(
'WP Rocket',
fn() => function_exists( 'rocket_clean_domain' ),
function () {
rocket_clean_domain();
if ( function_exists( 'rocket_clean_minify' ) ) {
rocket_clean_minify();
}
if ( function_exists( 'rocket_clean_cache_busting' ) ) {
rocket_clean_cache_busting();
}
},
__( 'Domain + minify cache cleaned', 'ovanap-admin-toolkit' )
);
$try(
'W3 Total Cache',
fn() => function_exists( 'w3tc_flush_all' ) || function_exists( 'w3tc_pgcache_flush' ),
function () {
if ( function_exists( 'w3tc_flush_all' ) ) {
w3tc_flush_all();
} else {
w3tc_pgcache_flush();
}
},
__( 'Flushed', 'ovanap-admin-toolkit' )
);
$try(
'WP Super Cache',
fn() => function_exists( 'wp_cache_clear_cache' ),
fn() => wp_cache_clear_cache(),
__( 'Cache cleared', 'ovanap-admin-toolkit' )
);
$try(
'LiteSpeed Cache',
fn() => class_exists( '\LiteSpeed\Purge' ) || has_action( 'litespeed_purge_all' ),
fn() => do_action( 'litespeed_purge_all' ),
__( 'Purge signal fired', 'ovanap-admin-toolkit' )
);
$server = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '';
if ( stripos( $server, 'litespeed' ) !== false && ! headers_sent() ) {
if ( $preview ) {
$out[] = $this->row( 'LiteSpeed server', self::STATUS_EXECUTED, __( 'Would send X-LiteSpeed-Purge header', 'ovanap-admin-toolkit' ) );
} else {
header( 'X-LiteSpeed-Purge: *' );
$out[] = $this->row( 'LiteSpeed server', self::STATUS_EXECUTED, __( 'Sent X-LiteSpeed-Purge: *', 'ovanap-admin-toolkit' ) );
}
}
$try(
'SG Optimizer',
fn() => function_exists( 'sg_cachepress_purge_cache' ),
fn() => sg_cachepress_purge_cache(),
__( 'Cache purged', 'ovanap-admin-toolkit' )
);
$try(
'Hummingbird',
fn() => has_action( 'wphb_clear_page_cache' ),
fn() => do_action( 'wphb_clear_page_cache' ),
__( 'Page cache cleared', 'ovanap-admin-toolkit' )
);
$try(
'Cache Enabler',
fn() => class_exists( 'Cache_Enabler' ) && method_exists( 'Cache_Enabler', 'clear_complete_cache' ),
fn() => \Cache_Enabler::clear_complete_cache(),
__( 'Cache cleared', 'ovanap-admin-toolkit' )
);
$try(
'WP Fastest Cache',
fn() => class_exists( 'WpFastestCache' ),
function () {
$wpfc = new \WpFastestCache();
if ( method_exists( $wpfc, 'deleteCache' ) ) {
$wpfc->deleteCache( true );
}
},
__( 'Cache deleted', 'ovanap-admin-toolkit' )
);
if ( empty( $out ) ) {
$out[] = $this->row( __( 'Page cache', 'ovanap-admin-toolkit' ), self::STATUS_NOT_DETECTED, __( 'No supported page cache plugin detected', 'ovanap-admin-toolkit' ) );
}
return apply_filters( 'ovkit_cache_manager_page_cache_results', $out, $preview );
}
// Builder cache.
/**
* Clear builder cache.
*
* @param bool $preview Preview.
* @return array
*/
private function clear_builder_cache( bool $preview = false ): array {
$out = array();
if ( class_exists( '\Elementor\Plugin' ) ) {
$el = \Elementor\Plugin::$instance ?? null;
if ( $el && isset( $el->files_manager ) && method_exists( $el->files_manager, 'clear_cache' ) ) {
if ( $preview ) {
$out[] = $this->row( 'Elementor', self::STATUS_EXECUTED, __( 'Would clear generated CSS', 'ovanap-admin-toolkit' ) );
} else {
try {
$el->files_manager->clear_cache();
$out[] = $this->row( 'Elementor', self::STATUS_EXECUTED, __( 'Generated CSS cleared', 'ovanap-admin-toolkit' ) );
} catch ( \Throwable $e ) {
$out[] = $this->row( 'Elementor', self::STATUS_FAILED, $e->getMessage() );
}
}
}
}
$gb_class = class_exists( 'GenerateBlocks_Dynamic_CSS' ) && method_exists( 'GenerateBlocks_Dynamic_CSS', 'flush' );
$gb_opt = get_option( 'generateblocks_dynamic_css_version' ) !== false;
if ( $gb_class || $gb_opt ) {
if ( $preview ) {
$out[] = $this->row( 'GenerateBlocks', self::STATUS_EXECUTED, __( 'Would flush dynamic CSS', 'ovanap-admin-toolkit' ) );
} else {
try {
if ( $gb_class ) {
\GenerateBlocks_Dynamic_CSS::flush();
$out[] = $this->row( 'GenerateBlocks', self::STATUS_EXECUTED, __( 'Dynamic CSS flushed', 'ovanap-admin-toolkit' ) );
} else {
update_option( 'generateblocks_dynamic_css_version', time() );
$out[] = $this->row( 'GenerateBlocks', self::STATUS_EXECUTED, __( 'Dynamic CSS version bumped', 'ovanap-admin-toolkit' ) );
}
} catch ( \Throwable $e ) {
$out[] = $this->row( 'GenerateBlocks', self::STATUS_FAILED, $e->getMessage() );
}
}
}
if ( class_exists( 'autoptimizeCache' ) && method_exists( 'autoptimizeCache', 'clearall' ) ) {
if ( $preview ) {
$out[] = $this->row( 'Autoptimize', self::STATUS_EXECUTED, __( 'Would clear all caches', 'ovanap-admin-toolkit' ) );
} else {
try {
\autoptimizeCache::clearall();
$out[] = $this->row( 'Autoptimize', self::STATUS_EXECUTED, __( 'All caches cleared', 'ovanap-admin-toolkit' ) );
} catch ( \Throwable $e ) {
$out[] = $this->row( 'Autoptimize', self::STATUS_FAILED, $e->getMessage() );
}
}
}
if ( empty( $out ) ) {
$out[] = $this->row( __( 'Builder / asset cache', 'ovanap-admin-toolkit' ), self::STATUS_NOT_DETECTED, __( 'No supported builder/asset cache detected', 'ovanap-admin-toolkit' ) );
}
return apply_filters( 'ovkit_cache_manager_builder_cache_results', $out, $preview );
}
// Detection.
/**
* Detect systems.
*
* @return array
*/
public function detect_systems(): array {
$found = array();
if ( function_exists( 'rocket_clean_domain' ) ) {
$found[] = array(
'key' => 'wp_rocket',
'label' => 'WP Rocket',
'scopes' => array( 'page' ),
);
}
if ( function_exists( 'w3tc_flush_all' ) || function_exists( 'w3tc_pgcache_flush' ) ) {
$found[] = array(
'key' => 'w3tc',
'label' => 'W3 Total Cache',
'scopes' => array( 'page' ),
);
}
if ( function_exists( 'wp_cache_clear_cache' ) ) {
$found[] = array(
'key' => 'wp_super_cache',
'label' => 'WP Super Cache',
'scopes' => array( 'page' ),
);
}
if ( class_exists( '\LiteSpeed\Purge' ) || has_action( 'litespeed_purge_all' ) ) {
$found[] = array(
'key' => 'litespeed',
'label' => 'LiteSpeed Cache',
'scopes' => array( 'page' ),
);
}
if ( function_exists( 'sg_cachepress_purge_cache' ) ) {
$found[] = array(
'key' => 'sg_optimizer',
'label' => 'SG Optimizer',
'scopes' => array( 'page' ),
);
}
if ( has_action( 'wphb_clear_page_cache' ) ) {
$found[] = array(
'key' => 'hummingbird',
'label' => 'Hummingbird',
'scopes' => array( 'page' ),
);
}
if ( class_exists( 'Cache_Enabler' ) ) {
$found[] = array(
'key' => 'cache_enabler',
'label' => 'Cache Enabler',
'scopes' => array( 'page' ),
);
}
if ( class_exists( 'WpFastestCache' ) ) {
$found[] = array(
'key' => 'wp_fastest',
'label' => 'WP Fastest Cache',
'scopes' => array( 'page' ),
);
}
if ( class_exists( '\Elementor\Plugin' ) ) {
$found[] = array(
'key' => 'elementor',
'label' => 'Elementor',
'scopes' => array( 'builder' ),
);
}
if ( class_exists( 'GenerateBlocks_Dynamic_CSS' ) || get_option( 'generateblocks_dynamic_css_version' ) !== false ) {
$found[] = array(
'key' => 'generateblocks',
'label' => 'GenerateBlocks',
'scopes' => array( 'builder' ),
);
}
if ( class_exists( 'autoptimizeCache' ) ) {
$found[] = array(
'key' => 'autoptimize',
'label' => 'Autoptimize',
'scopes' => array( 'builder' ),
);
}
if ( class_exists( 'WooCommerce' ) ) {
$found[] = array(
'key' => 'woocommerce',
'label' => 'WooCommerce',
'scopes' => array( 'woocommerce' ),
);
}
$server = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '';
if ( stripos( $server, 'litespeed' ) !== false ) {
$found[] = array(
'key' => 'litespeed_srv',
'label' => 'LiteSpeed server',
'scopes' => array( 'page' ),
);
}
return $found;
}
}