$this->slug(), 'risk' => 'low', 'summary' => "Clears the site's server-side caches so visitors see current content: " . 'the page cache (12 supported plugins), the object cache, and expired transients. ' . 'Call after content or configuration changes when stale output is suspected. ' . 'Dry-run lists which cache layers are detected without clearing anything.', 'annotations' => array( 'destructive' => false, 'idempotent' => true, ), 'input_schema' => array( 'type' => 'object', 'properties' => array( 'dry_run' => array( 'type' => 'boolean', 'default' => false, ), ), ), ); } public function dry_run( $atb_input ) { return $this->build_output( false ); } public function run( $atb_input ) { return $this->build_output( true ); } /** * Builds the common output for either a dry run or a real execution. * Every layer is evaluated the same way regardless of mode; only * $atb_execute decides whether purge calls actually fire. * * @param bool $atb_execute * @return array */ protected function build_output( $atb_execute ) { $atb_layers = array( $this->page_cache_layer( $atb_execute ), $this->object_cache_layer( $atb_execute ), $this->transients_layer( $atb_execute ), $this->opcache_layer(), ); $atb_performed = false; foreach ( $atb_layers as $atb_layer ) { if ( ! empty( $atb_layer['purged'] ) ) { $atb_performed = true; break; } } return $this->output( $atb_performed, ! $atb_execute, array( 'layers' => $atb_layers ) ); } /* -------------------------------------------------------------------- * Layers * ------------------------------------------------------------------ */ protected function page_cache_layer( $atb_execute ) { $atb_plugin = self::detect_page_cache_plugin(); if ( '' === $atb_plugin ) { return array( 'layer' => 'page-cache', 'detected' => false, 'purged' => false, 'reason' => 'no supported page cache plugin detected', ); } if ( ! $atb_execute ) { return array( 'layer' => 'page-cache', 'detected' => true, 'purged' => false, 'reason' => 'detected: ' . $atb_plugin, ); } try { $atb_purged = self::dispatch_page_cache_purge( $atb_plugin ); } catch ( \Throwable $atb_e ) { // A misbehaving cache plugin must never break the caller. return array( 'layer' => 'page-cache', 'detected' => true, 'purged' => false, 'reason' => 'purge failed: ' . $atb_plugin, ); } return array( 'layer' => 'page-cache', 'detected' => true, 'purged' => $atb_purged, 'reason' => $atb_purged ? ( 'purged: ' . $atb_plugin ) : ( 'purge function unavailable: ' . $atb_plugin ), ); } protected function object_cache_layer( $atb_execute ) { $atb_detected = function_exists( 'wp_using_ext_object_cache' ) && wp_using_ext_object_cache(); if ( ! $atb_execute ) { return array( 'layer' => 'object-cache', 'detected' => (bool) $atb_detected, 'purged' => false, 'reason' => $atb_detected ? 'external object cache detected' : 'no external object cache detected', ); } $atb_purged = function_exists( 'wp_cache_flush' ) ? (bool) wp_cache_flush() : false; return array( 'layer' => 'object-cache', 'detected' => (bool) $atb_detected, 'purged' => $atb_purged, 'reason' => $atb_purged ? 'flushed' : 'wp_cache_flush unavailable', ); } protected function transients_layer( $atb_execute ) { if ( ! $atb_execute ) { return array( 'layer' => 'transients', 'detected' => true, 'purged' => false, 'reason' => 'would delete expired transients', ); } if ( function_exists( 'delete_expired_transients' ) ) { delete_expired_transients(); return array( 'layer' => 'transients', 'detected' => true, 'purged' => true, 'reason' => 'expired transients deleted', ); } return array( 'layer' => 'transients', 'detected' => true, 'purged' => false, 'reason' => 'delete_expired_transients unavailable', ); } protected function opcache_layer() { return array( 'layer' => 'opcache', 'detected' => function_exists( 'opcache_reset' ), 'purged' => false, 'reason' => 'skipped', ); } /* -------------------------------------------------------------------- * Page-cache detection + dispatch — ported from * Prevent_Browser_Caching::get_active_page_cache_plugin() / * Prevent_Browser_Caching_Integrations::dispatch_purge(), verified * against current wp.org releases (v3.1-planning/03-research §2). * ------------------------------------------------------------------ */ /** * Returns the display name of the first detected page-cache plugin, or * '' if none of the 12 supported plugins is active. * * @return string */ public static function detect_page_cache_plugin() { $atb_known = array( 'WP Rocket' => defined( 'WP_ROCKET_VERSION' ), 'LiteSpeed Cache' => defined( 'LSCWP_V' ), 'W3 Total Cache' => defined( 'W3TC' ), 'WP Super Cache' => function_exists( 'wp_cache_serve_cache_file' ) || defined( 'WPCACHEHOME' ), 'WP Fastest Cache' => class_exists( 'WpFastestCache' ), 'WP-Optimize' => class_exists( 'WP_Optimize' ), 'Breeze' => defined( 'BREEZE_VERSION' ), 'Cache Enabler' => class_exists( 'Cache_Enabler' ), 'Hummingbird' => class_exists( 'Hummingbird\\WP_Hummingbird' ) || class_exists( 'WP_Hummingbird' ), 'SiteGround Optimizer' => defined( 'SiteGround_Optimizer\\VERSION' ), 'Swift Performance' => class_exists( 'Swift_Performance' ) || class_exists( 'Swift_Performance_Lite' ), 'Comet Cache' => class_exists( 'comet_cache' ), ); foreach ( $atb_known as $atb_name => $atb_active ) { if ( $atb_active ) { return $atb_name; } } return ''; } /** * Dispatches the verified purge call for a detected plugin name. * * @param string $atb_plugin * @return bool True when a purge call ran. */ public static function dispatch_page_cache_purge( $atb_plugin ) { switch ( $atb_plugin ) { case 'WP Rocket': if ( function_exists( 'rocket_clean_domain' ) ) { rocket_clean_domain(); return true; } break; case 'LiteSpeed Cache': if ( has_action( 'litespeed_purge_all' ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- third-party cache plugin's own purge hook. do_action( 'litespeed_purge_all', 'Agent Toolbelt' ); return true; } break; case 'W3 Total Cache': // Page cache only; flush_all would also wipe minify/object/CDN. if ( function_exists( 'w3tc_flush_posts' ) ) { w3tc_flush_posts(); return true; } break; case 'WP Super Cache': if ( function_exists( 'wp_cache_clear_cache' ) ) { wp_cache_clear_cache(); return true; } break; case 'WP Fastest Cache': if ( has_action( 'wpfc_clear_all_cache' ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- third-party cache plugin's own purge hook. do_action( 'wpfc_clear_all_cache' ); return true; } break; case 'WP-Optimize': if ( function_exists( 'WP_Optimize' ) ) { $atb_wpo = WP_Optimize(); if ( is_object( $atb_wpo ) && method_exists( $atb_wpo, 'get_page_cache' ) ) { $atb_cache = $atb_wpo->get_page_cache(); if ( is_object( $atb_cache ) && method_exists( $atb_cache, 'purge' ) ) { $atb_cache->purge(); return true; } } } break; case 'Breeze': if ( has_action( 'breeze_clear_all_cache' ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- third-party cache plugin's own purge hook. do_action( 'breeze_clear_all_cache' ); return true; } break; case 'Cache Enabler': if ( method_exists( 'Cache_Enabler', 'clear_complete_cache' ) ) { Cache_Enabler::clear_complete_cache(); return true; } break; case 'Hummingbird': if ( has_action( 'wphb_clear_page_cache' ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- third-party cache plugin's own purge hook. do_action( 'wphb_clear_page_cache' ); return true; } break; case 'SiteGround Optimizer': if ( function_exists( 'sg_cachepress_purge_cache' ) ) { sg_cachepress_purge_cache(); return true; } break; case 'Swift Performance': if ( class_exists( 'Swift_Performance_Cache' ) && method_exists( 'Swift_Performance_Cache', 'clear_all_cache' ) ) { Swift_Performance_Cache::clear_all_cache(); return true; } break; case 'Comet Cache': // clear() = current site; wipe() = whole network (don't). if ( method_exists( 'comet_cache', 'clear' ) ) { comet_cache::clear(); return true; } break; } return false; } } }