inception/redis-cache/includes/class-plugin.php
Danny Houtworm 3760074926 bla
2023-08-13 21:40:17 +00:00

1600 lines
48 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Main plugin class
*
* @package Rhubarb\RedisCache
*/
namespace Rhubarb\RedisCache;
use WP_Error;
use Exception;
defined( '\\ABSPATH' ) || exit;
/**
* Main plugin class definition
*/
class Plugin {
/**
* Settings page uri
*
* @var string $page
*/
private $page = '';
/**
* Settings page slug
*
* @var string $screen
*/
private $screen = '';
/**
* Allowed setting page actions
*
* @var string[] $actions
*/
private $actions = [
'enable-cache',
'disable-cache',
'flush-cache',
'update-dropin',
];
/**
* Plugin instance property
*
* @var Plugin|null
*/
private static $instance;
/**
* Plugin instantiation method
*
* @return Plugin
*/
public static function instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
if ( is_multisite() ) {
$this->page = 'settings.php?page=redis-cache';
$this->screen = 'settings_page_redis-cache-network';
} else {
$this->page = 'options-general.php?page=redis-cache';
$this->screen = 'settings_page_redis-cache';
}
Metrics::init();
$this->add_actions_and_filters();
}
/**
* Adds all necessary hooks
*
* @return void
*/
public function add_actions_and_filters() {
add_action( 'deactivate_plugin', [ $this, 'on_deactivation' ] );
add_action( 'admin_init', [ $this, 'maybe_update_dropin' ] );
add_action( 'admin_init', [ $this, 'maybe_redirect' ] );
add_action( 'init', [ $this, 'init' ] );
add_action( is_multisite() ? 'network_admin_menu' : 'admin_menu', [ $this, 'add_admin_menu_page' ] );
add_action( 'admin_notices', [ $this, 'show_admin_notices' ] );
add_action( 'network_admin_notices', [ $this, 'show_admin_notices' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_styles' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_redis_metrics' ] );
add_action( 'admin_bar_menu', [ $this, 'render_admin_bar' ], 998 );
add_action( 'load-settings_page_redis-cache', [ $this, 'do_admin_actions' ] );
add_action( 'wp_dashboard_setup', [ $this, 'setup_dashboard_widget' ] );
add_action( 'wp_network_dashboard_setup', [ $this, 'setup_dashboard_widget' ] );
add_action( 'wp_ajax_roc_dismiss_notice', [ $this, 'ajax_dismiss_notice' ] );
add_action( 'wp_ajax_roc_flush_cache', [ $this, 'ajax_flush_cache' ] );
add_filter( 'gettext_redis-cache', [ $this, 'get_text' ], 10, 2 );
add_filter( 'plugin_row_meta', [ $this, 'add_plugin_row_meta' ], 10, 2 );
add_filter( sprintf( '%splugin_action_links_%s', is_multisite() ? 'network_admin_' : '', WP_REDIS_BASENAME ), [ $this, 'add_plugin_actions_links' ] );
add_action( 'wp_head', [ $this, 'register_shutdown_hooks' ] );
add_filter( 'qm/collectors', [ $this, 'register_qm_collector' ], 25 );
add_filter( 'qm/outputter/html', [ $this, 'register_qm_output' ] );
add_filter( 'perflab_disable_object_cache_dropin', '__return_true' );
add_filter( 'w3tc_config_item_objectcache.enabled', '__return_false' );
add_action( 'litespeed_init', [ $this, 'litespeed_disable_objectcache' ] );
}
/**
* Callback of the `init` hook.
*
* @return void
*/
public function init() {
load_plugin_textdomain( 'redis-cache', false, 'redis-cache/languages' );
if ( is_admin() && ! wp_next_scheduled( 'rediscache_discard_metrics' ) ) {
wp_schedule_event( time(), 'hourly', 'rediscache_discard_metrics' );
}
}
/**
* Adds a submenu page to "Settings"
*
* @return void
*/
public function add_admin_menu_page() {
add_submenu_page(
is_multisite() ? 'settings.php' : 'options-general.php',
__( 'Redis Object Cache', 'redis-cache' ),
__( 'Redis', 'redis-cache' ),
$this->manage_redis_capability(),
'redis-cache',
[ $this, 'show_admin_page' ]
);
}
/**
* Displays the settings page
*
* @return void
*/
public function show_admin_page() {
// Request filesystem credentials?
if ( isset( $_GET['_wpnonce'], $_GET['action'] ) ) {
$action = sanitize_key( $_GET['action'] );
$nonce = sanitize_key( $_GET['_wpnonce'] );
foreach ( $this->actions as $name ) {
// Nonce verification.
if ( $action === $name && wp_verify_nonce( $nonce, $action ) ) {
$url = $this->action_link( $action );
if ( $this->initialize_filesystem( $url ) === false ) {
return; // Request filesystem credentials.
}
}
}
}
if ( wp_next_scheduled( 'redis_gather_metrics' ) ) {
wp_clear_scheduled_hook( 'redis_gather_metrics' );
}
UI::register_tab(
'overview',
__( 'Overview', 'redis-cache' ),
[ 'default' => true ]
);
UI::register_tab(
'metrics',
__( 'Metrics', 'redis-cache' ),
[ 'disabled' => ! Metrics::is_enabled() ]
);
UI::register_tab(
'diagnostics',
__( 'Diagnostics', 'redis-cache' )
);
// Show the admin page.
require_once WP_REDIS_PLUGIN_PATH . '/includes/ui/settings.php';
}
/**
* Adds the dashboard metrics widget
*
* @return void
*/
public function setup_dashboard_widget() {
if ( ! $this->current_user_can_manage_redis() ) {
return;
}
if ( ! Metrics::is_enabled() ) {
return;
}
wp_add_dashboard_widget(
'dashboard_rediscache',
__( 'Redis Object Cache', 'redis-cache' ),
[ $this, 'show_dashboard_widget' ],
null,
null,
'normal',
'high'
);
}
/**
* Displays the dashboard widget
*
* @return void
*/
public function show_dashboard_widget() {
require_once WP_REDIS_PLUGIN_PATH . '/includes/ui/widget.php';
}
/**
* Adds the settings page to the plugin action links on the plugin page
*
* @param string[] $links The current plugin action links.
* @return string[]
*/
public function add_plugin_actions_links( $links ) {
$settings = sprintf(
'<a href="%s">%s</a>',
network_admin_url( $this->page ),
esc_html__( 'Settings', 'redis-cache' )
);
return array_merge( [ $settings ], $links );
}
/**
* Ensure Author URI has UTM parameters.
*
* @param string $translation Translated text.
* @param string $text Text to translate.
* @return string
*/
public function get_text( $translation, $text ) {
if ( $text === 'https://objectcache.pro' ) {
return $this->link_to_ocp( 'author', false );
}
return $translation;
}
/**
* Adds plugin meta links on the plugin page
*
* @param string[] $plugin_meta An array of the plugin's metadata.
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
* @return string[] An array of the plugin's metadata.
*/
public function add_plugin_row_meta( array $plugin_meta, $plugin_file ) {
if ( strpos( $plugin_file, 'redis-cache.php' ) === false ) {
return $plugin_meta;
}
if ( self::acceleratewp_install() ) {
return $plugin_meta;
}
$plugin_meta[] = sprintf(
'<a href="%1$s"><span class="dashicons dashicons-star-filled" aria-hidden="true" style="font-size: 14px; line-height: 1.3"></span>%2$s</a>',
$this->link_to_ocp( 'meta-row' ),
esc_html_x( 'Upgrade to Pro', 'verb', 'redis-cache' )
);
return $plugin_meta;
}
/**
* Returns the link to Object Cache Pro.
*
* @param string $medium
* @param bool $as_html
* @return string
*/
public function link_to_ocp($medium, $as_html = true)
{
$ref = 'oss';
if ( self::acceleratewp_install( true ) ) {
$ref = 'oss-cl';
}
$url = "https://objectcache.pro/?ref={$ref}&utm_source=wp-plugin&utm_medium={$medium}";
return $as_html ? str_replace('&', '&amp;', $url) : $url;
}
/**
* Enqueues admin style resources
*
* @return void
*/
public function enqueue_admin_styles() {
$screen = get_current_screen();
if ( ! isset( $screen->id ) ) {
return;
}
$screens = [
$this->screen,
'dashboard',
'dashboard-network',
];
if ( ! in_array( $screen->id, $screens, true ) ) {
return;
}
wp_enqueue_style( 'redis-cache', WP_REDIS_PLUGIN_DIR . '/assets/css/admin.css', [], WP_REDIS_VERSION );
}
/**
* Enqueues admin script resources
*
* @return void
*/
public function enqueue_admin_scripts() {
$screen = get_current_screen();
if ( ! isset( $screen->id ) ) {
return;
}
$screens = [
$this->screen,
'dashboard',
'dashboard-network',
'edit-shop_order',
'edit-product',
'woocommerce_page_wc-admin',
];
if ( ! in_array( $screen->id, $screens, true ) ) {
return;
}
$clipboard = file_exists( ABSPATH . WPINC . '/js/clipboard.min.js' );
wp_enqueue_script(
'redis-cache',
plugins_url( 'assets/js/admin.js', WP_REDIS_FILE ),
array_merge( [ 'jquery', 'underscore' ], $clipboard ? [ 'clipboard' ] : [] ),
WP_REDIS_VERSION,
true
);
wp_localize_script(
'redis-cache',
'rediscache',
[
'jQuery' => 'jQuery',
'disable_pro' => $screen->id !== $this->screen
|| ( defined( 'WP_REDIS_DISABLE_BANNERS' ) && WP_REDIS_DISABLE_BANNERS )
|| self::acceleratewp_install(),
'l10n' => [
'time' => __( 'Time', 'redis-cache' ),
'bytes' => __( 'Bytes', 'redis-cache' ),
'ratio' => __( 'Ratio', 'redis-cache' ),
'calls' => __( 'Calls', 'redis-cache' ),
'no_data' => __( 'Not enough data collected, yet.', 'redis-cache' ),
'no_cache' => __( 'Enable object cache to collect data.', 'redis-cache' ),
'pro' => 'Object Cache Pro',
],
]
);
}
/**
* Enqueues scripts to display recorded metrics
*
* @return void
*/
public function enqueue_redis_metrics() {
if ( ! Metrics::is_enabled() ) {
return;
}
$screen = get_current_screen();
if ( ! isset( $screen->id ) ) {
return;
}
if ( ! in_array( $screen->id, [ $this->screen, 'dashboard', 'dashboard-network' ], true ) ) {
return;
}
wp_enqueue_script(
'redis-cache-charts',
plugins_url( 'assets/js/apexcharts.min.js', WP_REDIS_FILE ),
[],
WP_REDIS_VERSION,
true
);
if ( ! $this->get_redis_status() ) {
return;
}
$min_time = $screen->id === $this->screen
? Metrics::max_time()
: MINUTE_IN_SECONDS * 30;
$metrics = Metrics::get( $min_time );
wp_localize_script( 'redis-cache', 'rediscache_metrics', $metrics );
}
/**
* Registers a new cache collector for the Query Monitor plugin
*
* @param array $collectors Array of currently registered collectors.
* @return array
*/
public function register_qm_collector( array $collectors ) {
$collectors['cache'] = new QM_Collector();
return $collectors;
}
/**
* Registers a new cache output using our collector for the Query Monitor plugin
*
* @param array $output Array of current QM_Output handlers.
* @return array
*/
public function register_qm_output( $output ) {
$collector = \QM_Collectors::get( 'cache' );
if (
$collector instanceof QM_Collector &&
method_exists( 'QM_Output_Html', 'before_non_tabular_output' )
) {
$output['cache'] = new QM_Output( $collector );
}
return $output;
}
/**
* Checks if the `object-cache.php` drop-in exists
*
* @return bool
*/
public function object_cache_dropin_exists() {
return file_exists( WP_CONTENT_DIR . '/object-cache.php' );
}
/**
* Validates the `object-cache.php` drop-in
*
* @return bool
*/
public function validate_object_cache_dropin() {
if ( ! $this->object_cache_dropin_exists() ) {
return false;
}
$dropin = get_plugin_data( WP_CONTENT_DIR . '/object-cache.php' );
$plugin = get_plugin_data( WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php' );
/**
* Filters the drop-in validation state
*
* @since 2.0.16
* @param bool $state The validation state of the drop-in.
* @param string $dropin The `PluginURI` of the drop-in.
* @param string $plugin The `PluginURI` of the plugin.
*/
return apply_filters(
'redis_cache_validate_dropin',
$dropin['PluginURI'] === $plugin['PluginURI'],
$dropin['PluginURI'],
$plugin['PluginURI']
);
}
/**
* Checks if the `object-cache.php` drop-in is outdated
*
* @return bool
*/
public function object_cache_dropin_outdated() {
if ( ! $this->object_cache_dropin_exists() ) {
return false;
}
$dropin = get_plugin_data( WP_CONTENT_DIR . '/object-cache.php' );
$plugin = get_plugin_data( WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php' );
if ( $dropin['PluginURI'] === $plugin['PluginURI'] ) {
return version_compare( $dropin['Version'], $plugin['Version'], '<' );
}
return false;
}
/**
* Retrieves the current human-readable status
*
* @return string
*/
public function get_status() {
global $wp_object_cache;
if ( defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED ) {
return __( 'Disabled', 'redis-cache' );
}
if ( ! $this->object_cache_dropin_exists() ) {
return __( 'Not enabled', 'redis-cache' );
}
if ( $this->object_cache_dropin_outdated() ) {
return __( 'Drop-in is outdated', 'redis-cache' );
}
if ( ! $this->validate_object_cache_dropin() ) {
return __( 'Drop-in is invalid', 'redis-cache' );
}
if ( method_exists( $wp_object_cache, 'redis_status' ) ) {
return $wp_object_cache->redis_status()
? __( 'Connected', 'redis-cache' )
: __( 'Not connected', 'redis-cache' );
}
return __( 'Unknown', 'redis-cache' );
}
/**
* Retrieves the Redis connection status
*
* @return bool|null Boolean Redis connection status if available, null otherwise.
*/
public function get_redis_status() {
global $wp_object_cache;
if ( defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED ) {
return null;
}
if ( $this->object_cache_dropin_outdated() ) {
return null;
}
if ( ! $this->validate_object_cache_dropin() ) {
return null;
}
if ( ! method_exists( $wp_object_cache, 'redis_status' ) ) {
return null;
}
return $wp_object_cache->redis_status();
}
/**
* Check whether we can connect to Redis via Predis.
*
* @return bool|string
*/
public function check_redis_connection() {
try {
$predis = new Predis();
$predis->connect();
} catch ( Exception $error ) {
return $error->getMessage();
}
return true;
}
/**
* Returns the redis version if possible
*
* @see WP_Object_Cache::redis_version()
* @return null|string
*/
public function get_redis_version() {
global $wp_object_cache;
if ( defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED ) {
return null;
}
if ( ! $this->validate_object_cache_dropin() || ! method_exists( $wp_object_cache, 'redis_version' ) ) {
return null;
}
return $wp_object_cache->redis_version();
}
/**
* Returns the currently used redis client (if any)
*
* @return null|string
*/
public function get_redis_client_name() {
global $wp_object_cache;
if ( isset( $wp_object_cache->diagnostics[ 'client' ] ) ) {
return $wp_object_cache->diagnostics[ 'client' ];
}
if ( ! defined( 'WP_REDIS_CLIENT' ) ) {
return null;
}
return WP_REDIS_CLIENT;
}
/**
* Fetches the redis diagnostics data
*
* @return null|array
*/
public function get_diagnostics() {
global $wp_object_cache;
if ( ! $this->validate_object_cache_dropin() || ! property_exists( $wp_object_cache, 'diagnostics' ) ) {
return null;
}
return $wp_object_cache->diagnostics;
}
/**
* Retrieves the redis prefix
*
* @return null|mixed
*/
public function get_redis_prefix() {
return defined( 'WP_REDIS_PREFIX' ) ? WP_REDIS_PREFIX : null;
}
/**
* Retrieves the redis maximum time to live
*
* @return null|mixed
*/
public function get_redis_maxttl() {
return defined( 'WP_REDIS_MAXTTL' ) ? WP_REDIS_MAXTTL : null;
}
/**
* Displays admin notices
*
* @return void
*/
public function show_admin_notices() {
$this->pro_notice();
$this->wc_pro_notice();
// Only show admin notices to users with the right capability.
if ( ! $this->current_user_can_manage_redis() ) {
return;
}
// Do not display the dropin message if you want
if ( defined( 'WP_REDIS_DISABLE_DROPIN_BANNERS' ) && WP_REDIS_DISABLE_DROPIN_BANNERS ) {
return;
}
if ( defined( 'RedisCachePro\Version' ) || defined( 'ObjectCachePro\Version' ) ) {
return;
}
if ( $this->object_cache_dropin_exists() ) {
if ( $this->validate_object_cache_dropin() ) {
if ( $this->object_cache_dropin_outdated() ) {
$message = sprintf(
// translators: %s = Action link to update the drop-in.
__( 'The Redis object cache drop-in is outdated. Please <a href="%s">update the drop-in</a>.', 'redis-cache' ),
$this->action_link( 'update-dropin' )
);
}
} else {
$message = sprintf(
// translators: %s = Link to settings page.
__( 'A foreign object cache drop-in was found. To use Redis for object caching, please <a href="%s">enable the drop-in</a>.', 'redis-cache' ),
esc_url( network_admin_url( $this->page ) )
);
}
if ( isset( $message ) ) {
printf( '<div class="update-nag notice notice-warning inline">%s</div>', wp_kses_post( $message ) );
}
}
}
/**
* Display the admin bar menu item.
*
* @param \WP_Admin_Bar $wp_admin_bar
*
* @return void
*/
public function render_admin_bar( $wp_admin_bar ) {
if ( defined( 'WP_REDIS_DISABLE_ADMINBAR' ) && WP_REDIS_DISABLE_ADMINBAR ) {
return;
}
if ( ! $this->current_user_can_manage_redis() ) {
return;
}
$nodeTitle = __( 'Object Cache', 'redis-cache' );
$style = preg_replace( '/\s+/', ' ', $this->admin_bar_style() );
$script = preg_replace( '/\s+/', ' ', $this->admin_bar_script() );
$html = "\n{$style}\n{$script}\n";
$redis_status = $this->get_redis_status();
$wp_admin_bar->add_node([
'id' => 'redis-cache',
'title' => $nodeTitle,
'meta' => [
'html' => $html,
'class' => $redis_status === false ? 'redis-cache-error' : '',
],
]);
if ( $redis_status ) {
$wp_admin_bar->add_node([
'parent' => 'redis-cache',
'id' => 'redis-cache-flush',
'title' => __( 'Flush Cache', 'redis-cache' ),
'href' => $this->action_link( 'flush-cache' ),
]);
}
$wp_admin_bar->add_node([
'parent' => 'redis-cache',
'id' => 'redis-cache-metrics',
'title' => __( 'Settings', 'redis-cache' ),
'href' => network_admin_url( $this->page ),
]);
$wp_admin_bar->add_group(
[
'id' => 'redis-cache-info',
'parent' => 'redis-cache',
'meta' => [
'class' => 'ab-sub-secondary',
],
]
);
$value = $this->get_status();
// translators: %s = The status of the Redis connection.
$title = sprintf( __( 'Status: %s', 'redis-cache' ), $value );
if ( $redis_status ) {
global $wp_object_cache;
$info = $wp_object_cache->info();
$hits = number_format_i18n( $info->hits );
$misses = number_format_i18n( $info->misses );
$size = size_format( $info->bytes );
$value = sprintf(
'%s%%&nbsp;&nbsp;%s/%s&nbsp;&nbsp;%s',
$info->ratio,
$hits,
$misses,
$size
);
$title = sprintf(
// translators: 1: Hit ratio, 2: Hits, 3: Misses. 4: Human-readable size of cache.
__( '(Current page) Hit Ratio: %1$s%%, Hits %2$s, Misses: %3$s, Size: %4$s', 'redis-cache' ),
$info->ratio,
$hits,
$misses,
$size
);
}
$wp_admin_bar->add_node([
'parent' => 'redis-cache-info',
'id' => 'redis-cache-info-details',
'title' => $value,
'href' => $redis_status && Metrics::is_enabled() ? network_admin_url( $this->page . '#metrics' ) : '',
'meta' => [
'title' => $title,
],
]);
}
/**
* Returns the admin-bar <style> tag.
*
* @return string
*/
protected function admin_bar_style()
{
return <<<HTML
<style>
#wpadminbar ul li.redis-cache-error {
background: #b30000;
}
#wpadminbar:not(.mobile) .ab-top-menu > li.redis-cache-error:hover > .ab-item {
background: #b30000;
color: #fff;
}
</style>
HTML;
}
/**
* Returns the admin-bar <script> tag.
*
* @return string
*/
protected function admin_bar_script()
{
$nonce = wp_create_nonce();
$ajaxurl = esc_url( admin_url( 'admin-ajax.php' ) );
$flushMessage = __( 'Flushing cache...', 'redis-cache' );
return <<<HTML
<script>
(function (element) {
if (! element) {
return;
}
element.addEventListener('click', async function (event) {
event.preventDefault();
var node = document.querySelector('#wp-admin-bar-redis-cache');
var textNode = node.querySelector('.ab-item:first-child');
if (! textNode.dataset.text) {
textNode.dataset.text = textNode.innerText;
}
node.classList.remove('hover');
textNode.innerText = '{$flushMessage}';
try {
var data = new FormData();
data.append('action', 'roc_flush_cache');
data.append('nonce', '{$nonce}');
var response = await fetch('{$ajaxurl}', {
method: 'POST',
body: data,
});
textNode.innerText = await response.text();
setTimeout(function () {
textNode.innerText = textNode.dataset.text;
}, 3000);
} catch (error) {
textNode.innerText = textNode.dataset.text;
alert('Object cache could not be flushed: ' + error);
}
});
})(
document.querySelector('#wp-admin-bar-redis-cache-flush > a')
);
</script>
HTML;
}
/**
* Executes admin actions
*
* @return void
*/
public function do_admin_actions() {
global $wp_filesystem;
if ( isset( $_GET['_wpnonce'], $_GET['action'] ) ) {
$action = sanitize_key( $_GET['action'] );
$nonce = sanitize_key( $_GET['_wpnonce'] );
// Nonce verification.
foreach ( $this->actions as $name ) {
if ( $action === $name && ! wp_verify_nonce( $nonce, $action ) ) {
return;
}
}
if ( in_array( $action, $this->actions, true ) ) {
$url = $this->action_link( $action );
if ( $action === 'flush-cache' ) {
wp_cache_flush()
? add_settings_error(
'redis-cache',
'flush',
__( 'Object cache flushed.', 'redis-cache' ),
'updated'
)
: add_settings_error(
'redis-cache',
'flush',
__( 'Object cache could not be flushed.', 'redis-cache' ),
'error'
);
}
// do we have filesystem credentials?
if ( $this->initialize_filesystem( $url, true ) ) {
if ( $action === 'enable-cache' ) {
$result = $wp_filesystem->copy(
WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php',
WP_CONTENT_DIR . '/object-cache.php',
true,
FS_CHMOD_FILE
);
if ( $result ) {
try {
$predis = new Predis();
$predis->flush();
} catch ( Exception $exception ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( $exception );
}
}
/**
* Fires on cache enable event
*
* @since 1.3.5
* @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful.
*/
do_action( 'redis_object_cache_enable', $result );
$result
? add_settings_error(
'redis-cache',
'dropin',
__( 'Object cache enabled.', 'redis-cache' ),
'updated'
)
: add_settings_error(
'redis-cache',
'dropin',
__( 'Object cache could not be enabled.', 'redis-cache' ),
'error'
);
}
if ( $action === 'disable-cache' ) {
$result = $wp_filesystem->delete( WP_CONTENT_DIR . '/object-cache.php' );
if ( $result ) {
try {
$predis = new Predis();
$predis->flush();
} catch ( Exception $exception ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( $exception );
}
}
/**
* Fires on cache enable event
*
* @since 1.3.5
* @param bool $result Whether the filesystem event (deletion of the `object-cache.php` file) was successful.
*/
do_action( 'redis_object_cache_disable', $result );
$result
? add_settings_error(
'redis-cache',
'dropin',
__( 'Object cache disabled.', 'redis-cache' ),
'updated'
)
: add_settings_error(
'redis-cache',
'dropin',
__( 'Object cache could not be disabled.', 'redis-cache' ),
'error'
);
}
if ( $action === 'update-dropin' ) {
$result = $wp_filesystem->copy(
WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php',
WP_CONTENT_DIR . '/object-cache.php',
true,
FS_CHMOD_FILE
);
/**
* Fires on cache enable event
*
* @since 1.3.5
* @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful.
*/
do_action( 'redis_object_cache_update_dropin', $result );
$result
? add_settings_error(
'redis-cache',
'dropin',
__( 'Updated object cache drop-in and enabled Redis object cache.', 'redis-cache' ),
'updated'
)
: add_settings_error(
'redis-cache',
'dropin',
__( 'Object cache drop-in could not be updated.', 'redis-cache' ),
'error'
);
}
}
$messages = get_settings_errors( 'redis-cache' );
if ( count( $messages ) !== 0 ) {
set_transient( 'settings_errors', $messages, 30 );
wp_safe_redirect(
network_admin_url( add_query_arg( 'settings-updated', 1, $this->page ) )
);
exit;
}
}
}
}
/**
* Dismisses the admin notice for the current user
*
* @return void
*/
public function ajax_dismiss_notice() {
if ( isset( $_POST['notice'] ) ) {
check_ajax_referer( 'roc_dismiss_notice' );
$notice = sprintf(
'roc_dismissed_%s',
sanitize_key( $_POST['notice'] )
);
update_user_meta( get_current_user_id(), $notice, '1' );
}
wp_die();
}
/**
* Flushes object cache
*
* @return void
*/
public function ajax_flush_cache() {
if ( ! wp_verify_nonce( $_POST['nonce'] ) ) {
$message = 'Invalid Nonce.';
} else if ( wp_cache_flush() ) {
$message = 'Object cache flushed.';
} else {
$message = 'Object cache could not be flushed.';
}
wp_die( __( $message , 'redis-cache' ) );
}
/**
* Displays a redis cache pro admin notice
*
* @return void
*/
public function pro_notice() {
$screen = get_current_screen();
if ( ! isset( $screen->id ) ) {
return;
}
if ( $screen->id === $this->screen && ( defined( 'RedisCachePro\Version' ) || defined( 'ObjectCachePro\Version' ) ) ) {
printf(
'<div class="notice notice-warning"><p>%s</p></div>',
wp_kses_post(
sprintf(
// translators: %s = Action link to update the drop-in.
__( 'The Object Cache Pro plugin appears to be installed and should be used. You can safely <a href="%s">uninstall Redis Object Cache</a>.', 'redis-cache' ),
esc_url( network_admin_url( 'plugins.php?plugin_status=all&s=redis%20object%20cache' ) )
)
)
);
return;
}
if ( defined( 'RedisCachePro\Version' ) || defined( 'ObjectCachePro\Version' ) ) {
return;
}
if ( defined( 'WP_REDIS_DISABLE_BANNERS' ) && WP_REDIS_DISABLE_BANNERS ) {
return;
}
if ( ! in_array( $screen->id, [ 'dashboard', 'dashboard-network' ], true ) ) {
return;
}
if ( ! $this->current_user_can_manage_redis() ) {
return;
}
if ( intval( get_user_meta( get_current_user_id(), 'roc_dismissed_pro_release_notice', true ) ) === 1 ) {
return;
}
printf(
'<div class="notice notice-info is-dismissible" data-dismissible="pro_release_notice" data-nonce="%s"><p><strong>%s</strong> %s</p></div>',
esc_attr( wp_create_nonce( 'roc_dismiss_notice' ) ),
esc_html__( 'Object Cache Pro!', 'redis-cache' ),
sprintf(
// translators: %s = Link to the plugin setting screen.
wp_kses_post( __( 'A <u>business class</u> object cache backend. Truly reliable, highly-optimized and fully customizable, with a <u>dedicated engineer</u> when you most need it. <a href="%s">Learn more »</a>', 'redis-cache' ) ),
esc_url( network_admin_url( $this->page ) )
)
);
}
/**
* Displays a redis cache pro admin notice specifically for WooCommerce
*
* @return void
*/
public function wc_pro_notice() {
if ( defined( 'RedisCachePro\Version' ) || defined( 'ObjectCachePro\Version' ) ) {
return;
}
if ( defined( 'WP_REDIS_DISABLE_BANNERS' ) && WP_REDIS_DISABLE_BANNERS ) {
return;
}
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
$screen = get_current_screen();
if ( ! isset( $screen->id ) ) {
return;
}
if ( ! in_array( $screen->id, [ 'edit-shop_order', 'edit-product', 'woocommerce_page_wc-admin' ], true ) ) {
return;
}
if ( ! $this->current_user_can_manage_redis() ) {
return;
}
if ( intval( get_user_meta( get_current_user_id(), 'roc_dismissed_wc_pro_notice', true ) ) === 1 ) {
return;
}
printf(
'<div class="notice woocommerce-message woocommerce-admin-promo-messages is-dismissible" data-dismissible="wc_pro_notice" data-nonce="%s"><p><strong>%s</strong></p><p>%s</p></div>',
esc_attr( wp_create_nonce( 'roc_dismiss_notice' ) ),
esc_html__( 'Object Cache Pro + WooCommerce = ❤️', 'redis-cache' ),
sprintf(
// translators: %s = Link to the plugin's settings screen.
wp_kses_post( __( 'Object Cache Pro is a <u>business class</u> object cache thats highly-optimized for WooCommerce to provide true reliability, peace of mind and faster load times for your store. <a style="color: #bb77ae;" href="%s">Learn more »</a>', 'redis-cache' ) ),
esc_url( network_admin_url( $this->page ) )
)
);
}
/**
* Registers all hooks associated with the shutdown hook
*
* @return void
*/
public function register_shutdown_hooks() {
if ( ! defined( 'WP_REDIS_DISABLE_COMMENT' ) || ! WP_REDIS_DISABLE_COMMENT ) {
add_action( 'shutdown', [ $this, 'maybe_print_comment' ], 0 );
}
}
/**
* Displays the redis cache html comment
*
* @return void
*/
public function maybe_print_comment() {
/** @var \WP_Object_Cache $wp_object_cache */
global $wp_object_cache;
if (
( defined( 'WP_CLI' ) && WP_CLI ) ||
( defined( 'DOING_CRON' ) && DOING_CRON ) ||
( defined( 'DOING_AJAX' ) && DOING_AJAX ) ||
( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
( defined( 'JSON_REQUEST' ) && JSON_REQUEST ) ||
( defined( 'IFRAME_REQUEST' ) && IFRAME_REQUEST ) ||
( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) ||
( defined( 'WC_API_REQUEST' ) && WC_API_REQUEST )
) {
return;
}
if ( function_exists( 'wp_is_json_request' ) && wp_is_json_request() ) {
return;
}
if (
! isset( $wp_object_cache->cache, $wp_object_cache->diagnostics ) ||
! is_array( $wp_object_cache->cache )
) {
return;
}
if ($this->incompatible_content_type()) {
return;
}
$message = sprintf(
'Performance optimized by Redis Object Cache. Learn more: %s',
'https://wprediscache.com'
);
if ( ! WP_DEBUG_DISPLAY ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
printf( "\n<!-- %s -->\n", $message );
return;
}
$bytes = strlen( serialize( $wp_object_cache->cache ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
$debug = sprintf(
// translators: %1$d = number of objects. %2$s = human-readable size of cache. %3$s = name of the used client.
__( 'Retrieved %1$d objects (%2$s) from Redis using %3$s.', 'redis-cache' ),
$wp_object_cache->cache_hits,
function_exists( 'size_format' ) ? size_format( $bytes ) : "{$bytes} bytes",
$wp_object_cache->diagnostics['client']
);
printf(
"<!--\n%s\n\n%s\n-->\n",
$message, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_html( $debug )
);
}
/**
* Whether incompatible content type headers were sent.
*
* @return bool
*/
protected function incompatible_content_type()
{
$jsonContentType = static function ($headers) {
foreach ($headers as $header => $value) {
if (stripos((string) $header, 'content-type') === false) {
continue;
}
if (stripos((string) $value, '/json') === false) {
continue;
}
return true;
}
return false;
};
if (function_exists('headers_list')) {
$headers = [];
foreach (headers_list() as $header) {
[$name, $value] = explode(':', $header);
$headers[$name] = $value;
}
if ($jsonContentType($headers)) {
return true;
}
}
if (function_exists('apache_response_headers')) {
if ($headers = apache_response_headers()) {
return $jsonContentType($headers);
}
}
return false;
}
/**
* Initializes the WP filesystem API to be ready for use
*
* @param string $url The URL to post the form to.
* @param bool $silent Whether to ask the user for credentials if necessary or not.
* @return bool
*/
public function initialize_filesystem( $url, $silent = false ) {
if ( $silent ) {
ob_start();
}
$credentials = request_filesystem_credentials( $url );
if ( false === $credentials ) {
if ( $silent ) {
ob_end_clean();
}
return false;
}
if ( ! WP_Filesystem( $credentials ) ) {
request_filesystem_credentials( $url );
if ( $silent ) {
ob_end_clean();
}
return false;
}
return true;
}
/**
* Test if we can write in the WP_CONTENT_DIR and modify the `object-cache.php` drop-in
*
* @return true|WP_Error
*/
public function test_filesystem_writing() {
/** @var \WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
if ( ! $this->initialize_filesystem( '', true ) ) {
return new WP_Error( 'fs', __( 'Could not initialize filesystem.', 'redis-cache' ) );
}
$cachefile = WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php';
$testfile = WP_CONTENT_DIR . '/.redis-write-test.tmp';
if ( ! $wp_filesystem->exists( $cachefile ) ) {
return new WP_Error( 'exists', __( 'Object cache file doesnt exist.', 'redis-cache' ) );
}
if ( $wp_filesystem->exists( $testfile ) ) {
if ( ! $wp_filesystem->delete( $testfile ) ) {
return new WP_Error( 'delete', __( 'Test file exists, but couldnt be deleted.', 'redis-cache' ) );
}
}
if ( ! $wp_filesystem->is_writable( WP_CONTENT_DIR ) ) {
return new WP_Error( 'copy', __( 'Content directory is not writable.', 'redis-cache' ) );
}
if ( ! $wp_filesystem->copy( $cachefile, $testfile, true, FS_CHMOD_FILE ) ) {
return new WP_Error( 'copy', __( 'Failed to copy test file.', 'redis-cache' ) );
}
if ( ! $wp_filesystem->exists( $testfile ) ) {
return new WP_Error( 'exists', __( 'Copied test file doesnt exist.', 'redis-cache' ) );
}
$meta = get_file_data( $testfile, [ 'Version' => 'Version' ] );
if ( $meta['Version'] !== WP_REDIS_VERSION ) {
return new WP_Error( 'version', __( 'Couldnt verify test file contents.', 'redis-cache' ) );
}
if ( ! $wp_filesystem->delete( $testfile ) ) {
return new WP_Error( 'delete', __( 'Copied test file couldnt be deleted.', 'redis-cache' ) );
}
return true;
}
/**
* Calls the drop-in update method if necessary
*
* @return void
*/
public function maybe_update_dropin() {
if ( defined( 'WP_REDIS_DISABLE_DROPIN_AUTOUPDATE' ) && WP_REDIS_DISABLE_DROPIN_AUTOUPDATE ) {
return;
}
if ( $this->object_cache_dropin_outdated() ) {
add_action( 'shutdown', [ $this, 'update_dropin' ] );
}
}
/**
* Redirects to the plugin settings if the plugin was just activated
*
* @return void
*/
public function maybe_redirect() {
if ( ! get_transient( '_rediscache_activation_redirect' ) ) {
return;
}
if ( ! $this->current_user_can_manage_redis() ) {
return;
}
delete_transient( '_rediscache_activation_redirect' );
wp_safe_redirect( network_admin_url( $this->page ) );
}
/**
* Updates the `object-cache.php` drop-in
*
* @return void
*/
public function update_dropin() {
global $wp_filesystem;
if ( ! $this->validate_object_cache_dropin() ) {
return;
}
if ( $this->initialize_filesystem( '', true ) ) {
$result = $wp_filesystem->copy(
WP_REDIS_PLUGIN_PATH . '/includes/object-cache.php',
WP_CONTENT_DIR . '/object-cache.php',
true,
FS_CHMOD_FILE
);
/**
* Fires on cache enable event
*
* @since 1.3.5
* @param bool $result Whether the filesystem event (copy of the `object-cache.php` file) was successful.
*/
do_action( 'redis_object_cache_update_dropin', $result );
}
}
/**
* Plugin activation hook
*
* @return void
*/
public static function on_activation() {
set_transient( '_rediscache_activation_redirect', 1, 30 );
}
/**
* Plugin deactivation hook
*
* @param string $plugin Plugin basename.
* @return void
*/
public function on_deactivation( $plugin ) {
global $wp_filesystem;
ob_start();
if ( $plugin === WP_REDIS_BASENAME ) {
$timestamp = wp_next_scheduled( 'rediscache_discard_metrics' );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, 'rediscache_discard_metrics' );
}
wp_cache_flush();
if ( $this->validate_object_cache_dropin() && $this->initialize_filesystem( '', true ) ) {
$wp_filesystem->delete( WP_CONTENT_DIR . '/object-cache.php' );
}
}
ob_end_clean();
}
/**
* Helper method to retrieve a nonced plugin action link
*
* @param string $action The action to be executed once the link is followed.
* @return string
*/
public function action_link( $action ) {
if ( ! in_array( $action, $this->actions, true ) ) {
return '';
}
return wp_nonce_url(
network_admin_url( add_query_arg( 'action', $action, $this->page ) ),
$action
);
}
/**
* Obscure `password` URL parameter.
*
* @param string $url
* @return string
*/
public function obscure_url_secrets( $url ) {
return preg_replace(
'/password=[^&]+/',
'password=*****',
(string) $url
);
}
/**
* The capability required to manage Redis.
*
* @return string
*/
public function manage_redis_capability() {
return is_multisite() ? 'manage_network_options' : 'manage_options';
}
/**
* Does the current user have the capability to manage Redis?
*
* @return bool
*/
public function current_user_can_manage_redis() {
return current_user_can( $this->manage_redis_capability() );
}
/**
* Prevent LiteSpeed Cache from overwriting the `object-cache.php` drop-in.
*
* @return void
*/
public function litespeed_disable_objectcache()
{
if ( isset( $_POST['LSCWP_CTRL'], $_POST['LSCWP_NONCE'], $_POST['object'] ) ) {
$_POST['object'] = '0';
}
}
/**
* Returns `true` if the plugin was installed by AccelerateWP from CloudLinux.
*
* @param bool $ignore_banner_constant
* @return bool
*/
public static function acceleratewp_install( $ignore_banner_constant = false ) {
$path = defined( 'WP_REDIS_PATH' ) ? WP_REDIS_PATH : null;
$scheme = defined( 'WP_REDIS_SCHEME' ) ? WP_REDIS_SCHEME : null;
if ( $scheme === 'unix' && strpos( (string) $path, '.clwpos/redis.sock' ) !== false ) {
if ( $ignore_banner_constant ) {
return true;
} else {
return defined( 'WP_REDIS_DISABLE_BANNERS' ) && WP_REDIS_DISABLE_BANNERS;
}
}
return false;
}
}