diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e34b3b0
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,23 @@
+# This file is for unifying the coding style for different editors and IDEs
+# editorconfig.org
+
+# WordPress Coding Standards
+# https://make.wordpress.org/core/handbook/coding-standards/
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = tab
+indent_size = 4
+
+[{.jshintrc,*.json,*.yml}]
+indent_style = space
+indent_size = 2
+
+[{*.txt,wp-config-sample.php}]
+end_of_line = crlf
+
diff --git a/README.md b/README.md
index 1eac0b6..0780b38 100644
--- a/README.md
+++ b/README.md
@@ -587,6 +587,10 @@ Fix url escaping [#82](https://github.com/rtCamp/nginx-helper/pull/82) - by
* First release
+## Credits ##
+
+This plugin’s Cloudflare edge cache purging and cache tag architecture is inspired by the excellent work in [pantheon-systems/pantheon-advanced-page-cache](https://github.com/pantheon-systems/pantheon-advanced-page-cache).
+
## Upgrade Notice ##
### 2.2.3 ###
diff --git a/admin/class-nginx-helper-admin.php b/admin/class-nginx-helper-admin.php
index a35ca9d..da38542 100644
--- a/admin/class-nginx-helper-admin.php
+++ b/admin/class-nginx-helper-admin.php
@@ -9,6 +9,8 @@
* @subpackage nginx-helper/admin
*/
+use EasyCache\Cloudflare_Client;
+
/**
* The admin-specific functionality of the plugin.
*
@@ -20,7 +22,7 @@
* @author rtCamp
*/
class Nginx_Helper_Admin {
-
+
/**
* The ID of this plugin.
*
@@ -29,7 +31,7 @@ class Nginx_Helper_Admin {
* @var string $plugin_name The ID of this plugin.
*/
private $plugin_name;
-
+
/**
* The version of this plugin.
*
@@ -38,7 +40,7 @@ class Nginx_Helper_Admin {
* @var string $version The current version of this plugin.
*/
private $version;
-
+
/**
* Various settings tabs.
*
@@ -47,7 +49,7 @@ class Nginx_Helper_Admin {
* @var string $settings_tabs Various settings tabs.
*/
private $settings_tabs;
-
+
/**
* Purge options.
*
@@ -56,7 +58,16 @@ class Nginx_Helper_Admin {
* @var string[] $options Purge options.
*/
public $options;
-
+
+ /**
+ * Purge options.
+ *
+ * @since 2.0.0
+ * @access public
+ * @var string[] $options Cloudflare options.
+ */
+ public $cf_options;
+
/**
* WP-CLI Command.
*
@@ -65,7 +76,7 @@ class Nginx_Helper_Admin {
* @var string $options WP-CLI Command.
*/
const WP_CLI_COMMAND = 'nginx-helper';
-
+
/**
* Initialize the class and set its properties.
*
@@ -74,37 +85,42 @@ class Nginx_Helper_Admin {
* @param string $version The version of this plugin.
*/
public function __construct( $plugin_name, $version ) {
-
+
$this->plugin_name = $plugin_name;
$this->version = $version;
-
- $this->options = $this->nginx_helper_settings();
+
+ $this->options = $this->nginx_helper_settings();
+ $this->cf_options = $this->get_cloudflare_settings();
}
-
+
/**
* Initialize the settings tab.
* Required since i18n is used in the settings tab which can be invoked only after init hook since WordPress 6.7
*/
public function initialize_setting_tab() {
-
+
/**
* Define settings tabs
*/
$this->settings_tabs = apply_filters(
- 'rt_nginx_helper_settings_tabs',
- array(
- 'general' => array(
- 'menu_title' => __( 'General', 'nginx-helper' ),
- 'menu_slug' => 'general',
- ),
- 'support' => array(
- 'menu_title' => __( 'Support', 'nginx-helper' ),
- 'menu_slug' => 'support',
- ),
- )
+ 'rt_nginx_helper_settings_tabs',
+ array(
+ 'general' => array(
+ 'menu_title' => __( 'General', 'nginx-helper' ),
+ 'menu_slug' => 'general',
+ ),
+ 'support' => array(
+ 'menu_title' => __( 'Support', 'nginx-helper' ),
+ 'menu_slug' => 'support',
+ ),
+ 'cloudflare' => array(
+ 'menu_title' => __( 'Cloudflare', 'nginx-helper' ),
+ 'menu_slug' => 'cloudflare',
+ ),
+ )
);
}
-
+
/**
* Register the stylesheets for the admin area.
*
@@ -113,7 +129,7 @@ public function initialize_setting_tab() {
* @param string $hook The current admin page.
*/
public function enqueue_styles( $hook ) {
-
+
/**
* This function is provided for demonstration purposes only.
*
@@ -125,16 +141,16 @@ public function enqueue_styles( $hook ) {
* between the defined hooks and the functions defined in this
* class.
*/
-
+
if ( 'settings_page_nginx' !== $hook ) {
return;
}
-
+
wp_enqueue_style( $this->plugin_name . '-icons', plugin_dir_url( __FILE__ ) . 'icons/css/nginx-fontello.css', array(), $this->version, 'all' );
wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/nginx-helper-admin.css', array(), $this->version, 'all' );
-
+
}
-
+
/**
* Register the JavaScript for the admin area.
*
@@ -143,7 +159,7 @@ public function enqueue_styles( $hook ) {
* @param string $hook The current admin page.
*/
public function enqueue_scripts( $hook ) {
-
+
/**
* This function is provided for demonstration purposes only.
*
@@ -155,29 +171,29 @@ public function enqueue_scripts( $hook ) {
* between the defined hooks and the functions defined in this
* class.
*/
-
+
if ( 'settings_page_nginx' !== $hook ) {
return;
}
-
+
wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/nginx-helper-admin.js', array( 'jquery' ), $this->version, false );
-
+
$do_localize = array(
'purge_confirm_string' => esc_html__( 'Purging entire cache is not recommended. Would you like to continue?', 'nginx-helper' ),
);
wp_localize_script( $this->plugin_name, 'nginx_helper', $do_localize );
-
+
}
-
+
/**
* Add admin menu.
*
* @since 2.0.0
*/
public function nginx_helper_admin_menu() {
-
+
if ( is_multisite() ) {
-
+
add_submenu_page(
'settings.php',
__( 'Nginx Helper', 'nginx-helper' ),
@@ -186,9 +202,9 @@ public function nginx_helper_admin_menu() {
'nginx',
array( &$this, 'nginx_helper_setting_page' )
);
-
+
} else {
-
+
add_submenu_page(
'options-general.php',
__( 'Nginx Helper', 'nginx-helper' ),
@@ -197,22 +213,22 @@ public function nginx_helper_admin_menu() {
'nginx',
array( &$this, 'nginx_helper_setting_page' )
);
-
+
}
-
+
}
-
+
/**
* Function to add toolbar purge link.
*
* @param object $wp_admin_bar Admin bar object.
*/
public function nginx_helper_toolbar_purge_link( $wp_admin_bar ) {
-
+
if ( ! current_user_can( 'Nginx Helper | Purge cache' ) ) {
return;
}
-
+
if ( is_admin() ) {
$nginx_helper_urls = 'all';
$link_title = __( 'Purge Cache', 'nginx-helper' );
@@ -220,7 +236,7 @@ public function nginx_helper_toolbar_purge_link( $wp_admin_bar ) {
$nginx_helper_urls = 'current-url';
$link_title = __( 'Purge Current Page', 'nginx-helper' );
}
-
+
$purge_url = add_query_arg(
array(
'nginx_helper_action' => 'purge',
@@ -228,9 +244,9 @@ public function nginx_helper_toolbar_purge_link( $wp_admin_bar ) {
'nginx_helper_dismiss' => get_transient( 'rt_wp_nginx_helper_suggest_purge_notice' ),
)
);
-
+
$nonced_url = wp_nonce_url( $purge_url, 'nginx_helper-purge_all' );
-
+
$wp_admin_bar->add_menu(
array(
'id' => 'nginx-helper-purge-all',
@@ -239,9 +255,9 @@ public function nginx_helper_toolbar_purge_link( $wp_admin_bar ) {
'meta' => array( 'title' => $link_title ),
)
);
-
+
}
-
+
/**
* Display settings.
*
@@ -252,7 +268,7 @@ public function nginx_helper_toolbar_purge_link( $wp_admin_bar ) {
public function nginx_helper_setting_page() {
include plugin_dir_path( __FILE__ ) . 'partials/nginx-helper-admin-display.php';
}
-
+
/**
* Default settings.
*
@@ -260,7 +276,7 @@ public function nginx_helper_setting_page() {
* @return array
*/
public function nginx_helper_default_settings() {
-
+
return array(
'enable_purge' => 0,
'cache_method' => 'enable_fastcgi',
@@ -297,30 +313,83 @@ public function nginx_helper_default_settings() {
'roles_with_purge_cap' => array(),
'purge_woo_products' => 0,
);
-
+
+ }
+
+ public function store_default_options() {
+ $options = get_site_option( 'rt_wp_nginx_helper_options', array() );
+ $default_settings = $this->nginx_helper_default_settings();
+
+ $removable_default_settings = array(
+ 'redis_port',
+ 'redis_prefix',
+ 'redis_hostname',
+ 'redis_database',
+ 'redis_unix_socket'
+ );
+
+ // Remove all the keys that are not to be stored by default.
+ foreach ( $removable_default_settings as $removable_key ) {
+ unset( $default_settings[ $removable_key ] );
+ }
+
+ $diffed_options = wp_parse_args( $options, $default_settings );
+
+ add_site_option( 'rt_wp_nginx_helper_options', $diffed_options );
+
+ $this->store_cloudflare_settings();
+ }
+
+ /**
+ * Gets the default settings for cloudflare.
+ *
+ * @return array An array of settings.
+ */
+ public function get_cloudflare_default_settings() {
+ return array(
+ 'api_token' => '',
+ 'zone_id' => '',
+ 'default_cache_ttl' => 604800,
+ 'api_token_enabled_by_constant' => false,
+ );
+ }
+
+ /**
+ * Gets the current cloudflare settings.
+ *
+ * @return array The current settings.
+ */
+ public function get_cloudflare_settings() {
+ $default_settings = $this->get_cloudflare_default_settings();
+
+ $stored_options = get_site_option( 'easycache_cf_settings', array() );
+
+ if ( defined( 'EASYCACHE_CLOUDFLARE_API_TOKEN' ) && !empty( EASYCACHE_CLOUDFLARE_API_TOKEN ) ) {
+ $stored_options['api_token'] = EASYCACHE_CLOUDFLARE_API_TOKEN;
+ $stored_options['api_token_enabled_by_constant'] = true;
+ }
+
+ $diff_options = wp_parse_args( $stored_options, $default_settings );
+
+ $diff_options['is_enabled'] = ! empty( $diff_options['api_token'] ) && ! empty( $diff_options['zone_id'] );
+
+ return $diff_options;
+ }
+
+ /**
+ * Stores the cloudflare settings.
+ *
+ * @return array The current settings.
+ */
+ public function store_cloudflare_settings() {
+ $default_settings = $this->get_cloudflare_default_settings();
+
+ $stored_options = get_site_option( 'easycache_cf_settings', array() );
+
+ $diff_options = wp_parse_args( $stored_options, $default_settings );
+
+ add_site_option( 'easycache_cf_settings', $diff_options );
}
-
- public function store_default_options() {
- $options = get_site_option( 'rt_wp_nginx_helper_options', array() );
- $default_settings = $this->nginx_helper_default_settings();
-
- $removable_default_settings = array(
- 'redis_port',
- 'redis_prefix',
- 'redis_hostname',
- 'redis_database',
- 'redis_unix_socket'
- );
-
- // Remove all the keys that are not to be stored by default.
- foreach ( $removable_default_settings as $removable_key ) {
- unset( $default_settings[ $removable_key ] );
- }
-
- $diffed_options = wp_parse_args( $options, $default_settings );
-
- add_site_option( 'rt_wp_nginx_helper_options', $diffed_options );
- }
/**
* Get settings.
@@ -328,7 +397,7 @@ public function store_default_options() {
* @since 2.0.0
*/
public function nginx_helper_settings() {
-
+
$options = get_site_option(
'rt_wp_nginx_helper_options',
array(
@@ -338,18 +407,18 @@ public function nginx_helper_settings() {
'redis_database' => 0,
)
);
-
+
$data = wp_parse_args(
$options,
$this->nginx_helper_default_settings()
);
-
+
$is_redis_enabled = (
defined( 'RT_WP_NGINX_HELPER_REDIS_HOSTNAME' ) &&
defined( 'RT_WP_NGINX_HELPER_REDIS_PORT' ) &&
defined( 'RT_WP_NGINX_HELPER_REDIS_PREFIX' )
);
-
+
$data['redis_acl_enabled_by_constant'] = defined('RT_WP_NGINX_HELPER_REDIS_USERNAME') && defined('RT_WP_NGINX_HELPER_REDIS_PASSWORD');
$data['redis_socket_enabled_by_constant'] = defined('RT_WP_NGINX_HELPER_REDIS_UNIX_SOCKET');
$data['redis_unix_socket'] = $data['redis_socket_enabled_by_constant'] ? RT_WP_NGINX_HELPER_REDIS_UNIX_SOCKET : $data['redis_unix_socket'];
@@ -365,13 +434,13 @@ public function nginx_helper_settings() {
$data['cache_method'] = 'enable_redis';
$data['redis_hostname'] = RT_WP_NGINX_HELPER_REDIS_HOSTNAME;
$data['redis_port'] = RT_WP_NGINX_HELPER_REDIS_PORT;
- $data['redis_prefix'] = RT_WP_NGINX_HELPER_REDIS_PREFIX;
+ $data['redis_prefix'] = RT_WP_NGINX_HELPER_REDIS_PREFIX;
$data['redis_database'] = defined('RT_WP_NGINX_HELPER_REDIS_DATABASE') ? RT_WP_NGINX_HELPER_REDIS_DATABASE : 0;
return $data;
-
+
}
-
+
/**
* Nginx helper setting link function.
*
@@ -380,20 +449,20 @@ public function nginx_helper_settings() {
* @return mixed
*/
public function nginx_helper_settings_link( $links ) {
-
+
if ( is_network_admin() ) {
$setting_page = 'settings.php';
} else {
$setting_page = 'options-general.php';
}
-
+
$settings_link = '' . __( 'Settings', 'nginx-helper' ) . '';
array_unshift( $links, $settings_link );
-
+
return $links;
-
+
}
-
+
/**
* Check if the nginx log is enabled.
*
@@ -401,20 +470,20 @@ public function nginx_helper_settings_link( $links ) {
* @return boolean
*/
public function is_nginx_log_enabled() {
-
+
$options = get_site_option( 'rt_wp_nginx_helper_options', array() );
-
+
if ( ! empty( $options['enable_log'] ) && 1 === (int) $options['enable_log'] ) {
return true;
}
-
+
if ( defined( 'NGINX_HELPER_LOG' ) && true === NGINX_HELPER_LOG ) {
return true;
}
-
+
return false;
}
-
+
/**
* Retrieve the asset path.
*
@@ -422,13 +491,13 @@ public function is_nginx_log_enabled() {
* @return string asset path of the plugin.
*/
public function functional_asset_path() {
-
+
$log_path = WP_CONTENT_DIR . '/uploads/nginx-helper/';
-
+
return apply_filters( 'nginx_asset_path', $log_path );
-
+
}
-
+
/**
* Retrieve the asset url.
*
@@ -436,36 +505,36 @@ public function functional_asset_path() {
* @return string asset url of the plugin.
*/
public function functional_asset_url() {
-
+
$log_url = WP_CONTENT_URL . '/uploads/nginx-helper/';
-
+
return apply_filters( 'nginx_asset_url', $log_url );
-
+
}
-
+
/**
* Get latest news.
*
* @since 2.0.0
*/
public function nginx_helper_get_feeds() {
-
+
// Get RSS Feed(s).
require_once ABSPATH . WPINC . '/feed.php';
-
+
$maxitems = 0;
$rss_items = array();
-
+
// Get a SimplePie feed object from the specified feed source.
$rss = fetch_feed( 'https://rtcamp.com/blog/feed/' );
-
+
if ( ! is_wp_error( $rss ) ) { // Checks that the object is created correctly.
-
+
// Figure out how many total items there are, but limit it to 5.
$maxitems = $rss->get_item_quantity( 5 );
// Build an array of all the items, starting with element 0 (first element).
$rss_items = $rss->get_items( 0, $maxitems );
-
+
}
?>
@@ -473,7 +542,7 @@ public function nginx_helper_get_feeds() {
if ( 0 === $maxitems ) {
echo '- ' . esc_html_e( 'No items', 'nginx-helper' ) . '.
';
} else {
-
+
// Loop through each feed item and display each item as a hyperlink.
foreach ( $rss_items as $item ) {
?>
@@ -500,24 +569,24 @@ public function nginx_helper_get_feeds() {
options['enable_purge'] || 1 !== (int) $this->options['enable_stamp'] ) {
return;
}
-
+
if ( ! empty( $pagenow ) && 'wp-login.php' === $pagenow ) {
return;
}
-
+
foreach ( headers_list() as $header ) {
list( $key, $value ) = explode( ':', $header, 2 );
$key = strtolower( $key );
@@ -528,32 +597,32 @@ public function add_timestamps() {
break;
}
}
-
+
/**
* Don't add timestamp if run from ajax, cron or wpcli.
*/
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return;
}
-
+
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
return;
}
-
+
if ( defined( 'WP_CLI' ) && WP_CLI ) {
return;
}
-
+
$timestamps = "\n\n" .
'';
-
+
echo wp_kses( $timestamps, array() );
-
+
}
-
+
/**
* Get map
*
@@ -562,83 +631,83 @@ public function add_timestamps() {
* @return string
*/
public function get_map() {
-
+
if ( ! $this->options['enable_map'] ) {
return;
}
-
+
if ( is_multisite() ) {
-
+
global $wpdb;
-
+
$rt_all_blogs = $wpdb->get_results(
$wpdb->prepare(
'SELECT blog_id, domain, path FROM ' . $wpdb->blogs . " WHERE site_id = %d AND archived = '0' AND mature = '0' AND spam = '0' AND deleted = '0'",
$wpdb->siteid
)
);
-
+
$wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
-
+
$rt_domain_map_sites = '';
-
+
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->dmtable}'" ) === $wpdb->dmtable ) { // phpcs:ignore
$rt_domain_map_sites = $wpdb->get_results( "SELECT blog_id, domain FROM {$wpdb->dmtable} ORDER BY id DESC" );
}
-
+
$rt_nginx_map = '';
$rt_nginx_map_array = array();
-
+
if ( $rt_all_blogs ) {
-
+
foreach ( $rt_all_blogs as $blog ) {
-
+
if ( true === SUBDOMAIN_INSTALL ) {
$rt_nginx_map_array[ $blog->domain ] = $blog->blog_id;
} else {
-
+
if ( 1 !== $blog->blog_id ) {
$rt_nginx_map_array[ $blog->path ] = $blog->blog_id;
}
}
}
}
-
+
if ( $rt_domain_map_sites ) {
-
+
foreach ( $rt_domain_map_sites as $site ) {
$rt_nginx_map_array[ $site->domain ] = $site->blog_id;
}
}
-
+
foreach ( $rt_nginx_map_array as $domain => $domain_id ) {
$rt_nginx_map .= "\t" . $domain . "\t" . $domain_id . ";\n";
}
-
+
return $rt_nginx_map;
-
+
}
-
+
}
-
+
/**
* Update map
*/
public function update_map() {
-
+
if ( is_multisite() ) {
-
+
$rt_nginx_map = $this->get_map();
-
+
$fp = fopen( $this->functional_asset_path() . 'map.conf', 'w+' );
if ( $fp ) {
fwrite( $fp, $rt_nginx_map );
fclose( $fp );
}
}
-
+
}
-
+
/**
* Purge url when post status is changed.
*
@@ -650,7 +719,7 @@ public function update_map() {
* @param object $post Post object.
*/
public function set_future_post_option_on_future_status( $new_status, $old_status, $post ) {
-
+
global $blog_id, $nginx_purger;
$exclude_post_types = apply_filters( 'rt_nginx_helper_exclude_post_types', array( 'nav_menu_item' ) );
@@ -658,20 +727,20 @@ public function set_future_post_option_on_future_status( $new_status, $old_statu
if ( in_array( $post->post_type, $exclude_post_types, true ) ) {
return;
}
-
+
if ( ! $this->options['enable_purge'] || $this->is_import_request() ) {
return;
}
-
+
$purge_status = array( 'publish', 'future' );
-
+
if ( in_array( $old_status, $purge_status, true ) || in_array( $new_status, $purge_status, true ) ) {
-
+
$nginx_purger->log( 'Purge post on transition post STATUS from ' . $old_status . ' to ' . $new_status );
$nginx_purger->purge_post( $post->ID );
-
+
}
-
+
if (
'future' === $new_status && $post && 'future' === $post->post_status &&
(
@@ -682,15 +751,15 @@ public function set_future_post_option_on_future_status( $new_status, $old_statu
)
)
) {
-
+
$nginx_purger->log( 'Set/update future_posts option ( post id = ' . $post->ID . ' and blog id = ' . $blog_id . ' )' );
$this->options['future_posts'][ $blog_id ][ $post->ID ] = strtotime( $post->post_date_gmt ) + 60;
update_site_option( 'rt_wp_nginx_helper_options', $this->options );
-
+
}
-
+
}
-
+
/**
* Unset future post option on delete
*
@@ -700,9 +769,9 @@ public function set_future_post_option_on_future_status( $new_status, $old_statu
* @param int $post_id Post id.
*/
public function unset_future_post_option_on_delete( $post_id ) {
-
+
global $blog_id, $nginx_purger;
-
+
if (
! $this->options['enable_purge'] ||
empty( $this->options['future_posts'] ) ||
@@ -712,18 +781,18 @@ public function unset_future_post_option_on_delete( $post_id ) {
) {
return;
}
-
+
$nginx_purger->log( 'Unset future_posts option ( post id = ' . $post_id . ' and blog id = ' . $blog_id . ' )' );
-
+
unset( $this->options['future_posts'][ $blog_id ][ $post_id ] );
-
+
if ( ! count( $this->options['future_posts'][ $blog_id ] ) ) {
unset( $this->options['future_posts'][ $blog_id ] );
}
-
+
update_site_option( 'rt_wp_nginx_helper_options', $this->options );
}
-
+
/**
* Update map when new blog added in multisite.
*
@@ -732,18 +801,18 @@ public function unset_future_post_option_on_delete( $post_id ) {
* @param string $blog_id blog id.
*/
public function update_new_blog_options( $blog_id ) {
-
+
global $nginx_purger;
-
+
$nginx_purger->log( "New site added ( id $blog_id )" );
$this->update_map();
$nginx_purger->log( "New site added to nginx map ( id $blog_id )" );
$helper_options = $this->nginx_helper_default_settings();
update_blog_option( $blog_id, 'rt_wp_nginx_helper_options', $helper_options );
$nginx_purger->log( "Default options updated for the new blog ( id $blog_id )" );
-
+
}
-
+
/**
* Purge all urls.
* Purge current page cache when purging is requested from front
@@ -752,18 +821,18 @@ public function update_new_blog_options( $blog_id ) {
* @global object $nginx_purger
*/
public function purge_all() {
-
+
if ( $this->is_import_request() ) {
return;
}
-
+
global $nginx_purger, $wp;
-
+
$method = null;
if ( isset( $_SERVER['REQUEST_METHOD'] ) ) {
$method = wp_strip_all_tags( $_SERVER['REQUEST_METHOD'] );
}
-
+
$action = '';
if ( 'POST' === $method ) {
if ( isset( $_POST['nginx_helper_action'] ) ) {
@@ -774,34 +843,34 @@ public function purge_all() {
$action = wp_strip_all_tags( $_GET['nginx_helper_action'] );
}
}
-
+
if ( empty( $action ) ) {
return;
}
-
+
if ( ! current_user_can( 'Nginx Helper | Purge cache' ) ) {
wp_die( 'Sorry, you do not have the necessary privileges to edit these options.' );
}
-
+
if ( 'done' === $action ) {
-
+
add_action( 'admin_notices', array( &$this, 'display_notices' ) );
add_action( 'network_admin_notices', array( &$this, 'display_notices' ) );
return;
-
+
}
-
+
check_admin_referer( 'nginx_helper-purge_all' );
-
+
$current_url = user_trailingslashit( home_url( $wp->request ) );
-
+
if ( ! is_admin() ) {
$action = 'purge_current_page';
$redirect_url = $current_url;
} else {
$redirect_url = add_query_arg( array( 'nginx_helper_action' => 'done' ) );
}
-
+
switch ( $action ) {
case 'purge':
$nginx_purger->purge_all();
@@ -810,30 +879,34 @@ public function purge_all() {
$nginx_purger->purge_url( $current_url );
break;
}
-
+
if ( 'purge' === $action ) {
-
+
/**
* Fire an action after the entire cache has been purged whatever caching type is used.
*
* @since 2.2.2
*/
do_action( 'rt_nginx_helper_after_purge_all' );
-
+
+ }
+
+ if( $this->cf_options['is_enabled'] ) {
+ Cloudflare_Client::purgeEverything();
}
-
+
wp_redirect( esc_url_raw( $redirect_url ) );
exit();
-
+
}
-
+
/**
* Dispay plugin notices.
*/
public function display_notices() {
echo '' . esc_html__( 'Purge initiated', 'nginx-helper' ) . '
';
}
-
+
/**
* Preloads the cache for the website.
*
@@ -842,42 +915,42 @@ public function display_notices() {
public function preload_cache() {
$is_cache_preloaded = $this->options['is_cache_preloaded'];
$preload_cache_enabled = $this->options['preload_cache'];
-
+
if ( $preload_cache_enabled && false === boolval( $is_cache_preloaded ) ) {
$this->options['is_cache_preloaded'] = true;
-
+
update_site_option( 'rt_wp_nginx_helper_options', $this->options );
$this->preload_cache_from_sitemap();
}
}
-
+
/**
* This function preloads the cache from sitemap url.
*
* @return void
*/
private function preload_cache_from_sitemap() {
-
+
$sitemap_urls = $this->get_index_sitemap_urls();
$all_urls = array();
-
+
foreach ( $sitemap_urls as $sitemap_url ) {
$urls = $this->extract_sitemap_urls( $sitemap_url );
$all_urls = array_merge( $all_urls, $urls );
}
-
+
$args = array(
'timeout' => 1,
'blocking' => false,
'sslverify' => false,
);
-
+
foreach ( $all_urls as $url ) {
wp_remote_get( esc_url_raw( $url ), $args );
}
-
+
}
-
+
/**
* Fetches all the sitemap urls for the site.
*
@@ -891,7 +964,7 @@ private function get_index_sitemap_urls() {
}
return $urls;
}
-
+
/**
* Parse sitemap content and extract all URLs.
*
@@ -900,35 +973,35 @@ private function get_index_sitemap_urls() {
*/
private function extract_sitemap_urls( $sitemap_url ) {
$response = wp_remote_get( $sitemap_url );
-
+
$urls = array();
-
+
if ( is_wp_error( $response ) ) {
return $urls;
}
-
+
$sitemap_content = wp_remote_retrieve_body( $response );
-
+
libxml_use_internal_errors( true );
$xml = simplexml_load_string( $sitemap_content );
-
+
if ( false === $xml ) {
return new WP_Error( 'sitemap_parse_error', esc_html__( 'Failed to parse the sitemap XML', 'nginx-helper' ) );
}
-
+
$urls = array();
-
+
if ( false === $xml ) {
return $urls;
}
-
+
foreach ( $xml->url as $url ) {
$urls[] = (string) $url->loc;
}
-
+
return $urls;
}
-
+
/**
* Determines if the current request is for importing Posts/ WordPress content.
*
@@ -937,7 +1010,7 @@ private function extract_sitemap_urls( $sitemap_url ) {
public function is_import_request() {
$import_query_var = sanitize_text_field( wp_unslash( $_GET['import'] ?? '' ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is already in the admin dashboard.
$has_import_started = did_action( 'import_start' );
-
+
return ( defined( 'WP_IMPORTING' ) && true === WP_IMPORTING )
|| 0 !== $has_import_started
|| ! empty( $import_query_var );
@@ -1112,21 +1185,123 @@ public function purge_product_cache_on_purchase( $order ) {
public function purge_product_cache_on_update( $product_id ) {
global $nginx_purger;
- if ( empty( $nginx_purger ) ) {
- return;
+ if ( empty( $nginx_purger ) ) {
+ return;
}
if ( ! $this->options['enable_purge'] ) {
return;
}
-
+
$nginx_purger->log( 'WooCommerce product update - purging cache for product ID: ' . $product_id );
-
+
$product_url = get_permalink( $product_id );
-
+
if ( $product_url ) {
$nginx_purger->purge_url( $product_url );
}
}
-
+
+ /**
+ * Handles the cache rule update on Cloudflare tab.
+ *
+ * @return void
+ */
+ public function handle_cf_cache_rule_update() {
+ $nonce = isset( $_POST['easycache_add_cache_rule_nonce'] ) ? wp_unslash( $_POST['easycache_add_cache_rule_nonce'] ) : '';
+
+ if ( wp_verify_nonce( $nonce, 'easycache_add_cache_rule_nonce' ) ) {
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+
+ $result = EasyCache\Cloudflare_Client::setupCacheRule();
+
+ set_transient( 'ec_page_rule_save_state_admin_notice', $result, 60 );
+ }
+ }
+
+ /**
+ * Display admin notices for cloudflare page save rules.
+ */
+ public function cf_page_rule_save_display_admin_notices() {
+ if ( $result = get_transient( 'ec_page_rule_save_state_admin_notice' ) ) {
+ $class = 'notice';
+ $message = '';
+
+ switch ( $result ) {
+ case 'created':
+ $class .= ' notice-success';
+ $message = __( 'The Cloudflare Cache Rule was created successfully.', 'nginx-helper' );
+ break;
+ case 'exists':
+ $class .= ' notice-info';
+ $message = __( 'The Cache Rule already exists. No action was taken.', 'nginx-helper' );
+ break;
+ default:
+ $class .= ' notice-error';
+ $message = __( 'Failed to create the Cache Rule. Please check that your API Token has Cache Rules Read/Write permissions.', 'nginx-helper' );
+ break;
+ }
+
+ printf( '', esc_attr( $class ), esc_html( $message ) );
+ delete_transient( 'ec_page_rule_save_state_admin_notice' );
+ }
+ }
+
+ /**
+ * Register a toolbar button to purge the cache for the current page.
+ *
+ * @param object $wp_admin_bar Instance of WP_Admin_Bar.
+ */
+ public static function add_cloudflare_admin_bar_purge( $wp_admin_bar ) {
+ if ( is_admin() || ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+
+ if ( ! empty( $_GET['message'] ) && 'ec-cleared-url-cache' === $_GET['message'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $title = esc_html__( 'URL Cache Cleared', 'easycache' );
+ } else {
+ $title = esc_html__( 'Clear URL Cache', 'easycache' );
+ }
+
+ $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( $_SERVER['REQUEST_URI'] ) : '';
+ $wp_admin_bar->add_menu( [
+ 'parent' => '',
+ 'id' => 'clear-page-cache',
+ 'title' => $title,
+ 'meta' => [
+ 'title' => __( 'Purge the current URL from Cloudflare cache.', 'easycache' ),
+ ],
+ 'href' => wp_nonce_url( admin_url( 'admin-ajax.php?action=ec_clear_url_cache&path=' . rawurlencode( home_url( $request_uri ) ) ), 'ec-clear-url-cache' ),
+ ] );
+ }
+
+ /**
+ * Handle an admin-ajax request to clear the URL cache for Cloudflare.
+ *
+ * @return void
+ */
+ public static function handle_cloudflare_clear_cache_ajax() {
+ $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( $_GET['_wpnonce'] ) : '';
+ if ( empty( $nonce )
+ || ! wp_verify_nonce( $nonce, 'ec-clear-url-cache' )
+ || ! current_user_can( 'manage_options' ) ) {
+ wp_die( esc_html__( "You shouldn't be doing this.", 'easycache' ) );
+ }
+
+ $path = isset( $_GET['path'] ) ? esc_url_raw( $_GET['path'] ) : '';
+ if ( empty( $path ) ) {
+ wp_die( esc_html__( 'No path provided.', 'easycache' ) );
+ }
+
+ $ret = Cloudflare_Client::purgeByUrls( [ $path ] );
+ if ( ! $ret ) {
+ wp_die( esc_html__( 'Failed to clear URL cache.', 'easycache' ) );
+ }
+
+ wp_safe_redirect( add_query_arg( 'message', 'ec-cleared-url-cache', $path ) );
+ exit;
+ }
}
diff --git a/admin/partials/easycache-cloudflare-options.php b/admin/partials/easycache-cloudflare-options.php
new file mode 100644
index 0000000..4e4a26e
--- /dev/null
+++ b/admin/partials/easycache-cloudflare-options.php
@@ -0,0 +1,150 @@
+get_cloudflare_default_settings();
+
+ $args = wp_parse_args( $all_inputs, $default_args );
+
+ update_site_option( 'easycache_cf_settings', $args );
+
+ echo '' . esc_html__( 'Settings saved.', 'nginx-helper' ) . '
';
+}
+
+if( isset( $nginx_helper_admin ) && method_exists( $nginx_helper_admin, 'handle_cf_cache_rule_update' ) ) {
+ $nginx_helper_admin->handle_cf_cache_rule_update();
+}
+
+// Display any updates from Cloudflare Purge Rules.
+if( isset( $nginx_helper_admin ) && method_exists( $nginx_helper_admin, 'cf_page_rule_save_display_admin_notices' ) ) {
+ $nginx_helper_admin->cf_page_rule_save_display_admin_notices();
+}
+
+
+$ec_site_settings = $nginx_helper_admin->get_cloudflare_settings();
+?>
+
+
+
+
+
+
+
diff --git a/admin/partials/nginx-helper-admin-display.php b/admin/partials/nginx-helper-admin-display.php
index bd15371..44251db 100644
--- a/admin/partials/nginx-helper-admin-display.php
+++ b/admin/partials/nginx-helper-admin-display.php
@@ -49,6 +49,9 @@
case 'support':
include plugin_dir_path( __FILE__ ) . 'nginx-helper-support-options.php';
break;
+ case 'cloudflare':
+ include plugin_dir_path( __FILE__ ) . 'easycache-cloudflare-options.php';
+ break;
}
?>
diff --git a/composer.json b/composer.json
index f215446..fcb7e63 100644
--- a/composer.json
+++ b/composer.json
@@ -1,17 +1,32 @@
{
"name": "rtcamp/nginx-helper",
- "description": "Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does a few more things.",
- "keywords": ["wordpress", "plugin", "nginx", "nginx-helper", "fastcgi", "redis-cache", "redis", "cache"],
+ "type": "wordpress-plugin",
+ "description": "Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also provides cloudflare edge cache purging with Cache-Tags.",
+ "keywords": [
+ "wordpress",
+ "plugin",
+ "nginx",
+ "nginx-helper",
+ "fastcgi",
+ "redis-cache",
+ "redis",
+ "cache",
+ "cloudflare",
+ "cloudflare-edge-cache",
+ "cache-tags",
+ "easycache"
+ ],
"homepage": "https://rtcamp.com/nginx-helper/",
"license": "GPL-2.0+",
- "authors": [{
- "name": "rtCamp",
- "email": "support@rtcamp.com",
- "homepage": "https://rtcamp.com"
- }],
+ "authors": [
+ {
+ "name": "rtCamp",
+ "email": "support@rtcamp.com",
+ "homepage": "https://rtcamp.com"
+ }
+ ],
"minimum-stability": "dev",
"prefer-stable": true,
- "type": "wordpress-plugin",
"support": {
"issues": "https://github.com/rtCamp/nginx-helper/issues",
"forum": "https://wordpress.org/support/plugin/nginx-helper",
@@ -20,9 +35,15 @@
},
"require": {
"php": ">=5.3.2",
- "composer/installers": "^1.0"
+ "composer/installers": "^1.0",
+ "cloudflare/sdk": "^1.1"
},
"require-dev": {
"wpreadme2markdown/wpreadme2markdown": "*"
+ },
+ "config": {
+ "allow-plugins": {
+ "composer/installers": true
+ }
}
}
diff --git a/composer.lock b/composer.lock
index 589b071..1e4cbd2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1,42 +1,98 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
- "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "hash": "f8ee8d46fadaee8c9cc194ef126e7404",
+ "content-hash": "9a39933154bb8e65621b25ca590a1e84",
"packages": [
+ {
+ "name": "cloudflare/sdk",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cloudflare/cloudflare-php.git",
+ "reference": "2d3f198773e865b5de2357d7bdbc52bdf42e8f97"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cloudflare/cloudflare-php/zipball/2d3f198773e865b5de2357d7bdbc52bdf42e8f97",
+ "reference": "2d3f198773e865b5de2357d7bdbc52bdf42e8f97",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/guzzle": "^7.0.1",
+ "php": ">=7.2.5",
+ "psr/http-message": "~1.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.6",
+ "phpmd/phpmd": "@stable",
+ "phpunit/phpunit": "^5.7"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Cloudflare\\API\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Junade Ali",
+ "email": "junade@cloudflare.com"
+ }
+ ],
+ "description": "PHP binding for v4 of the Cloudflare Client API.",
+ "support": {
+ "issues": "https://github.com/cloudflare/cloudflare-php/issues",
+ "source": "https://github.com/cloudflare/cloudflare-php/tree/1.4.0"
+ },
+ "time": "2024-12-17T23:18:20+00:00"
+ },
{
"name": "composer/installers",
- "version": "v1.0.6",
+ "version": "v1.12.0",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
- "reference": "b3bd071ea114a57212c75aa6a2eef5cfe0cc798f"
+ "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/installers/zipball/b3bd071ea114a57212c75aa6a2eef5cfe0cc798f",
- "reference": "b3bd071ea114a57212c75aa6a2eef5cfe0cc798f",
+ "url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19",
+ "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19",
"shasum": ""
},
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0"
+ },
"replace": {
+ "roundcube/plugin-installer": "*",
"shama/baton": "*"
},
"require-dev": {
- "composer/composer": "1.0.*@dev",
- "phpunit/phpunit": "3.7.*"
+ "composer/composer": "1.6.* || ^2.0",
+ "composer/semver": "^1 || ^3",
+ "phpstan/phpstan": "^0.12.55",
+ "phpstan/phpstan-phpunit": "^0.12.16",
+ "symfony/phpunit-bridge": "^4.2 || ^5",
+ "symfony/process": "^2.3"
},
- "type": "composer-installer",
+ "type": "composer-plugin",
"extra": {
- "class": "Composer\\Installers\\Installer",
+ "class": "Composer\\Installers\\Plugin",
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-main": "1.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Composer\\Installers\\": "src/"
+ "psr-4": {
+ "Composer\\Installers\\": "src/Composer/Installers"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -47,77 +103,512 @@
{
"name": "Kyle Robinson Young",
"email": "kyle@dontkry.com",
- "homepage": "https://github.com/shama",
- "role": "Developer"
+ "homepage": "https://github.com/shama"
}
],
"description": "A multi-framework Composer library installer",
- "homepage": "http://composer.github.com/installers/",
+ "homepage": "https://composer.github.io/installers/",
"keywords": [
- "TYPO3 CMS",
- "TYPO3 Flow",
- "TYPO3 Neos",
+ "Craft",
+ "Dolibarr",
+ "Eliasis",
+ "Hurad",
+ "ImageCMS",
+ "Kanboard",
+ "Lan Management System",
+ "MODX Evo",
+ "MantisBT",
+ "Mautic",
+ "Maya",
+ "OXID",
+ "Plentymarkets",
+ "Porto",
+ "RadPHP",
+ "SMF",
+ "Starbug",
+ "Thelia",
+ "Whmcs",
+ "WolfCMS",
"agl",
+ "aimeos",
+ "annotatecms",
+ "attogram",
+ "bitrix",
"cakephp",
+ "chef",
+ "cockpit",
"codeigniter",
+ "concrete5",
+ "croogo",
+ "dokuwiki",
"drupal",
+ "eZ Platform",
+ "elgg",
+ "expressionengine",
"fuelphp",
+ "grav",
"installer",
+ "itop",
"joomla",
+ "known",
"kohana",
"laravel",
- "li3",
+ "lavalite",
"lithium",
+ "magento",
+ "majima",
"mako",
+ "mediawiki",
+ "miaoxing",
"modulework",
+ "modx",
+ "moodle",
+ "osclass",
+ "pantheon",
"phpbb",
+ "piwik",
"ppi",
+ "processwire",
+ "puppet",
+ "pxcms",
+ "reindex",
+ "roundcube",
+ "shopware",
"silverstripe",
+ "sydes",
+ "sylius",
"symfony",
+ "tastyigniter",
+ "typo3",
"wordpress",
- "zend"
+ "yawik",
+ "zend",
+ "zikula"
],
- "time": "2013-08-20 04:37:09"
- }
- ],
- "packages-dev": [
+ "support": {
+ "issues": "https://github.com/composer/installers/issues",
+ "source": "https://github.com/composer/installers/tree/v1.12.0"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-09-13T08:19:44+00:00"
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "7.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
+ "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^2.3",
+ "guzzlehttp/psr7": "^2.8",
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-client": "^1.0",
+ "symfony/deprecation-contracts": "^2.2 || ^3.0"
+ },
+ "provide": {
+ "psr/http-client-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "ext-curl": "*",
+ "guzzle/client-integration-tests": "3.0.2",
+ "php-http/message-factory": "^1.1",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20",
+ "psr/log": "^1.1 || ^2.0 || ^3.0"
+ },
+ "suggest": {
+ "ext-curl": "Required for CURL handler support",
+ "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+ "psr/log": "Required for using the Log middleware"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Jeremy Lindblom",
+ "email": "jeremeamia@gmail.com",
+ "homepage": "https://github.com/jeremeamia"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "psr-18",
+ "psr-7",
+ "rest",
+ "web service"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/guzzle/issues",
+ "source": "https://github.com/guzzle/guzzle/tree/7.10.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-23T22:36:01+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "2.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "481557b130ef3790cf82b713667b43030dc9c957"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957",
+ "reference": "481557b130ef3790cf82b713667b43030dc9c957",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/promises/issues",
+ "source": "https://github.com/guzzle/promises/tree/2.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-22T14:34:08+00:00"
+ },
{
- "name": "symfony/console",
- "version": "v2.7.1",
+ "name": "guzzlehttp/psr7",
+ "version": "2.8.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/Console.git",
- "reference": "564398bc1f33faf92fc2ec86859983d30eb81806"
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "21dc724a0583619cd1652f673303492272778051"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/564398bc1f33faf92fc2ec86859983d30eb81806",
- "reference": "564398bc1f33faf92fc2ec86859983d30eb81806",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
+ "reference": "21dc724a0583619cd1652f673303492272778051",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
},
"require-dev": {
- "psr/log": "~1.0",
- "symfony/event-dispatcher": "~2.1",
- "symfony/phpunit-bridge": "~2.7",
- "symfony/process": "~2.1"
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"suggest": {
- "psr/log": "For using the console logger",
- "symfony/event-dispatcher": "",
- "symfony/process": ""
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-23T21:21:41+00:00"
+ },
+ {
+ "name": "psr/http-client",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client"
+ },
+ "time": "2023-09-23T14:17:50+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
- "Symfony\\Component\\Console\\": ""
+ "Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -126,39 +617,213 @@
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "time": "2024-04-15T12:06:14+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/1.1"
+ },
+ "time": "2023-04-04T09:50:52+00:00"
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "time": "2019-03-08T08:55:37+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony Console Component",
+ "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
- "time": "2015-06-10 15:30:22"
- },
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:21:43+00:00"
+ }
+ ],
+ "packages-dev": [
{
"name": "wpreadme2markdown/wpreadme2markdown",
- "version": "2.0.0",
+ "version": "4.1.1",
"source": {
"type": "git",
- "url": "https://github.com/benbalter/WP-Readme-to-Github-Markdown.git",
- "reference": "dceae108111232949affc9107c98276c6fa6c98f"
+ "url": "https://github.com/wpreadme2markdown/wp-readme-to-markdown.git",
+ "reference": "abe788b2a15d13073e47100f8a5312b8175e78a7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/benbalter/WP-Readme-to-Github-Markdown/zipball/dceae108111232949affc9107c98276c6fa6c98f",
- "reference": "dceae108111232949affc9107c98276c6fa6c98f",
+ "url": "https://api.github.com/repos/wpreadme2markdown/wp-readme-to-markdown/zipball/abe788b2a15d13073e47100f8a5312b8175e78a7",
+ "reference": "abe788b2a15d13073e47100f8a5312b8175e78a7",
"shasum": ""
},
"require": {
- "php": ">= 5.3.3",
- "symfony/console": "~2.4"
+ "guzzlehttp/guzzle": "^7.3",
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "squizlabs/php_codesniffer": "*"
},
- "bin": [
- "bin/wp2md"
- ],
"type": "library",
"autoload": {
"psr-4": {
@@ -171,11 +836,11 @@
],
"authors": [
{
- "name": "Christian Archer",
- "email": "chrstnarchr@aol.com"
+ "name": "Benjamin J. Balter"
},
{
- "name": "Benjamin J. Balter"
+ "name": "Christian Archer",
+ "email": "sunchaser@sunchaser.info"
}
],
"description": "Convert WordPress Plugin readme.txt to Markdown",
@@ -185,16 +850,21 @@
"readme",
"wordpress"
],
- "time": "2014-05-28 21:28:31"
+ "support": {
+ "issues": "https://github.com/wpreadme2markdown/wp-readme-to-markdown/issues",
+ "source": "https://github.com/wpreadme2markdown/wp-readme-to-markdown/tree/4.1.1"
+ },
+ "time": "2024-12-16T19:44:24+00:00"
}
],
"aliases": [],
- "minimum-stability": "stable",
- "stability-flags": [],
- "prefer-stable": false,
+ "minimum-stability": "dev",
+ "stability-flags": {},
+ "prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": ">=5.3.2"
},
- "platform-dev": []
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
}
diff --git a/includes/class-cli.php b/includes/class-cli.php
new file mode 100644
index 0000000..21cc8c4
--- /dev/null
+++ b/includes/class-cli.php
@@ -0,0 +1,101 @@
+...
+ * : One or more cache tags.
+ *
+ * ## EXAMPLES
+ *
+ * # Purge the 'post-1' cache tag from Cloudflare.
+ * $ wp cloudflare cache purge-tag post-1
+ * Success: Purged tag.
+ *
+ * @subcommand purge-tag
+ */
+ public function purge_tag( $args ) {
+ $ret = Cloudflare_Client::purgeByTags( $args );
+ if ( ! $ret ) {
+ WP_CLI::error( 'Failed to purge tags.' );
+ } else {
+ $message = count( $args ) > 1 ? 'Purged tags.' : 'Purged tag.';
+ WP_CLI::success( $message );
+ }
+ }
+
+ /**
+ * Purge one or more paths from Cloudflare.
+ *
+ * ## OPTIONS
+ *
+ * ...
+ * : One or more paths.
+ *
+ * ## EXAMPLES
+ *
+ * # Purge the homepage from Cloudflare.
+ * $ wp cloudflare cache purge-path '/'
+ * Success: Purged path.
+ *
+ * @subcommand purge-path
+ */
+ public function purge_path( $args ) {
+ $ret = Cloudflare_Client::purgeByUrls( $args );
+ if ( ! $ret ) {
+ WP_CLI::error( 'Failed to purge paths.' );
+ } else {
+ $message = count( $args ) > 1 ? 'Purged paths.' : 'Purged path.';
+ WP_CLI::success( $message );
+ }
+ }
+
+ /**
+ * Purge the entire Cloudflare cache for the zone.
+ *
+ * WARNING! Purging the entire page cache can have a severe performance
+ * impact on a high-traffic site. We encourage you to explore other options
+ * first.
+ *
+ * ## OPTIONS
+ *
+ * [--yes]
+ * : Answer yes to the confirmation message.
+ *
+ * ## EXAMPLES
+ *
+ * # Purging the entire page cache will display a confirmation prompt.
+ * $ wp cloudflare cache purge-all
+ * Are you sure you want to purge the entire page cache? [y/n] y
+ * Success: Purged page cache.
+ *
+ * @subcommand purge-all
+ */
+ public function purge_all( $_, $assoc_args ) {
+ WP_CLI::confirm( 'Are you sure you want to purge the entire page cache?', $assoc_args );
+ $ret = Cloudflare_Client::purgeEverything();
+ if ( ! $ret ) {
+ WP_CLI::error( 'Failed to purge all.' );
+ } else {
+ WP_CLI::success( 'Purged page cache.' );
+ }
+ }
+}
diff --git a/includes/class-cloudflare-client.php b/includes/class-cloudflare-client.php
new file mode 100644
index 0000000..c4d9338
--- /dev/null
+++ b/includes/class-cloudflare-client.php
@@ -0,0 +1,286 @@
+get_cloudflare_settings();
+ $token = isset( $options['api_token'] ) ? $options['api_token'] : '';
+ $zone_id = isset( $options['zone_id'] ) ? $options['zone_id'] : '';
+
+ if ( empty( $token ) || empty( $zone_id ) ) {
+ error_log( 'Advanced Cloudflare Cache: API Token or Zone ID not configured.' );
+
+ return false;
+ }
+
+ try {
+ $key = new APIToken( $token );
+ $adapter = new Guzzle( $key );
+ $zones = new Zones( $adapter );
+
+ $result = $zones->cachePurge( $zone_id, null, $tags, null );
+
+ if ( $result ) {
+ error_log( 'Advanced Cloudflare Cache: Successfully purged by tags: ' . implode( ', ', $tags ) );
+
+ return true;
+ } else {
+ error_log( 'Advanced Cloudflare Cache: Failed to purge by tags: ' . implode( ', ', $tags ) );
+
+ return false;
+ }
+ } catch ( Exception $e ) {
+ error_log( 'Advanced Cloudflare Cache: Exception when purging by tags: ' . $e->getMessage() );
+
+ return false;
+ }
+ }
+
+ /**
+ * Purge the entire cache for the zone.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public static function purgeEverything() {
+ global $nginx_helper_admin;
+
+ $options = $nginx_helper_admin->get_cloudflare_settings();
+ $token = isset( $options['api_token'] ) ? $options['api_token'] : '';
+ $zone_id = isset( $options['zone_id'] ) ? $options['zone_id'] : '';
+
+ if ( empty( $token ) || empty( $zone_id ) ) {
+ error_log( 'Advanced Cloudflare Cache: API Token or Zone ID not configured.' );
+
+ return false;
+ }
+
+ try {
+ $key = new APIToken( $token );
+ $adapter = new Guzzle( $key );
+ $zones = new Zones( $adapter );
+
+ $result = $zones->cachePurgeEverything( $zone_id );
+
+ if ( $result ) {
+ error_log( 'Advanced Cloudflare Cache: Successfully purged everything.' );
+
+ return true;
+ } else {
+ error_log( 'Advanced Cloudflare Cache: Failed to purge everything.' );
+
+ return false;
+ }
+ } catch ( Exception $e ) {
+ error_log( 'Advanced Cloudflare Cache: Exception when purging everything: ' . $e->getMessage() );
+
+ return false;
+ }
+ }
+
+ /**
+ * Purge the cache for a given set of URLs.
+ *
+ * @param array $urls The URLs to purge.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public static function purgeByUrls( array $urls ) {
+ if ( empty( $urls ) ) {
+ return false;
+ }
+
+ global $nginx_helper_admin;
+
+ $options = $nginx_helper_admin->get_cloudflare_settings();
+ $token = isset( $options['api_token'] ) ? $options['api_token'] : '';
+ $zone_id = isset( $options['zone_id'] ) ? $options['zone_id'] : '';
+
+ if ( empty( $token ) || empty( $zone_id ) ) {
+ error_log( 'Advanced Cloudflare Cache: API Token or Zone ID not configured.' );
+
+ return false;
+ }
+
+ try {
+ $key = new APIToken( $token );
+ $adapter = new Guzzle( $key );
+ $zones = new Zones( $adapter );
+
+ $result = $zones->cachePurge( $zone_id, $urls, null, null );
+
+ if ( $result ) {
+ error_log( 'Advanced Cloudflare Cache: Successfully purged by URLs: ' . implode( ', ', $urls ) );
+
+ return true;
+ } else {
+ error_log( 'Advanced Cloudflare Cache: Failed to purge by URLs: ' . implode( ', ', $urls ) );
+
+ return false;
+ }
+ } catch ( Exception $e ) {
+ error_log( 'Advanced Cloudflare Cache: Exception when purging by URLs: ' . $e->getMessage() );
+
+ return false;
+ }
+ }
+
+ /**
+ * Sets up the "Cache Rule" required to purge the edge cache.
+ *
+ * @return string 'created', 'exists', or 'failed'.
+ */
+ public static function setupCacheRule() {
+ global $nginx_helper_admin;
+
+ if ( ! $nginx_helper_admin ) {
+ return 'failed';
+ }
+
+ $options = $nginx_helper_admin->get_cloudflare_settings();
+ $token = isset( $options['api_token'] ) ? sanitize_text_field( $options['api_token'] ) : '';
+ $zone_id = isset( $options['zone_id'] ) ? sanitize_text_field( $options['zone_id'] ) : '';
+
+ if ( empty( $token ) || empty( $zone_id ) ) {
+ error_log( 'Advanced Cloudflare Cache: API Token or Zone ID not configured.' );
+ return 'failed';
+ }
+
+ $key = new APIToken( $token );
+ $adapter = new Guzzle( $key );
+
+ try {
+ $rulesets_response = $adapter->get( sprintf( 'zones/%s/rulesets', esc_attr( $zone_id ) ) );
+ $raw_response = $rulesets_response->getBody() ?? '';
+ $response_data = json_decode( $raw_response, true );
+
+ if ( ! is_array( $response_data ) || ! array_key_exists( 'result', $response_data ) ) {
+ error_log( 'Advanced Cloudflare Cache: Invalid response when fetching rulesets.' );
+ return 'failed';
+ }
+ } catch ( Exception $e ) {
+ error_log( 'Advanced Cloudflare Cache: Exception when fetching rulesets: ' . esc_html( $e->getMessage() ) );
+ return 'failed';
+ }
+
+ $cache_ruleset_id = null;
+ foreach ( $response_data['result'] as $ruleset ) {
+ if ( 'http_request_cache_settings' === $ruleset['phase'] ) {
+ $cache_ruleset_id = sanitize_text_field( $ruleset['id'] );
+ break;
+ }
+ }
+
+ $site_url = esc_url( get_site_url() );
+ $rule = [
+ 'expression' => '(http.request.full_uri wildcard "' . $site_url . '/*" and not http.cookie contains "wordpress_logged" and not http.cookie contains "NO_CACHE" and not http.cookie contains "S+ESS" and not http.cookie contains "fbs" and not http.cookie contains "SimpleSAML" and not http.cookie contains "PHPSESSID" and not http.cookie contains "wordpress" and not http.cookie contains "wp-" and not http.cookie contains "comment_author_" and not http.cookie contains "duo_wordpress_auth_cookie" and not http.cookie contains "duo_secure_wordpress_auth_cookie" and not http.cookie contains "bp_completed_create_steps" and not http.cookie contains "bp_new_group_id" and not http.cookie contains "wp-resetpass-" and not http.cookie contains "woocommerce" and not http.cookie contains "amazon_Login_")',
+ 'action' => 'set_cache_settings',
+ 'action_parameters' => [
+ 'cache' => true,
+ ],
+ 'description' => 'EasyEngine Cache Manager Ruleset',
+ ];
+
+ // If no cache rule exist then we can directly create a new.
+ if ( null === $cache_ruleset_id ) {
+ $ruleset = [
+ 'name' => 'default',
+ 'kind' => 'zone',
+ 'phase' => 'http_request_cache_settings',
+ 'description' => 'Set\'s the edge cache rules by Nginx-Helper Cache Manager.',
+ 'rules' => [ $rule ],
+ ];
+
+ try {
+ $ruleset_resp = $adapter->post( sprintf( 'zones/%s/rulesets', esc_attr( $zone_id ) ), $ruleset );
+ $raw_ruleset_body = $ruleset_resp->getBody();
+ $ruleset_body = json_decode( $raw_ruleset_body );
+
+ if ( isset( $ruleset_body->success ) && true === $ruleset_body->success ) {
+ return 'created';
+ }
+
+ error_log( 'Advanced Cloudflare Cache: Failed to create cache rule. Response: ' . wp_json_encode( $ruleset_body ) );
+ return 'failed';
+ } catch ( Exception $e ) {
+ error_log( 'Advanced Cloudflare Cache: Exception when creating cache ruleset: ' . esc_html( $e->getMessage() ) );
+ return 'failed';
+ }
+ }
+
+ // Get the existing rule for cache and then update it to add our new rule.
+ try {
+ $ruleset_resp = $adapter->get( sprintf( 'zones/%s/rulesets/%s', esc_attr( $zone_id ), esc_attr( $cache_ruleset_id ) ) );
+
+ if ( 200 !== $ruleset_resp->getStatusCode() ) {
+ error_log( 'Advanced Cloudflare Cache: Failed to fetch existing cache rule. Ruleset ID: ' . wp_json_encode( $cache_ruleset_id ) );
+ return 'failed';
+ }
+ } catch ( Exception $e ) {
+ error_log( 'Advanced Cloudflare Cache: Exception when fetching existing ruleset: ' . esc_html( $e->getMessage() ) );
+ return 'failed';
+ }
+
+ $raw_ruleset_body = $ruleset_resp->getBody();
+ $ruleset_body = json_decode( $raw_ruleset_body, true );
+
+ $existing_rules = is_array( $ruleset_body['result']['rules'] ) ? $ruleset_body['result']['rules'] : [];
+
+ $rule_exists = false;
+ foreach ( $existing_rules as $existing_rule ) {
+ if ( isset( $existing_rule['description'] ) && 'EasyEngine Cache Manager Ruleset' === $existing_rule['description'] ) {
+ $rule_exists = true;
+ break;
+ }
+ }
+
+ if ( $rule_exists ) {
+ return 'exists';
+ }
+
+ $existing_rules[] = $rule;
+
+ try {
+ $ruleset_resp = $adapter->put( sprintf( 'zones/%s/rulesets/%s', esc_attr( $zone_id ), esc_attr( $cache_ruleset_id ) ), [ 'rules' => $existing_rules ] );
+ $raw_ruleset_body = $ruleset_resp->getBody();
+ $ruleset_body = json_decode( $raw_ruleset_body );
+
+ if ( isset( $ruleset_body->success ) && true === $ruleset_body->success ) {
+ return 'created';
+ }
+
+ error_log( 'Advanced Cloudflare Cache: Failed to update cache rule. Response: ' . wp_json_encode( $ruleset_body ) );
+ return 'failed';
+ } catch ( Exception $e ) {
+ error_log( 'Advanced Cloudflare Cache: Exception when updating cache ruleset: ' . esc_html( $e->getMessage() ) );
+ return 'failed';
+ }
+ }
+}
diff --git a/includes/class-cloudflare-purger.php b/includes/class-cloudflare-purger.php
new file mode 100644
index 0000000..be7cccf
--- /dev/null
+++ b/includes/class-cloudflare-purger.php
@@ -0,0 +1,436 @@
+post_status ) {
+ return;
+ }
+ self::purge_post_with_related( $post );
+ }
+
+ /**
+ * Purge cache tags associated with a post being published or unpublished.
+ *
+ * @param string $new_status New status for the post.
+ * @param string $old_status Old status for the post.
+ * @param WP_Post $post Post object.
+ */
+ public function action_transition_post_status( $new_status, $old_status, $post ) {
+ if ( 'publish' !== $new_status && 'publish' !== $old_status ) {
+ return;
+ }
+ self::purge_post_with_related( $post );
+ if ( 'publish' === $old_status ) {
+ return;
+ }
+ // Targets 404 pages that could be cached with no cache tags (i.e.
+ // a drafted post going live after the 404 has been cached).
+ self::clear_post_path( $post );
+ }
+
+
+ /**
+ * Purge the cache for a given post's path
+ *
+ * @param WP_Post $post Post object.
+ *
+ * @since 1.0.0
+ */
+ public function clear_post_path( $post ) {
+ $post_path = get_permalink( $post->ID );
+ $parsed_url = parse_url( $post_path );
+ $path = $parsed_url['path'];
+ $paths = [ trailingslashit( $path ), untrailingslashit( $path ) ];
+
+ /**
+ * Paths possibly without cache tags purges
+ *
+ * @param array $paths paths to clear.
+ */
+ $paths = apply_filters( 'ec_clear_post_path', $paths );
+ Cloudflare_Client::purgeByUrls( $paths );
+ }
+
+ /**
+ * Purge cache tags associated with a post being deleted.
+ *
+ * @param integer $post_id ID for the post to be deleted.
+ */
+ public function action_before_delete_post( $post_id ) {
+ $post = get_post( $post_id );
+ self::purge_post_with_related( $post );
+ }
+
+ /**
+ * Purge cache tags associated with an attachment being deleted.
+ *
+ * @param integer $post_id ID for the modified attachment.
+ */
+ public function action_delete_attachment( $post_id ) {
+ $post = get_post( $post_id );
+ self::purge_post_with_related( $post );
+ }
+
+ /**
+ * Purge the post's cache tag when the post cache is cleared.
+ *
+ * @param integer $post_id ID for the modified post.
+ */
+ public function action_clean_post_cache( $post_id ) {
+ $type = get_post_type( $post_id );
+
+ /**
+ * Allow specific post types to ignore the purge process.
+ *
+ * @param array $ignored_post_types Post types to ignore.
+ *
+ * @return array
+ * @since 1.0.0
+ */
+ $ignored_post_types = apply_filters( 'ec_purge_post_type_ignored', [ 'revision' ] );
+
+ if ( $type && in_array( $type, $ignored_post_types, true ) ) {
+ return;
+ }
+
+ $keys = [
+ 'post-' . $post_id,
+ 'rest-post-' . $post_id,
+ 'post-huge',
+ 'rest-post-huge',
+ ];
+
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when clearing post cache.
+ *
+ * @param array $keys cache tags.
+ * @param array $post_id ID for purged post.
+ */
+ $keys = apply_filters( 'ec_purge_clean_post_cache', $keys, $post_id );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+ /**
+ * Purge cache tags associated with a term being created.
+ *
+ * @param integer $term_id ID for the created term.
+ * @param int $tt_id Term taxonomy ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ public function action_created_term( $term_id, $tt_id, $taxonomy ) {
+ self::purge_term( $term_id );
+ $keys = [ 'rest-' . $taxonomy . '-collection' ];
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when creating a new term.
+ *
+ * @param array $keys cache tags.
+ * @param array $term_id ID for new term.
+ * @param array $tt_id Term taxonomy ID for new term.
+ * @param string $taxonomy Taxonomy for the new term.
+ */
+ $keys = apply_filters( 'ec_purge_create_term', $keys, $term_id, $tt_id, $taxonomy );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+ /**
+ * Purge cache tags associated with a term being edited.
+ *
+ * @param integer $term_id ID for the edited term.
+ */
+ public function action_edited_term( $term_id ) {
+ self::purge_term( $term_id );
+ }
+
+ /**
+ * Purge cache tags associated with a term being deleted.
+ *
+ * @param integer $term_id ID for the deleted term.
+ */
+ public function action_delete_term( $term_id ) {
+ self::purge_term( $term_id );
+ }
+
+ /**
+ * Purge the term's archive cache tag when the term is modified.
+ *
+ * @param integer $term_ids One or more IDs of modified terms.
+ */
+ public function action_clean_term_cache( $term_ids ) {
+ $keys = [];
+ $term_ids = is_array( $term_ids ) ? $term_ids : [ $term_ids ];
+ foreach ( $term_ids as $term_id ) {
+ $keys[] = 'term-' . $term_id;
+ $keys[] = 'rest-term-' . $term_id;
+ }
+ $keys[] = 'term-huge';
+ $keys[] = 'rest-term-huge';
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when clearing term cache.
+ *
+ * @param array $keys cache tags.
+ * @param array $term_ids IDs for purged terms.
+ */
+ $keys = apply_filters( 'ec_purge_clean_term_cache', $keys, $term_ids );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+ /**
+ * Purge cache tags when an approved comment is updated.
+ *
+ * @param integer $id The comment ID.
+ * @param WP_Comment $comment Comment object.
+ */
+ public function action_wp_insert_comment( $id, $comment ) {
+ if ( 1 !== (int) $comment->comment_approved ) {
+ return;
+ }
+ $keys = [
+ 'rest-comment-' . $comment->comment_ID,
+ 'rest-comment-collection',
+ 'rest-comment-huge',
+ ];
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when inserting a new comment.
+ *
+ * @param array $keys cache tags.
+ * @param integer $id Comment ID.
+ * @param WP_Comment $comment Comment to be inserted.
+ */
+ $keys = apply_filters( 'ec_purge_insert_comment', $keys, $id, $comment );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+ /**
+ * Purge cache tags when a comment is approved or unapproved.
+ *
+ * @param int|string $new_status The new comment status.
+ * @param int|string $old_status The old comment status.
+ * @param object $comment The comment data.
+ */
+ public function action_transition_comment_status( $new_status, $old_status, $comment ) {
+ $keys = [
+ 'rest-comment-' . $comment->comment_ID,
+ 'rest-comment-collection',
+ 'rest-comment-huge',
+ ];
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when transitioning a comment status.
+ *
+ * @param array $keys cache tags.
+ * @param string $new_status New comment status.
+ * @param string $old_status Old comment status.
+ * @param WP_Comment $comment Comment being transitioned.
+ */
+ $keys = apply_filters( 'ec_purge_transition_comment_status', $keys, $new_status, $old_status, $comment );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+ /**
+ * Purge the comment's cache tag when the comment is modified.
+ *
+ * @param integer $comment_id Modified comment id.
+ */
+ public function action_clean_comment_cache( $comment_id ) {
+ $keys = [
+ 'rest-comment-' . $comment_id,
+ 'rest-comment-huge',
+ ];
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when cleaning comment cache.
+ *
+ * @param array $keys cache tags.
+ * @param integer $id Comment ID.
+ */
+ $keys = apply_filters( 'ec_purge_clean_comment_cache', $keys, $comment_id );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+ /**
+ * Purge the cache tags associated with a post being modified.
+ *
+ * @param object $post Object representing the modified post.
+ */
+ private function purge_post_with_related( $post ) {
+ /**
+ * Allow specific post types to ignore the purge process.
+ *
+ * @param array $ignored_post_types Post types to ignore.
+ *
+ * @return array
+ * @since 1.0.0
+ */
+ $ignored_post_types = apply_filters( 'ec_purge_post_type_ignored', [ 'revision' ] );
+
+ if ( in_array( $post->post_type, $ignored_post_types, true ) ) {
+ return;
+ }
+
+ $keys = [
+ 'post-' . $post->ID,
+ $post->post_type . '-archive',
+ 'rest-' . $post->post_type . '-collection',
+ 'home',
+ 'front',
+ '404',
+ 'feed',
+ 'post-huge',
+ ];
+
+ if ( post_type_supports( $post->post_type, 'author' ) ) {
+ $keys[] = 'user-' . $post->post_author;
+ $keys[] = 'user-huge';
+ }
+
+ if ( post_type_supports( $post->post_type, 'comments' ) ) {
+ $keys[] = 'rest-comment-post-' . $post->ID;
+ $keys[] = 'rest-comment-post-huge';
+ }
+
+ $taxonomies = wp_list_filter(
+ get_object_taxonomies( $post->post_type, 'objects' ),
+ [ 'public' => true ]
+ );
+
+ foreach ( $taxonomies as $taxonomy ) {
+ $terms = get_the_terms( $post, $taxonomy->name );
+ if ( $terms ) {
+ foreach ( $terms as $term ) {
+ $keys[] = 'term-' . $term->term_id;
+ }
+ $keys[] = 'term-huge';
+ }
+ }
+
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * Related cache tags purged when purging a post.
+ *
+ * @param array $keys cache tags.
+ * @param WP_Post $post Post object.
+ */
+ $keys = apply_filters( 'ec_purge_post_with_related', $keys, $post );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+ /**
+ * Purge the cache tags associated with a term being modified.
+ *
+ * @param integer $term_id ID for the modified term.
+ */
+ private function purge_term( $term_id ) {
+ $keys = [
+ 'term-' . $term_id,
+ 'rest-term-' . $term_id,
+ 'post-term-' . $term_id,
+ 'term-huge',
+ 'rest-term-huge',
+ 'post-term-huge',
+ ];
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when purging a term.
+ *
+ * @param array $keys cache tags.
+ * @param integer $term_id Term ID.
+ */
+ $keys = apply_filters( 'ec_purge_term', $keys, $term_id );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+
+ /**
+ * Purge a variety of cache tags when a user is modified.
+ *
+ * @param integer $user_id ID for the modified user.
+ */
+ public function action_clean_user_cache( $user_id ) {
+ $keys = [
+ 'user-' . $user_id,
+ 'rest-user-' . $user_id,
+ 'user-huge',
+ 'rest-user-huge',
+ ];
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when clearing user cache.
+ *
+ * @param array $keys cache tags.
+ * @param array $user_id ID for purged user.
+ */
+ $keys = apply_filters( 'ec_purge_clean_user_cache', $keys, $user_id );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+
+ /**
+ * Purge a variety of cache tags when an option is modified.
+ *
+ * @param string $option Name of the updated option.
+ */
+ public function action_updated_option( $option ) {
+ if ( ! function_exists( 'get_registered_settings' ) ) {
+ return;
+ }
+ $settings = get_registered_settings();
+ if ( empty( $settings[ $option ] ) || empty( $settings[ $option ]['show_in_rest'] ) ) {
+ return;
+ }
+ $rest_name = ! empty( $settings[ $option ]['show_in_rest']['name'] ) ? $settings[ $option ]['show_in_rest']['name'] : $option;
+ $keys = [
+ 'rest-setting-' . $rest_name,
+ 'rest-setting-huge',
+ ];
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ /**
+ * cache tags purged when updating an option cache.
+ *
+ * @param array $keys cache tags.
+ * @param string $option Option name.
+ */
+ $keys = apply_filters( 'ec_purge_updated_option', $keys, $option );
+ Cloudflare_Client::purgeByTags( $keys );
+ }
+}
diff --git a/includes/class-cloudflare-tag-emitter.php b/includes/class-cloudflare-tag-emitter.php
new file mode 100644
index 0000000..4022f20
--- /dev/null
+++ b/includes/class-cloudflare-tag-emitter.php
@@ -0,0 +1,462 @@
+ true ], 'objects' ) as $post_type ) {
+ add_filter( "rest_prepare_{$post_type->name}", [ $this, 'filter_rest_prepare_post' ], 10, 3 );
+ $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
+ self::get_instance()->rest_api_collection_endpoints[ '/wp/v2/' . $base ] = $post_type->name;
+ }
+ foreach ( get_taxonomies( [ 'show_in_rest' => true ], 'objects' ) as $taxonomy ) {
+ add_filter( "rest_prepare_{$taxonomy->name}", [ $this, 'filter_rest_prepare_term' ], 10, 3 );
+ $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
+ self::get_instance()->rest_api_collection_endpoints[ '/wp/v2/' . $base ] = $taxonomy->name;
+ }
+ add_filter( 'rest_prepare_comment', [ $this, 'filter_rest_prepare_comment' ], 10, 3 );
+ self::get_instance()->rest_api_collection_endpoints['/wp/v2/comments'] = 'comment';
+ add_filter( 'rest_prepare_user', [ $this, 'filter_rest_prepare_user' ], 10, 3 );
+ add_filter( 'rest_pre_get_setting', [ $this, 'filter_rest_pre_get_setting' ], 10, 2 );
+ self::get_instance()->rest_api_collection_endpoints['/wp/v2/users'] = 'user';
+ }
+
+ /**
+ * Reset cache tags before a REST API response is generated.
+ *
+ * @param mixed $result Response to replace the requested version with.
+ * @param WP_REST_Server $server Server instance.
+ * @param WP_REST_Request $request Request used to generate the response.
+ */
+ public function filter_rest_pre_dispatch( $result, $server, $request ) {
+ if ( isset( self::get_instance()->rest_api_collection_endpoints[ $request->get_route() ] ) ) {
+ self::get_instance()->rest_api_cache_tags[] = 'rest-' . self::get_instance()->rest_api_collection_endpoints[ $request->get_route() ] . '-collection';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Render cache tags after a REST API response is prepared
+ *
+ * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response.
+ * @param WP_REST_Server $server Server instance.
+ */
+ public function filter_rest_post_dispatch( $result, $server ) {
+ $keys = self::get_rest_api_cache_tags();
+ if ( ! empty( $keys ) && $result instanceof \WP_REST_Response ) {
+ $result->header( self::HEADER_KEY, implode( ' ', $keys ) );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Determine which posts are present in a REST API response.
+ *
+ * @param WP_REST_Response $response The response object.
+ * @param WP_Post $post Post object.
+ * @param WP_REST_Request $request Request object.
+ */
+ public function filter_rest_prepare_post( $response, $post, $request ) {
+ self::get_instance()->rest_api_cache_tags[] = 'rest-post-' . $post->ID;
+
+ return $response;
+ }
+
+ /**
+ * Determine which terms are present in a REST API response.
+ *
+ * @param WP_REST_Response $response The response object.
+ * @param WP_Post $term Term object.
+ * @param WP_REST_Request $request Request object.
+ */
+ public function filter_rest_prepare_term( $response, $term, $request ) {
+ self::get_instance()->rest_api_cache_tags[] = 'rest-term-' . $term->term_id;
+
+ return $response;
+ }
+
+ /**
+ * Determine which comments are present in a REST API response.
+ *
+ * @param WP_REST_Response $response The response object.
+ * @param WP_Comment $comment The original comment object.
+ * @param WP_REST_Request $request Request used to generate the response.
+ */
+ public function filter_rest_prepare_comment( $response, $comment, $request ) {
+ self::get_instance()->rest_api_cache_tags[] = 'rest-comment-' . $comment->comment_ID;
+ self::get_instance()->rest_api_cache_tags[] = 'rest-comment-post-' . $comment->comment_post_ID;
+
+ return $response;
+ }
+
+ /**
+ * Determine which users are present in a REST API response.
+ *
+ * @param WP_REST_Response $response The response object.
+ * @param WP_Post $user User object.
+ * @param WP_REST_Request $request Request object.
+ */
+ public function filter_rest_prepare_user( $response, $user, $request ) {
+ self::get_instance()->rest_api_cache_tags[] = 'rest-user-' . $user->ID;
+
+ return $response;
+ }
+
+ /**
+ * Determine which settings are present in a REST API request
+ *
+ * @param mixed $result Value to use for the requested setting. Can be a scalar
+ * matching the registered schema for the setting, or null to
+ * follow the default get_option() behavior.
+ * @param string $name Setting name (as shown in REST API responses).
+ */
+ public function filter_rest_pre_get_setting( $result, $name ) {
+ self::get_instance()->rest_api_cache_tags[] = 'rest-setting-' . $name;
+
+ return $result;
+ }
+
+ /**
+ * Get the cache tags to be included in this view.
+ *
+ * cache tags are generated based on the main WP_Query.
+ *
+ * @return array
+ */
+ public function get_main_query_cache_tags() {
+ global $wp_query;
+
+ $keys = [];
+ if ( is_front_page() ) {
+ $keys[] = 'front';
+ }
+ if ( is_home() ) {
+ $keys[] = 'home';
+ }
+ if ( is_404() ) {
+ $keys[] = '404';
+ }
+ if ( is_feed() ) {
+ $keys[] = 'feed';
+ }
+ if ( is_date() ) {
+ $keys[] = 'date';
+ }
+ if ( is_paged() ) {
+ $keys[] = 'paged';
+ }
+ if ( is_search() ) {
+ $keys[] = 'search';
+ if ( $wp_query->found_posts ) {
+ $keys[] = 'search-results';
+ } else {
+ $keys[] = 'search-no-results';
+ }
+ }
+
+ if ( ! empty( $wp_query->posts ) ) {
+ foreach ( $wp_query->posts as $p ) {
+ $keys[] = 'post-' . $p->ID;
+ if ( $wp_query->is_singular() ) {
+ if ( post_type_supports( $p->post_type, 'author' ) ) {
+ $keys[] = 'post-user-' . $p->post_author;
+ }
+
+ /**
+ * Filter ec_should_add_terms
+ * Gives the option to skip taxonomy terms for a given post
+ *
+ * @param $add_terms whether or not to create cache tags for a given post's taxonomy terms.
+ * @param $wp_query the full WP_Query object.
+ *
+ * @return bool
+ * usage: add_filter( 'ec_should_add_terms',"__return_false", 10, 2);
+ */
+ $add_terms = apply_filters( 'ec_should_add_terms', true, $wp_query );
+ if ( ! $add_terms ) {
+ continue;
+ }
+
+ foreach ( get_object_taxonomies( $p ) as $tax ) {
+ $terms = get_the_terms( $p->ID, $tax );
+ if ( $terms && ! is_wp_error( $terms ) ) {
+ foreach ( $terms as $t ) {
+ $keys[] = 'post-term-' . $t->term_id;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( is_singular() ) {
+ $keys[] = 'single';
+ if ( is_attachment() ) {
+ $keys[] = 'attachment';
+ }
+ } elseif ( is_archive() ) {
+ $keys[] = 'archive';
+ if ( is_post_type_archive() ) {
+ $keys[] = 'post-type-archive';
+ $post_types = get_query_var( 'post_type' );
+ // If multiple post types are queried, create a surrogate key for each.
+ if ( is_array( $post_types ) ) {
+ foreach ( $post_types as $post_type ) {
+ $keys[] = "$post_type-archive";
+ }
+ } else {
+ $keys[] = "$post_types-archive";
+ }
+ } elseif ( is_author() ) {
+ $user_id = get_queried_object_id();
+ if ( $user_id ) {
+ $keys[] = 'user-' . $user_id;
+ }
+ } elseif ( is_category() || is_tag() || is_tax() ) {
+ $term_id = get_queried_object_id();
+ if ( $term_id ) {
+ $keys[] = 'term-' . $term_id;
+ }
+ }
+ }
+
+ // Don't emit cache tags in the admin, unless defined by the filter.
+ if ( is_admin() ) {
+ $keys = [];
+ }
+
+ /**
+ * Customize cache tags sent in the header.
+ *
+ * @param array $keys Existing cache tags generated by the plugin.
+ */
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ $keys = apply_filters( 'ec_main_query_cache_tags', $keys );
+ $keys = array_unique( $keys );
+ $keys = self::filter_huge_cache_tags_list( $keys );
+
+ return $keys;
+ }
+
+ /**
+ * Get the cache tags to be included in this view.
+ *
+ * cache tags are generated based on filters added to REST API controllers.
+ *
+ * @return array
+ */
+ public function get_rest_api_cache_tags() {
+
+ /**
+ * Customize cache tags sent in the REST API header.
+ *
+ * @param array $keys Existing cache tags generated by the plugin.
+ */
+ $keys = self::get_instance()->rest_api_cache_tags;
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ $keys = apply_filters( 'ec_rest_api_cache_tags', $keys );
+ $keys = array_unique( $keys );
+ $keys = self::filter_huge_cache_tags_list( $keys );
+
+ return $keys;
+ }
+
+ /**
+ * Reset cache tags stored on the instance.
+ */
+ public function reset_rest_api_cache_tags() {
+ self::get_instance()->rest_api_cache_tags = [];
+ }
+
+ /**
+ * Filter the cache tags to ensure that the length doesn't exceed what nginx can handle.
+ *
+ * @param array $keys Existing cache tags generated by the plugin.
+ *
+ * @return array
+ */
+ public function filter_huge_cache_tags_list( $keys ) {
+ $output = implode( ' ', $keys );
+ if ( strlen( $output ) <= self::HEADER_MAX_LENGTH ) {
+ return $keys;
+ }
+
+ $keycats = [];
+ foreach ( $keys as $k ) {
+ $p = strrpos( $k, '-' );
+ if ( false === $p ) {
+ $keycats[ $k ][] = $k;
+ continue;
+ }
+ $cat = substr( $k, 0, $p + 1 );
+ $keycats[ $cat ][] = $k;
+ }
+
+ // Sort by the output length of the key category.
+ uasort(
+ $keycats,
+ function ( $a, $b ) {
+ $ca = strlen( implode( ' ', $a ) );
+ $cb = strlen( implode( ' ', $b ) );
+ if ( $ca === $cb ) {
+ return 0;
+ }
+
+ return $ca > $cb ? - 1 : 1;
+ }
+ );
+
+ $cats = array_keys( $keycats );
+ foreach ( $cats as $c ) {
+ $keycats[ $c ] = [ $c . 'huge' ];
+ $keyout = [];
+ foreach ( $keycats as $v ) {
+ $keyout = array_merge( $keyout, $v );
+ }
+ $output = implode( ' ', $keyout );
+ if ( strlen( $output ) <= self::HEADER_MAX_LENGTH ) {
+ return $keyout;
+ }
+ }
+
+ return $keyout;
+ }
+
+ /**
+ * Inspect the model and get the right cache tags.
+ *
+ * @param WPGraphQL\Model\Model|mixed $model Model object, array, etc.
+ */
+ public function filter_graphql_dataloader_get_model( $model ) {
+ if ( ! $model instanceof \WPGraphQL\Model\Model ) {
+ return $model;
+ }
+
+ $reflect = new \ReflectionClass( $model );
+ $class_short_name = $reflect->getShortName();
+ $cache_tag_prefix = strtolower( $class_short_name );
+ if ( isset( $model->id ) ) {
+ if ( ! empty( $model->databaseId ) ) {
+ self::get_instance()->graphql_cache_tags[] = $cache_tag_prefix . '-' . $model->databaseId;
+ }
+ }
+
+ return $model;
+ }
+
+ /**
+ * Get the cache tags to be included in this view.
+ *
+ * cache tags are generated based on filters added to GraphQL controllers.
+ *
+ * @return array
+ */
+ public function get_graphql_cache_tags() {
+
+ /**
+ * Customize cache tags sent in the GraphQL header.
+ *
+ * @param array $keys Existing cache tags generated by the plugin.
+ */
+ $keys = self::get_instance()->graphql_cache_tags;
+ $keys[] = 'graphql-collection';
+ $keys = ec_cf_prefix_cache_tags_with_blog_id( $keys );
+ $keys = apply_filters( 'ec_graphql_cache_tags', $keys );
+ $keys = array_unique( $keys );
+ $keys = self::filter_huge_cache_tags_list( $keys );
+
+ return $keys;
+ }
+
+ /**
+ * Send additional headers to graphql response.
+ *
+ * @param array $headers Existing headers as set by graphql plugin.
+ */
+ public function filter_graphql_response_headers_to_send( $headers ) {
+ $keys = self::get_graphql_cache_tags();
+ if ( ! empty( $keys ) ) {
+ $headers[ self::HEADER_KEY ] = implode( ' ', $keys );
+ }
+
+ return $headers;
+ }
+}
diff --git a/includes/class-nginx-helper.php b/includes/class-nginx-helper.php
index 7cf8cb0..085e7a2 100644
--- a/includes/class-nginx-helper.php
+++ b/includes/class-nginx-helper.php
@@ -12,6 +12,9 @@
* @subpackage nginx-helper/includes
*/
+use EasyCache\Cloudflare_Purger;
+use EasyCache\CloudFlare_Tag_Emitter;
+
/**
* The core plugin class.
*
@@ -237,7 +240,7 @@ private function define_admin_hooks() {
// expose action to allow other plugins to purge the cache.
$this->loader->add_action( 'rt_nginx_helper_purge_all', $nginx_purger, 'purge_all' );
-
+
// add action to preload the cache
$this->loader->add_action( 'admin_init', $nginx_helper_admin, 'preload_cache' );
$this->loader->add_action( 'plugins_loaded', $this, 'handle_nginx_helper_upgrade' );
@@ -253,6 +256,38 @@ private function define_admin_hooks() {
// WooCommerce integration.
$this->loader->add_action( 'plugins_loaded', $nginx_helper_admin, 'init_woocommerce_hooks' );
+ if ( $nginx_helper_admin->cf_options['is_enabled'] ) {
+ $this->loader->add_filter( 'wp_headers', $this, 'handle_cloudflare_headers', 999 );
+ $this->loader->add_action( 'admin_bar_menu', $nginx_helper_admin, 'add_cloudflare_admin_bar_purge', 100 );
+ $this->loader->add_action( 'wp_ajax_ec_clear_url_cache', $nginx_helper_admin, 'handle_cloudflare_clear_cache_ajax' );
+
+ // Add the cache tags.
+ $this->loader->add_filter( 'wp', CloudFlare_Tag_Emitter::get_instance(), 'action_wp' );
+ $this->loader->add_action( 'rest_api_init', CloudFlare_Tag_Emitter::get_instance(), 'action_rest_api_init' );
+ $this->loader->add_filter( 'rest_pre_dispatch', CloudFlare_Tag_Emitter::get_instance(), 'filter_rest_pre_dispatch', 10, 3 );
+ $this->loader->add_filter( 'rest_post_dispatch', CloudFlare_Tag_Emitter::get_instance(), 'filter_rest_post_dispatch', 10, 2 );
+ $this->loader->add_filter( 'graphql_dataloader_get_model', CloudFlare_Tag_Emitter::get_instance(), 'filter_graphql_dataloader_get_model' );
+ $this->loader->add_filter( 'graphql_response_headers_to_send', CloudFlare_Tag_Emitter::get_instance(), 'filter_graphql_response_headers_to_send' );
+
+ /**
+ * Clears cache tags when various modification behaviors are performed.
+ */
+ $this->loader->add_action( 'wp_insert_post', Cloudflare_Purger::get_instance(), 'action_wp_insert_post', 10, 2 );
+ $this->loader->add_action( 'transition_post_status', Cloudflare_Purger::get_instance(), 'action_transition_post_status', 10, 3 );
+ $this->loader->add_action( 'before_delete_post', Cloudflare_Purger::get_instance(), 'action_before_delete_post' );
+ $this->loader->add_action( 'delete_attachment', Cloudflare_Purger::get_instance(), 'action_delete_attachment' );
+ $this->loader->add_action( 'clean_post_cache', Cloudflare_Purger::get_instance(), 'action_clean_post_cache' );
+ $this->loader->add_action( 'created_term', Cloudflare_Purger::get_instance(), 'action_created_term', 10, 3 );
+ $this->loader->add_action( 'edited_term', Cloudflare_Purger::get_instance(), 'action_edited_term' );
+ $this->loader->add_action( 'delete_term', Cloudflare_Purger::get_instance(), 'action_delete_term' );
+ $this->loader->add_action( 'clean_term_cache', Cloudflare_Purger::get_instance(), 'action_clean_term_cache' );
+ $this->loader->add_action( 'wp_insert_comment', Cloudflare_Purger::get_instance(), 'action_wp_insert_comment', 10, 2 );
+ $this->loader->add_action( 'transition_comment_status', Cloudflare_Purger::get_instance(), 'action_transition_comment_status', 10, 3 );
+ $this->loader->add_action( 'clean_comment_cache', Cloudflare_Purger::get_instance(), 'action_clean_comment_cache' );
+ $this->loader->add_action( 'clean_user_cache', Cloudflare_Purger::get_instance(), 'action_clean_user_cache' );
+ $this->loader->add_action( 'updated_option', Cloudflare_Purger::get_instance(), 'action_updated_option' );
+ }
+
}
/**
@@ -351,12 +386,62 @@ public function handle_nginx_helper_upgrade() {
$installed_version = get_option( 'nginx_helper_version', '0' );
if ( version_compare( $installed_version, $this->get_version(), '<' ) ) {
-
+
require_once NGINX_HELPER_BASEPATH . 'includes/class-nginx-helper-activator.php';
Nginx_Helper_Activator::set_user_caps();
update_option( 'nginx_helper_version', $this->get_version() );
}
+ }
+
+ /**
+ * Manage the cache headers for Cloudflare.
+ *
+ * @param array $headers The headers of the site.
+ *
+ * @return array The modified headers for cache.
+ */
+ public function handle_cloudflare_headers( $headers ) {
+
+ // Defensively remove any Cache-Control or Expires headers set by the server or other plugins.
+ // This ensures our plugin has the final say.
+ if ( isset( $headers['Cache-Control'] ) ) {
+ unset( $headers['Cache-Control'] );
+ }
+ if ( isset( $headers['Expires'] ) ) {
+ unset( $headers['Expires'] );
+ }
+
+ // Conditions for NOT caching (logged-in, admin, search, etc.)
+ $do_not_cache = is_user_logged_in() || is_admin() || is_search() || is_404() || is_customize_preview();
+
+ // Also check for common dynamic cookies
+ if ( ! $do_not_cache && ! empty( $_COOKIE ) ) {
+ foreach ( array_keys( $_COOKIE ) as $cookie_key ) {
+ if ( strpos( $cookie_key, 'wordpress_logged_in' ) !== false || strpos( $cookie_key, 'woocommerce_items_in_cart' ) !== false ) {
+ $do_not_cache = true;
+ break;
+ }
+ }
+ }
+
+ if ( $do_not_cache ) {
+ // User is logged in or page is dynamic. Send explicit NO CACHE headers.
+ $headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0';
+ $headers['Pragma'] = 'no-cache'; // For legacy HTTP/1.0 compatibility
+ $headers['Expires'] = 'Wed, 11 Jan 1984 05:00:00 GMT'; // Date in the past
+ } else {
+ // Page is for an anonymous user and is cacheable.
+ $options = get_option( 'easycache_cf_settings' );
+ $ttl = isset( $options['default_cache_ttl'] ) ? (int) $options['default_cache_ttl'] : 0;
+
+ if ( $ttl > 0 ) {
+ // Send CDN-friendly caching headers.
+ $headers['Cache-Control'] = 'public, max-age=0, s-maxage=' . $ttl;
+ }
+ }
+
+ return $headers;
}
}
diff --git a/nginx-helper.php b/nginx-helper.php
index bb3ff52..d2e610b 100644
--- a/nginx-helper.php
+++ b/nginx-helper.php
@@ -21,6 +21,11 @@
die;
}
+// Load Composer dependencies.
+if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
+ require_once __DIR__ . '/vendor/autoload.php';
+}
+
/**
* Base URL of plugin
*/
@@ -42,6 +47,9 @@
define( 'NGINX_HELPER_BASEPATH', plugin_dir_path( __FILE__ ) );
}
+require_once NGINX_HELPER_BASEPATH . '/utils/functions.php';
+require_once NGINX_HELPER_BASEPATH . '/utils/autoloader.php';
+
/**
* The code that runs during plugin activation.
* This action is documented in includes/class-nginx-helper-activator.php
@@ -90,7 +98,7 @@ function run_nginx_helper() {
require_once NGINX_HELPER_BASEPATH . 'class-nginx-helper-wp-cli-command.php';
\WP_CLI::add_command( 'nginx-helper', 'Nginx_Helper_WP_CLI_Command' );
-
+ \WP_CLI::add_command( 'cloudflare cache', 'EasyCache\\CLI' );
}
}
diff --git a/utils/autoloader.php b/utils/autoloader.php
new file mode 100644
index 0000000..94125dc
--- /dev/null
+++ b/utils/autoloader.php
@@ -0,0 +1,29 @@
+