page = 'settings.php?page=harborcache'; $this->screen = 'settings_page_harborcache-network'; } else { $this->page = 'options-general.php?page=harborcache'; $this->screen = 'settings_page_harborcache'; } 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( '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_harborcache', [ $this, 'do_admin_actions' ] ); add_action( 'wp_dashboard_setup', [ $this, 'setup_dashboard_widget' ] ); add_action( 'wp_network_dashboard_setup', [ $this, 'setup_dashboard_widget' ] ); 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_' : '', HARBORCACHE_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() { if ( ! Metrics::is_enabled() ) { if ( wp_next_scheduled( 'harborcache_metrics_discard' ) ) { wp_clear_scheduled_hook( 'harborcache_metrics_discard' ); } } elseif ( is_admin() && ! wp_next_scheduled( 'harborcache_metrics_discard' ) ) { wp_schedule_event( time(), 'hourly', 'harborcache_metrics_discard' ); } } /** * Adds a submenu page to "Settings" * * @return void */ public function add_admin_menu_page() { add_submenu_page( is_multisite() ? 'settings.php' : 'options-general.php', __( 'HarborCache – Object Cache with Redis', 'harborcache-object-cache-with-redis' ), __( 'HarborCache', 'harborcache-object-cache-with-redis' ), $this->manage_redis_capability(), 'harborcache', [ $this, 'show_admin_page' ] ); } /** * Displays the settings page * * @return void */ public function show_admin_page() { \HarborCache\Console::render(); } /** * Adds the dashboard metrics widget * * @return void */ public function setup_dashboard_widget() { \HarborCache\Console::widget(); } /** * Displays the dashboard widget * * @return void */ public function show_dashboard_widget() { \HarborCache\Console::widget(); } /** * 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', 'harborcache-object-cache-with-redis' ) ); 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 ) { 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 ) { return $plugin_meta; } /** * Legacy integration hook retained as a no-op for existing callers. * * @param string $medium * @param bool $as_html * @return string */ public function link_to_ocp($medium, $as_html = true){ return ''; } /** * Enqueues admin style resources * * @return void */ public function enqueue_admin_styles() { return; } /** * Enqueues admin script resources * * @return void */ public function enqueue_admin_scripts() { return; } /** * Enqueues scripts to display recorded metrics * * @return void */ public function enqueue_redis_metrics() { return; } /** * 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( \HarborCache\Paths::content( '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( \HarborCache\Paths::content( 'object-cache.php' ) ); $plugin = get_plugin_data( HARBORCACHE_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( \HarborCache\Paths::content( 'object-cache.php' ) ); $plugin = get_plugin_data( HARBORCACHE_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 (via config constant)', 'harborcache-object-cache-with-redis' ); } if ( ! $this->object_cache_dropin_exists() ) { return __( 'Not enabled', 'harborcache-object-cache-with-redis' ); } if ( $this->object_cache_dropin_outdated() ) { return __( 'Drop-in is outdated', 'harborcache-object-cache-with-redis' ); } if ( ! $this->validate_object_cache_dropin() ) { return __( 'Drop-in is invalid', 'harborcache-object-cache-with-redis' ); } if ( method_exists( $wp_object_cache, 'redis_status' ) ) { return $wp_object_cache->redis_status() ? __( 'Connected', 'harborcache-object-cache-with-redis' ) : __( 'Not connected', 'harborcache-object-cache-with-redis' ); } return __( 'Unknown', 'harborcache-object-cache-with-redis' ); } /** * 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.', 'harborcache-object-cache-with-redis' ), $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.', 'harborcache-object-cache-with-redis' ), esc_url( network_admin_url( $this->page ) ) ); } if ( isset( $message ) ) { printf( '