Files
kevin.bataille d2be239c3f fix: address Phase 1 code review findings
- Serve cache via template_redirect so feeds/404/REST are excluded and
  query conditionals work
- Bypass cache on query strings; key files by md5(host+path) to avoid
  collisions and support multisite
- Write wp-content/wp-recache-cache.php drop-in server and set WP_CACHE
  in wp-config.php so advanced-cache.php actually runs
- Only remove advanced-cache.php on deactivation when it is ours
- Respect DONOTCACHEPAGE, non-200 responses; bots read but never warm
  cache
- Fix comment purge (comment_post_ID) and heartbeat purge ($data arg)
- Fix exclude URLs textarea parsing; purge all when exclusions change
- Enqueue admin purge script on every admin-bar page, not just settings
- Sync tests, add TESTING.md manual test guide
2026-09-13 13:44:30 +02:00

180 lines
4.5 KiB
PHP

<?php
/**
* Core page cache functionality.
*
* @package WP_Recache
*/
defined('ABSPATH') || exit;
class WP_Recache_WPCache {
/**
* Whether this request started our output buffer.
*
* @var bool
*/
private $buffer_started = false;
/**
* Initialize cache hooks.
*/
public function init() {
// template_redirect: query conditionals (is_feed/is_404/is_preview) are
// available here, and REST/AJAX requests never reach it.
add_action('template_redirect', [$this, 'start_output_buffering'], 0);
add_action('shutdown', [$this, 'end_output_buffering']);
add_action('save_post', [$this, 'purge_post_cache']);
add_action('wp_insert_comment', [$this, 'purge_comment_cache']);
add_filter('heartbeat_received', [$this, 'purge_cache_onheartbeat'], 10, 2);
}
/**
* Start output buffering.
*/
public function start_output_buffering() {
if (wp_recache_should_exclude()) {
return;
}
if (wp_recache_is_cache_enabled()) {
$this->serve_cached_page();
}
}
/**
* Serve cached page if available.
*/
private function serve_cached_page() {
$url = $this->get_current_url();
$cache_path = wp_recache_get_cache_path_with_device($url);
if (file_exists($cache_path)) {
$modified_time = filemtime($cache_path);
$cache_duration = wp_recache_get_option('cache_duration', 3600);
if ((time() - $modified_time) < $cache_duration) {
header('X-WP-Recache: HIT');
header('X-WP-Recache-Time: ' . date('Y-m-d H:i:s', $modified_time));
readfile($cache_path);
exit;
}
}
// Bots get served warm cache but never warm it themselves.
if (wp_recache_is_robots()) {
return;
}
header('X-WP-Recache: MISS');
ob_start([$this, 'cache_output']);
$this->buffer_started = true;
}
/**
* Cache the output.
*
* @param string $output Page output.
* @return string Original output.
*/
public function cache_output($output) {
if (empty($output)) {
return $output;
}
// Respect opt-outs from WordPress and third-party plugins.
if (defined('DONOTCACHEPAGE') && DONOTCACHEPAGE) {
return $output;
}
$status = http_response_code();
if ($status !== false && $status !== 200) {
return $output;
}
if (wp_recache_is_robots()) {
return $output;
}
$url = $this->get_current_url();
$cache_path = wp_recache_get_cache_path_with_device($url);
$cache_dir = dirname($cache_path);
if (!is_dir($cache_dir)) {
wp_mkdir_p($cache_dir);
}
file_put_contents($cache_path, $output);
return $output;
}
/**
* End output buffering.
*/
public function end_output_buffering() {
if ($this->buffer_started && ob_get_level()) {
ob_end_flush();
}
}
/**
* Purge cache for a post.
*
* @param int $post_id Post ID.
*/
public function purge_post_cache($post_id) {
$url = get_permalink($post_id);
if ($url) {
wp_recache_delete_cache($url);
}
wp_recache_delete_cache(home_url('/'));
}
/**
* Purge cache for the post a comment belongs to.
*
* @param int $comment_id Comment ID.
*/
public function purge_comment_cache($comment_id) {
$comment = get_comment($comment_id);
if ($comment) {
$this->purge_post_cache($comment->comment_post_ID);
}
}
/**
* Purge cache on heartbeat.
*
* @param array $response Heartbeat response.
* @param array $data Data sent with the heartbeat request.
* @return array Modified response.
*/
public function purge_cache_onheartbeat($response, $data) {
if (isset($data['wp_recache_purge'])) {
wp_recache_delete_all_cache();
$response['wp_recache_purged'] = true;
}
return $response;
}
/**
* Get current request URL.
*
* @return string Current URL.
*/
private function get_current_url() {
$protocol = is_ssl() ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? '';
$uri = $_SERVER['REQUEST_URI'] ?? '/';
return $protocol . '://' . $host . $uri;
}
}