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( '%s', 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( '%2$s', $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('&', '&', $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 update the drop-in.', '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 enable the drop-in.', 'redis-cache' ), esc_url( network_admin_url( $this->page ) ) ); } if ( isset( $message ) ) { printf( '
%s
', 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%%  %s/%s  %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 HTML; } /** * Returns the admin-bar 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( '

%s

', 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 uninstall Redis Object Cache.', '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( '

%s %s

', 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 business class object cache backend. Truly reliable, highly-optimized and fully customizable, with a dedicated engineer when you most need it. Learn more »', '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( '

%s

%s

', 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 business class object cache that’s highly-optimized for WooCommerce to provide true reliability, peace of mind and faster load times for your store. Learn more »', '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\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", $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 doesn’t exist.', 'redis-cache' ) ); } if ( $wp_filesystem->exists( $testfile ) ) { if ( ! $wp_filesystem->delete( $testfile ) ) { return new WP_Error( 'delete', __( 'Test file exists, but couldn’t 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 doesn’t exist.', 'redis-cache' ) ); } $meta = get_file_data( $testfile, [ 'Version' => 'Version' ] ); if ( $meta['Version'] !== WP_REDIS_VERSION ) { return new WP_Error( 'version', __( 'Couldn’t verify test file contents.', 'redis-cache' ) ); } if ( ! $wp_filesystem->delete( $testfile ) ) { return new WP_Error( 'delete', __( 'Copied test file couldn’t 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; } }