Orderendurance-page-cache.php 0000644 00000141263 14757772062 0011224 0 ustar 00 1|0 (default 1)
* 'epc' => 1|0 (default 0)
*
* @var array
*/
public $udev_api_services = array(
'cf' => 1,
'epc' => 0,
);
/**
* The hook name for scheduling a cache purge event.
*
* @var string
*/
public $epc_scheduled_purge_all_hook = 'epc_scheduled_purge_all';
/**
* Endurance_Page_Cache constructor.
*/
public function __construct() {
if ( isset( $_GET['doing_wp_cron'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
add_action( $this->epc_scheduled_purge_all_hook, array( __CLASS__, 'scheduled_purge_all' ) );
return;
}
$this->throttled = array_filter( (array) get_transient( 'epc_throttled' ) );
$this->cache_level = $this->get_cache_level();
$this->cache_dir = WP_CONTENT_DIR . '/endurance-page-cache';
$cloudflare_state = get_option( 'endurance_cloudflare_enabled', false );
$this->cloudflare_enabled = (bool) $cloudflare_state;
$this->cloudflare_tier = ( is_numeric( $cloudflare_state ) && $cloudflare_state ) ? 'basic' : $cloudflare_state;
$this->udev_api_services['cf'] = $this->cloudflare_tier;
$path = defined( 'ABSPATH' ) ? ABSPATH : __DIR__;
$this->file_based_enabled = (bool) get_option( 'endurance_file_enabled', false === strpos( $path, 'public_html' ) );
array_push( $this->cache_exempt, rest_get_url_prefix() );
$this->hooks();
}
/**
* Retrieves the cache level from the database
*
* If cache level is set higher than 3, then it will reset it down to level 3
*
* @return int
*/
public function get_cache_level() {
$level = absint( get_option( 'endurance_cache_level', 2 ) );
if ( $level > 3 ) {
$level = 3;
update_option( 'endurance_cache_level', $level );
}
return $level;
}
/**
* Setup all WordPress actions and filters.
*/
public function hooks() {
if ( $this->is_enabled( 'page' ) ) {
add_action( 'init', array( $this, 'start' ) );
add_action( 'shutdown', array( $this, 'finish' ) );
add_action( 'shutdown', array( $this, 'shutdown' ) );
add_action( 'generate_rewrite_rules', array( $this, 'config_nginx' ) );
}
add_filter( 'mod_rewrite_rules', array( $this, 'htaccess_contents_rewrites' ), 77 );
add_filter( 'mod_rewrite_rules', array( $this, 'htaccess_contents_expirations' ), 88 );
add_action( 'update_option_endurance_cache_level', array( $this, 'update_htaccess' ) );
add_action( 'update_option_endurance_file_enabled', array( $this, 'update_htaccess' ) );
add_action( 'update_option_epc_skip_404_handling', array( $this, 'update_htaccess' ) );
add_action( 'update_option_epc_filetype_expirations', array( $this, 'update_htaccess' ) );
add_action( 'delete_option_epc_filetype_expirations', array( $this, 'update_htaccess' ) );
add_action( 'admin_init', array( $this, 'register_cache_settings' ) );
add_action( 'transition_post_status', array( $this, 'save_post' ), 10, 3 );
add_action( 'edit_terms', array( $this, 'edit_terms' ) );
add_action( 'comment_post', array( $this, 'comment' ) );
add_action( 'updated_option', array( $this, 'option_handler' ), 10, 3 );
add_action( 'epc_purge', array( $this, 'purge_all' ) );
add_action( 'epc_purge_request', array( $this, 'purge_request' ) );
add_action( 'wp_update_nav_menu', array( $this, 'purge_all' ) );
add_action( 'admin_bar_menu', array( $this, 'admin_toolbar' ), 99 );
add_action( 'init', array( $this, 'do_purge' ) );
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'status_link' ) );
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'update' ) );
add_filter( 'pre_update_option_mm_cache_settings', array( $this, 'cache_type_change' ), 10, 2 );
add_filter( 'pre_update_option_endurance_cache_level', array( $this, 'cache_level_change' ), 10, 2 );
add_filter( 'got_rewrite', array( $this, 'force_rewrite' ) );
add_action( 'shutdown', array( $this, 'udev_cache_purge_via_buffer' ) );
}
/**
* Customize the WP Admin Bar.
*
* @param \WP_Admin_Bar $wp_admin_bar Instance of the admin bar.
*/
public function admin_toolbar( $wp_admin_bar ) {
if ( current_user_can( 'manage_options' ) && $this->is_enabled() ) {
$args = array(
'id' => 'epc_purge_menu',
'title' => 'Caching',
);
$wp_admin_bar->add_node( $args );
$args = array(
'id' => 'epc_purge_menu-purge_all',
'title' => 'Purge All',
'parent' => 'epc_purge_menu',
'href' => add_query_arg( array( 'epc_purge_all' => true ) ),
);
$wp_admin_bar->add_node( $args );
if ( ! is_admin() ) {
$args = array(
'id' => 'epc_purge_menu-purge_single',
'title' => 'Purge This Page',
'parent' => 'epc_purge_menu',
'href' => add_query_arg( array( 'epc_purge_single' => true ) ),
);
$wp_admin_bar->add_node( $args );
}
$args = array(
'id' => 'epc_purge_menu-cache_settings',
'title' => 'Cache Settings',
'parent' => 'epc_purge_menu',
'href' => admin_url( 'options-general.php#epc_settings' ),
);
$wp_admin_bar->add_node( $args );
}
}
/**
* Register fields for cache settings.
*/
public function register_cache_settings() {
$section_name = 'epc_settings_section';
add_settings_section(
$section_name,
'Endurance Cache',
'__return_false',
'general'
);
add_settings_field(
'endurance_cache_level',
'Cache Level',
array( $this, 'output_cache_settings' ),
'general',
$section_name,
array( 'field' => 'endurance_cache_level' )
);
add_settings_field(
'epc_skip_404_handling',
'Skip WordPress 404 Handling For Static Files',
function () {
echo '';
},
'general',
$section_name,
array( 'field' => 'epc_skip_404_handling' )
);
register_setting( 'general', 'endurance_cache_level' );
register_setting( 'general', 'epc_skip_404_handling' );
}
/**
* Output the cache options.
*
* @param array $args Settings
*/
public function output_cache_settings( $args ) {
$cache_level = absint( get_option( $args['field'], 2 ) );
echo '';
}
/**
* Convert a string to studly case.
*
* @param string $value String to be converted.
*
* @return string
*/
public function to_studly_case( $value ) {
return str_replace( ' ', '', ucwords( str_replace( array( '-', '_' ), ' ', $value ) ) );
}
/**
* Convert a string to snake case.
*
* @param string $value String to be converted.
* @param string $delimiter Delimiter (can be a dash for conversion to kebab case).
*
* @return string
*/
public function to_snake_case( $value, $delimiter = '_' ) {
if ( ! ctype_lower( $value ) ) {
$value = preg_replace( '/(\s+)/u', '', ucwords( $value ) );
$value = trim( mb_strtolower( preg_replace( '/([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)/u', '$1' . $delimiter, $value ), 'UTF-8' ), $delimiter );
}
return $value;
}
/**
* Checks if this environment caches requests on the current filesystem
*
* @return bool True if uses file system to cache
*/
public function use_file_cache() {
return $this->file_based_enabled && $this->cache_level;
}
/**
* Whether or not to skip 404 handling for static files.
*
* Enable via WP-CLI: wp option set epc_skip_404_handling 1
*
* @return bool
*/
public function skip_404_handling() {
return (bool) get_option( 'epc_skip_404_handling' );
}
/**
* Handlers that listens for changes to options and checks to see, based on the option name, if the cache should
* be purged.
*
* @param string $option Option name
* @param mixed $old_value Old option value
* @param mixed $new_value New option value
*
* @return bool
*/
public function option_handler( $option, $old_value, $new_value ) {
// No need to process if nothing was updated
if ( $old_value === $new_value ) {
return false;
}
$exempt_if_equals = array(
'active_plugins' => true,
'html_type' => true,
'fs_accounts' => true,
'rewrite_rules' => true,
'uninstall_plugins' => true,
'wp_user_roles' => true,
);
// If we have an exact match, we can just stop here.
if ( array_key_exists( $option, $exempt_if_equals ) ) {
return false;
}
$force_if_contains = array(
'html',
'css',
'style',
'query',
'queries',
);
$exempt_if_contains = array(
'_active',
'_activated',
'_activation',
'_attempts',
'_available',
'_blacklist',
'_cache_validator',
'_check_',
'_checksum',
'_config',
'_count',
'_dectivated',
'_disable',
'_enable',
'_errors',
'_hash',
'_inactive',
'_installed',
'_key',
'_last_',
'_license',
'_log_',
'_mode',
'_options',
'_pageviews',
'_redirects',
'_rules',
'_schedule',
'_session',
'_settings',
'_shown',
'_stats',
'_status',
'_statistics',
'_supports',
'_sync',
'_task',
'_time',
'_token',
'_traffic',
'_transient',
'_url_',
'_version',
'_views',
'_visits',
'_whitelist',
'404s',
'cron',
'limit_login_',
'nonce',
'user_roles',
);
$force_purge = false;
if ( ctype_upper( str_replace( array( '-', '_' ), '', $option ) ) ) {
$option = strtolower( $option );
}
$option_name = '_' . $this->to_snake_case( $this->to_studly_case( $option ) ) . '_';
foreach ( $force_if_contains as $slug ) {
if ( false !== strpos( $option_name, $slug ) ) {
$force_purge = true;
break;
}
}
if ( ! $force_purge ) {
foreach ( $exempt_if_contains as $slug ) {
if ( false !== strpos( $option_name, $slug ) ) {
return false;
}
}
}
$this->add_trigger( 'option_handler' );
// Schedule a purge if not already scheduled.
$this->schedule_purge_all();
return true;
}
/**
* Schedules a single event for purging the cache.
*
* @return void
*/
public function schedule_purge_all() {
if ( ! wp_next_scheduled( $this->epc_scheduled_purge_all_hook ) ) {
wp_schedule_single_event( time() + 60, $this->epc_scheduled_purge_all_hook );
}
}
/**
* Static cron job handler to execute a purge all.
*/
public static function scheduled_purge_all() {
$instance = self::get_instance();
if ( $instance ) {
$instance->purge_all();
}
}
/**
* Purge single post when a comment is updated.
*
* @param int $comment_id ID of the comment.
*/
public function comment( $comment_id ) {
$comment = get_comment( $comment_id );
if ( $comment && property_exists( $comment, 'comment_post_ID' ) ) {
$post_url = get_permalink( $comment->comment_post_ID );
$this->purge_single( $post_url );
}
}
/**
* Purge appropriate caches when post when post is updated.
*
* @param string $old_status The previous post status
* @param string $new_status The new post status
* @param WP_Post $post The post object of the edited or created post
*/
public function save_post( $old_status, $new_status, $post ) {
$post_type_object = get_post_type_object( $post->post_type );
// Skip purging for non-public post types
if ( ! $post_type_object || ! $post_type_object->public ) {
return;
}
// Skip purging if the post wasn't public before and isn't now
if ( 'publish' !== $old_status && 'publish' !== $new_status ) {
return;
}
// Purge post URL when post is updated.
$permalink = get_permalink( $post );
if ( $permalink ) {
$this->purge_single( $permalink );
}
// Purge taxonomy term URLs for related terms.
$taxonomies = get_post_taxonomies( $post );
foreach ( $taxonomies as $taxonomy ) {
if ( $this->is_public_taxonomy( $taxonomy ) ) {
$terms = get_the_terms( $post, $taxonomy );
if ( is_array( $terms ) ) {
foreach ( $terms as $term ) {
$term_link = get_term_link( $term );
$this->purge_single( $term_link );
}
}
}
}
// Purge post type archive URL when post is updated.
$post_type_archive = get_post_type_archive_link( $post->post_type );
if ( $post_type_archive ) {
$this->purge_single( $post_type_archive );
}
// Purge date archive URL when post is updated.
$year_archive = get_year_link( (int) get_the_date( 'y', $post ) );
$year_archive_path = str_replace( get_site_url(), '', $year_archive );
$this->purge_dir( $year_archive_path );
}
/**
* Checks if a post is public.
*
* @param int $post_id The post ID.
*
* @return boolean
*/
public function is_public_post( $post_id ) {
$public = false;
if ( false === wp_is_post_autosave( $post_id ) ) {
$post_type = get_post_type( $post_id );
if ( $post_type ) {
$post_type_object = get_post_type_object( $post_type );
if ( $post_type_object && isset( $post_type_object->public ) ) {
$public = $post_type_object->public;
}
}
}
return $public;
}
/**
* Checks if a taxonomy is public.
*
* @param string $taxonomy Taxonomy name.
*
* @return boolean
*/
public function is_public_taxonomy( $taxonomy ) {
$public = false;
$taxonomy_object = get_taxonomy( $taxonomy );
if ( $taxonomy_object && isset( $taxonomy_object->public ) ) {
$public = $taxonomy_object->public;
}
return $public;
}
/**
* Purge taxonomy term URL when a term is updated.
*
* @param int $term_id Term ID
*/
public function edit_terms( $term_id ) {
$url = get_term_link( $term_id );
if ( ! is_wp_error( $url ) ) {
$this->purge_single( $url );
}
}
/**
* Write page content to cache.
*
* @param string $page Page content to be cached.
*
* @return string
*/
public function write( $page ) {
$base = wp_parse_url( trailingslashit( get_option( 'home' ) ), PHP_URL_PATH );
if ( ! empty( $page ) ) {
$path = WP_CONTENT_DIR . '/endurance-page-cache' . str_replace( get_option( 'home' ), '', esc_url( $_SERVER['REQUEST_URI'] ) );
$path = str_replace( '/endurance-page-cache' . $base, '/endurance-page-cache/', $path );
$path = str_replace( '//', '/', $path );
if ( file_exists( $path . '_index.html' ) && filemtime( $path . '_index.html' ) > time() - HOUR_IN_SECONDS ) {
return $page;
}
if ( false !== strpos( $page, '