Files
2026-07-02 15:54:39 -06:00

487 lines
15 KiB
PHP

<?php
/*
* Plugin name: Batcache Manager
* Plugin URI: http://www.github.com/spacedmonkey/batcache-manager
* Description: Cache clearing for batcache
* Author: Jonathan Harris
* Author URI: http://www.jonathandavidharris.co.uk
* Version: 2.0.2
*/
/**
* Class Batcache_Manager
*/
class Batcache_Manager {
/**
* List of feeds
*
* @since 2.0.0
*
* @var array
*/
private $feeds = array( 'rss', 'rss2', 'rdf', 'atom' );
/**
* List of links to process
*
* @since 2.0.0
*
* @var array
*/
private $links = array();
/**
* Instance of this class.
*
* @since 2.0.0
*
* @var object
*/
protected static $instance = null;
/**
*
*/
private function __construct() {
global $batcache, $wp_object_cache;
// Do not load if our advanced-cache.php isn't loaded
if ( ! isset( $batcache ) || ! is_object( $batcache ) || ! method_exists( $wp_object_cache, 'incr' ) ) {
return;
}
$batcache->configure_groups();
// Posts
add_action( 'clean_post_cache', array( $this, 'action_clean_post_cache' ), 15 );
// Terms
add_action( 'clean_term_cache', array( $this, 'action_clean_term_cache' ), 10, 3 );
//Comments
add_action( 'clean_comment_cache', array( $this, 'action_update_comment' ) ); // Only supported in 4.5
add_action( 'comment_post', array( $this, 'action_update_comment' ) );
add_action( 'wp_set_comment_status', array( $this, 'action_update_comment' ) );
add_action( 'edit_comment', array( $this, 'action_update_comment' ) );
// Users
add_action( 'clean_user_cache', array( $this, 'action_update_user' ) );
add_action( 'profile_update', array( $this, 'action_update_user' ) );
// Widgets
add_filter( 'widget_update_callback', array( $this, 'action_update_widget' ), 50 );
// Customiser
add_action( 'customize_save_after', array( $this, 'flush_all' ) );
// Theme
add_action( 'switch_theme', array( $this, 'flush_all' ) );
// Nav
add_action( 'wp_update_nav_menu', array( $this, 'flush_all' ) );
// Add site aliases to list of links
add_filter( 'batcache_manager_links', array( $this, 'add_site_alias' ) );
// Do the flush of the urls on shutdown
add_action( 'shutdown', array( $this, 'clear_urls' ) );
}
/**
* Return an instance of this class.
*
* @since 2.0.0
*
* @return object A single instance of this class.
*/
public static function get_instance() {
// If the single instance hasn't been set, set it now.
if ( null == self::$instance ) {
self::$instance = new self;
}
return self::$instance;
}
/**
* Determines whether a post type is considered "viewable".
*
* For built-in post types such as posts and pages, the 'public' value will be evaluated.
* For all others, the 'publicly_queryable' value will be used.
*
*
* @param string $post_type Post type.
*
* @return bool Whether the post type should be considered viewable.
*/
public function is_post_type_viewable( $post_type ) {
$post_type_object = get_post_type_object( $post_type );
if ( empty( $post_type_object ) ) {
return false;
}
return $post_type_object->publicly_queryable || ( $post_type_object->_builtin && $post_type_object->public );
}
/**
* Whether the taxonomy object is public.
*
* Checks to make sure that the taxonomy is an object first. Then Gets the
* object, and finally returns the public value in the object.
*
* A false return value might also mean that the taxonomy does not exist.
*
* @since 2.0.0
*
* @param string $taxonomy Name of taxonomy object.
*
* @return bool Whether the taxonomy is public.
*/
function is_taxonomy_viewable( $taxonomy ) {
if ( ! taxonomy_exists( $taxonomy ) ) {
return false;
}
$taxonomy = get_taxonomy( $taxonomy );
return $taxonomy->public;
}
/**
* Clear post on post update
*
* @param $post_id
*/
public function action_clean_post_cache( $post_id ) {
$post = get_post( $post_id );
if ( $post && $post->post_type && ! $this->is_post_type_viewable( $post->post_type ) || ! in_array( get_post_status( $post_id ), array( 'publish', 'trash' ) ) ) {
return;
}
// Only flush the permalink of the specific post that changed.
// Date archives, author archives, feeds, and the homepage are intentionally
// NOT flushed here — those shared URLs should only be invalidated on a
// full manual flush, not on every individual page save.
$permalink = get_permalink( $post );
if ( ! empty( $permalink ) ) {
$this->links[] = $permalink;
}
// --- START OF MODIFIED CODE ---
// Check if the updated post is a WooCommerce product
if ( 'product' === $post->post_type ) {
$product_url = get_permalink( $post );
// 1. Flush the specific product URL (Batcache)
self::clear_url( $product_url );
// 2. Store the product URL in the option table
update_option( 'edge-cache-single-page-url-purged', $product_url );
// 3. Purge Edge Cache for the specific URL
if (class_exists('Edge_Cache_Plugin')) {
// Set the default timezone to UTC before calling date()
$timezone_backup = date_default_timezone_get();
date_default_timezone_set('UTC');
$edge_cache = Edge_Cache_Plugin::get_instance();
$urls = array($product_url);
// Use the correct method to purge URIs
$result = $edge_cache->purge_uris_now($urls);
// Save time stamp to database if edge cache is purged for particular page
$edge_cache_purge_time = date('jS F Y g:ia') . "\nUTC";
update_option('single-page-edge-cache-purge-time-stamp', $edge_cache_purge_time);
// Restore the original timezone
date_default_timezone_set($timezone_backup);
}
}
// --- END OF MODIFIED CODE ---
}
/**
* Clear terms on term update
*
* @param array $ids Single or list of Term IDs.
* @param string $taxonomy
* @param bool $clean_taxonomy Optional. Whether to clean taxonomy wide caches (true), or just individual
* term object caches (false). Default true. Only support in WP 4.5
*/
public function action_clean_term_cache( $ids, $taxonomy, $clean_taxonomy = true ) {
// Clear taxonomy global caches. If false, lets not both.
if ( ! $clean_taxonomy ) {
return;
}
// If not a public taxonomy, don't clear caches.
if ( ! $this->is_taxonomy_viewable( $taxonomy ) ) {
return;
}
foreach ( $ids as $term ) {
$this->setup_term_urls( $term, $taxonomy );
}
}
/**
* Clear post page on comment update
*
* @param $comment_id
*/
public function action_update_comment( $comment_id ) {
$comment = get_comment( $comment_id );
$post_id = $comment->comment_post_ID;
$this->setup_post_urls( $post_id );
$this->setup_post_comment_urls( $post_id, $comment_id );
}
/**
* Clear author links on update user.
*
* @param $user_id
*/
public function action_update_user( $user_id ) {
$this->setup_author_urls( $user_id );
}
public function flush_all() {
if ( function_exists( 'batcache_flush_all' ) ) {
batcache_flush_all();
}
}
/**
* Flush all of the caches when a widget is updated.
*
* @param array $instance The current widget instance's settings.
*
* @return array $instance
*/
public function action_update_widget( $instance ) {
$this->flush_all();
return $instance;
}
/**
* Get term archive and feed links for each term
*
* @param $term
* @param $taxonomy
*/
public function setup_term_urls( $term, $taxonomy ) {
$term_link = get_term_link( $term, $taxonomy );
if ( ! is_wp_error( $term_link ) ) {
$this->links[] = $term_link;
}
foreach ( $this->feeds as $feed ) {
$term_link_feed = get_term_feed_link( $term, $taxonomy, $feed );
if ( $term_link_feed ) {
$this->links[] = $term_link_feed;
}
}
$taxonomy_object = get_taxonomy( $taxonomy );
if ( $taxonomy_object->show_in_rest && $taxonomy_object->rest_base ) {
$base = $taxonomy_object->rest_base;
$this->links[] = get_rest_url( null, '/wp/v2/' . $base );
$this->links[] = get_rest_url( null, '/wp/v2/' . $base . '/'. $term );
}
}
/**
* Home page / blog page and feed links
*/
public function setup_site_urls() {
if ( get_option( 'show_on_front' ) == 'page' ) {
$this->links[] = get_permalink( get_option( 'page_for_posts' ) );
}
$this->links[] = home_url( '/' );
foreach ( $this->feeds as $feed ) {
$this->links[] = get_feed_link( $feed );
}
}
/**
* Get permalink, date archives and custom post type links
*
* @param $post
*/
public function setup_post_urls( $post ) {
$post = get_post( $post );
$this->links[] = get_permalink( $post );
if ( $post->post_type == 'post' ) {
$year = get_the_time( "Y", $post );
$month = get_the_time( "m", $post );
$day = get_the_time( "d", $post );
$this->links[] = get_year_link( $year );
$this->links[] = get_month_link( $year, $month );
$this->links[] = get_day_link( $year, $month, $day );
} else if ( ! in_array( $post->post_type, get_post_types( array( 'public' => true ) ) ) ) {
if ( $archive_link = get_post_type_archive_link( $post->post_type ) ) {
$this->links[] = $archive_link;
}
foreach ( $this->feeds as $feed ) {
if ( $archive_link_feed = get_post_type_archive_feed_link( $post->post_type, $feed ) ) {
$this->links[] = $archive_link_feed;
}
}
}
$post_type = get_post_type_object( $post->post_type );
if ( $post_type->show_in_rest && $post_type->rest_base ) {
$base = $post_type->rest_base;
$this->links[] = get_rest_url( null, '/wp/v2/' . $base );
$this->links[] = get_rest_url( null, '/wp/v2/' . $base . '/'. $post->ID );
}
}
/**
* Author profile and feed links
*
* @param $author_id
*/
public function setup_author_urls( $author_id ) {
$this->links[] = get_author_posts_url( $author_id );
foreach ( $this->feeds as $feed ) {
$this->links[] = get_author_feed_link( $author_id, $feed );
}
$this->links[] = get_rest_url( null, '/wp/v2/users' );
$this->links[] = get_rest_url( null, '/wp/v2/users/' . $author_id );
}
/**
* Get feed urls for comments for single posts
*
* @param $post_id
*/
public function setup_post_comment_urls( $post_id, $comment_id = 0 ) {
foreach ( $this->feeds as $feed ) {
$this->links[] = get_post_comments_feed_link( $post_id, $feed );
}
foreach ( $this->feeds as $feed ) {
$this->links[] = get_feed_link( "comments_" . $feed );
}
$this->links[] = get_rest_url( null, '/wp/v2/comments' );
$this->links[] = get_rest_url( null, '/wp/v2/comments/' . $comment_id );
}
/**
* Work around for those using Domain mapping or have CMS on different url.
*
* @param $links
*/
public function add_site_alias( $links ) {
$home = parse_url( home_url(), PHP_URL_HOST );
$compare_urls = array(
parse_url( get_option( 'home' ), PHP_URL_HOST ),
parse_url( get_option( 'siteurl' ), PHP_URL_HOST ),
parse_url( site_url(), PHP_URL_HOST )
);
// Compare home, site urls with filtered home url
foreach ( $compare_urls as $compare_url ) {
if ( $compare_url != $home ) {
foreach ( $links as $url ) {
$links[] = str_replace( $home, $compare_url, $url );
}
}
}
return $links;
}
/**
* Loop around all urls and clear
*/
public function clear_urls() {
if ( empty ( $this->get_links() ) ) {
return;
}
foreach ( $this->get_links() as $url ) {
self::clear_url( $url );
}
// Clear out links
$this->links = array();
// --- START OF MODIFIED CODE ---
// Set the default timezone to UTC before calling date()
$timezone_backup = date_default_timezone_get();
date_default_timezone_set('UTC');
// Update the timestamp option with the specified format
$batcache_flush_time = date('jS F Y g:ia') . "\nUTC";
update_option( 'flush-object-cache-for-single-page-time-stamp', $batcache_flush_time );
// Restore the original timezone
date_default_timezone_set($timezone_backup);
// --- END OF MODIFIED CODE ---
}
/**
*
* @param $url
*
* @return bool|false|int
*/
public static function clear_url( $url ) {
global $batcache, $wp_object_cache;
$url = apply_filters( 'batcache_manager_link', $url );
if ( empty( $url ) ) {
return false;
}
do_action( 'batcache_manager_before_flush', $url );
// Force to http
$url = set_url_scheme( $url, 'http' );
$url_key = md5( $url );
wp_cache_add( "{$url_key}_version", 0, $batcache->group );
$retval = wp_cache_incr( "{$url_key}_version", 1, $batcache->group );
// $batcache_no_remote_group_key = array_search( $batcache->group, (array) $wp_object_cache->no_remote_groups );
$batcache_no_remote_group_key = property_exists($wp_object_cache, 'no_remote_groups') ? array_search( $batcache->group, (array) $wp_object_cache->no_remote_groups ) : false;
if ( false !== $batcache_no_remote_group_key ) {
// The *_version key needs to be replicated remotely, otherwise invalidation won't work.
// The race condition here should be acceptable.
unset( $wp_object_cache->no_remote_groups[ $batcache_no_remote_group_key ] );
$retval = wp_cache_set( "{$url_key}_version", $retval, $batcache->group );
$wp_object_cache->no_remote_groups[ $batcache_no_remote_group_key ] = $batcache->group;
}
do_action( 'batcache_manager_after_flush', $url, $retval );
return $retval;
}
/**
* Filter links
*
* @return array
*/
public function get_links() {
$this->links = apply_filters( 'batcache_manager_links', $this->links );
return array_unique( $this->links );
}
}
global $batcache_manager;
$batcache_manager = Batcache_Manager::get_instance();