diff --git a/assets/js/admin.js b/assets/js/admin.js index fc0f5b7f..bc1d9756 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -449,9 +449,12 @@ function ( event ) { event.preventDefault(); + var $parent = $( this ).parent(); + $.post( ajaxurl, { - notice: $( this ).parent().attr( 'data-dismissible' ), + notice: $parent.data( 'dismissible' ), action: 'roc_dismiss_notice', + _ajax_nonce: $parent.data( 'nonce' ), } ); } ); diff --git a/includes/class-plugin.php b/includes/class-plugin.php index 801503b0..c376d4a3 100644 --- a/includes/class-plugin.php +++ b/includes/class-plugin.php @@ -12,12 +12,30 @@ defined( '\\ABSPATH' ) || exit; +/** + * Main plugin class definition + */ class Plugin { - private $page; + /** + * 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 = array( 'enable-cache', 'disable-cache', @@ -26,14 +44,14 @@ class Plugin { ); /** - * Plugin instance property. + * Plugin instance property * * @var Plugin */ private static $instance; /** - * Plugin instanciation method. + * Plugin instanciation method * * @return Plugin */ @@ -45,6 +63,9 @@ public static function instance() { return self::$instance; } + /** + * Constructor + */ private function __construct() { require_once ABSPATH . 'wp-admin/includes/plugin.php'; @@ -66,6 +87,11 @@ private function __construct() { } } + /** + * Adds all necessary hooks + * + * @return void + */ public function add_actions_and_filters() { add_action( 'deactivate_plugin', array( $this, 'on_deactivation' ) ); add_action( 'admin_init', array( $this, 'maybe_update_dropin' ) ); @@ -97,8 +123,12 @@ public function add_actions_and_filters() { add_filter( 'qm/outputter/html', array( $this, 'register_qm_output' ) ); } + /** + * Adds a submenu page to "Settings" + * + * @return void + */ public function add_admin_menu_page() { - // add sub-page to "Settings" add_submenu_page( is_multisite() ? 'settings.php' : 'options-general.php', __( 'Redis Object Cache', 'redis-cache' ), @@ -109,18 +139,24 @@ public function add_admin_menu_page() { ); } + /** + * Displays the settings page + * + * @return void + */ public function show_admin_page() { - // request filesystem credentials? + // Request filesystem credentials? if ( isset( $_GET['_wpnonce'], $_GET['action'] ) ) { - $action = $_GET['action']; + $action = sanitize_key( $_GET['action'] ); + $nonce = sanitize_key( $_GET['_wpnonce'] ); foreach ( $this->actions as $name ) { - // verify nonce - if ( $action === $name && wp_verify_nonce( $_GET['_wpnonce'], $action ) ) { + // 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 + return; // Request filesystem credentials. } } } @@ -147,10 +183,15 @@ public function show_admin_page() { __( 'Diagnostics', 'redis-cache' ) ); - // show admin page + // 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 ( defined( 'WP_REDIS_DISABLE_METRICS' ) && WP_REDIS_DISABLE_METRICS ) { return; @@ -163,18 +204,33 @@ public function setup_dashboard_widget() { ); } + /** + * 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 ) { - // add settings link to plugin actions return array_merge( [ sprintf( '%s', network_admin_url( $this->page ), esc_html__( 'Settings', 'redis-cache' ) ) ], $links ); } + /** + * Enqueues admin style resources + * + * @return void + */ public function enqueue_admin_styles() { $screen = get_current_screen(); @@ -188,13 +244,18 @@ public function enqueue_admin_styles() { 'dashboard-network', ); - if ( ! in_array( $screen->id, $screens ) ) { + if ( ! in_array( $screen->id, $screens, true ) ) { return; } wp_enqueue_style( 'redis-cache', WP_REDIS_DIR . '/assets/css/admin.css', null, WP_REDIS_VERSION ); } + /** + * Enqueues admin script resources + * + * @return void + */ public function enqueue_admin_scripts() { $screen = get_current_screen(); @@ -211,7 +272,7 @@ public function enqueue_admin_scripts() { 'woocommerce_page_wc-admin', ); - if ( ! in_array( $screen->id, $screens ) ) { + if ( ! in_array( $screen->id, $screens, true ) ) { return; } @@ -219,7 +280,8 @@ public function enqueue_admin_scripts() { 'redis-cache', plugins_url( 'assets/js/admin.js', WP_REDIS_FILE ), array( 'jquery', 'underscore' ), - WP_REDIS_VERSION + WP_REDIS_VERSION, + true ); wp_localize_script( @@ -242,6 +304,11 @@ public function enqueue_admin_scripts() { ); } + /** + * Enqueues scripts to display recorded metrics + * + * @return void + */ public function enqueue_redis_metrics() { global $wp_object_cache; @@ -255,7 +322,7 @@ public function enqueue_redis_metrics() { return; } - if ( ! in_array( $screen->id, array( $this->screen, 'dashboard', 'dashboard-network' ) ) ) { + if ( ! in_array( $screen->id, array( $this->screen, 'dashboard', 'dashboard-network' ), true ) ) { return; } @@ -263,7 +330,8 @@ public function enqueue_redis_metrics() { 'redis-cache-charts', plugins_url( 'assets/js/apexcharts.min.js', WP_REDIS_FILE ), null, - WP_REDIS_VERSION + WP_REDIS_VERSION, + true ); if ( ! method_exists( $wp_object_cache, 'redis_instance' ) ) { @@ -279,17 +347,29 @@ public function enqueue_redis_metrics() { ); wp_localize_script( 'redis-cache', 'rediscache_metrics', $metrics ); - } catch (Exception $exception) { - error_log($exception); + } catch ( Exception $exception ) { + error_log( $exception ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } } + /** + * 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' ); @@ -303,10 +383,20 @@ public function register_qm_output( $output ) { 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; @@ -318,6 +408,11 @@ public function validate_object_cache_dropin() { return $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; @@ -333,6 +428,11 @@ public function object_cache_dropin_outdated() { return false; } + /** + * Retrieves the current human-readable status + * + * @return string + */ public function get_status() { global $wp_object_cache; @@ -380,6 +480,12 @@ public function get_redis_status() { return $wp_object_cache->redis_status(); } + /** + * Returns the redis version if possible + * + * @see WP_Object_Cache::redis_version() + * @return null|string + */ public function get_redis_version() { global $wp_object_cache; @@ -392,6 +498,11 @@ public function get_redis_version() { } } + /** + * Returns the currently used redis client (if any) + * + * @return null|string + */ public function get_redis_client_name() { global $wp_object_cache; @@ -404,6 +515,11 @@ public function get_redis_client_name() { } } + /** + * Fetches the redis diagnostics data + * + * @return null|array + */ public function get_diagnostics() { global $wp_object_cache; @@ -412,21 +528,36 @@ public function get_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() { if ( ! defined( 'WP_REDIS_DISABLE_BANNERS' ) || ! WP_REDIS_DISABLE_BANNERS ) { $this->pro_notice(); $this->wc_pro_notice(); } - // only show admin notices to users with the right capability + // Only show admin notices to users with the right capability. if ( ! current_user_can( is_multisite() ? 'manage_network_options' : 'manage_options' ) ) { return; } @@ -436,34 +567,40 @@ public function show_admin_notices() { if ( $this->validate_object_cache_dropin() ) { if ( $this->object_cache_dropin_outdated() ) { - // translators: %s = Action link to update the drop-in + // translators: %s = Action link to update the drop-in. $message = sprintf( __( 'The Redis object cache drop-in is outdated. Please update the drop-in.', 'redis-cache' ), $url ); } } else { - // translators: %s = Action link to update the drop-in + // translators: %s = Action link to update the drop-in. $message = sprintf( __( 'A foreign object cache drop-in was found. To use Redis for object caching, please enable the drop-in.', 'redis-cache' ), $url ); } if ( isset( $message ) ) { - printf( '
%s
', $message ); + printf( '
%s
', wp_kses_post( $message ) ); } } } + /** + * Executes admin actions + * + * @return void + */ public function do_admin_actions() { global $wp_filesystem; if ( isset( $_GET['_wpnonce'], $_GET['action'] ) ) { - $action = $_GET['action']; + $action = sanitize_key( $_GET['action'] ); + $nonce = sanitize_key( $_GET['_wpnonce'] ); - // verify nonce + // Nonce verification. foreach ( $this->actions as $name ) { - if ( $action === $name && ! wp_verify_nonce( $_GET['_wpnonce'], $action ) ) { + if ( $action === $name && ! wp_verify_nonce( $nonce, $action ) ) { return; } } - if ( in_array( $action, $this->actions ) ) { + if ( in_array( $action, $this->actions, true ) ) { $url = $this->action_link( $action ); if ( $action === 'flush-cache' ) { @@ -518,12 +655,14 @@ public function do_admin_actions() { $result ? add_settings_error( 'redis-cache', - 'dropin', __( 'Object cache disabled.', 'redis-cache' ), + 'dropin', + __( 'Object cache disabled.', 'redis-cache' ), 'updated' ) : add_settings_error( 'redis-cache', - 'dropin', __( 'Object cache could not be disabled.', 'redis-cache' ), + 'dropin', + __( 'Object cache could not be disabled.', 'redis-cache' ), 'error' ); } @@ -552,7 +691,6 @@ public function do_admin_actions() { 'error' ); } - } $messages = get_settings_errors( 'redis-cache' ); @@ -569,17 +707,31 @@ public function do_admin_actions() { } } + /** + * Dismisses the admin notice for the current user + * + * @return void + */ public function dismiss_notice() { - $notice = sprintf( - 'roc_dismissed_%s', - sanitize_key( $_POST['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' ); + update_user_meta( get_current_user_id(), $notice, '1' ); + } wp_die(); } + /** + * Displays a redis cache pro admin notice + * + * @return void + */ public function pro_notice() { $screen = get_current_screen(); @@ -587,7 +739,7 @@ public function pro_notice() { return; } - if ( ! in_array( $screen->id, array( 'dashboard', 'dashboard-network' ) ) ) { + if ( ! in_array( $screen->id, array( 'dashboard', 'dashboard-network' ), true ) ) { return; } @@ -595,21 +747,27 @@ public function pro_notice() { return; } - if ( get_user_meta( get_current_user_id(), 'roc_dismissed_pro_release_notice', true ) == '1' ) { + if ( 1 === intval( get_user_meta( get_current_user_id(), 'roc_dismissed_pro_release_notice', true ) ) ) { return; } printf( - '

%s %s

', - __( 'Object Cache Pro is out!', 'redis-cache' ), + '

%s %s

', + esc_attr( wp_create_nonce( 'roc_dismiss_notice' ) ), + esc_html__( 'Object Cache Pro is out!', 'redis-cache' ), sprintf( - // translators: %s = Link to the plugin setting screen - __( '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' ), - network_admin_url( $this->page ) + // 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 ( ! class_exists( 'WooCommerce' ) ) { return; @@ -621,7 +779,7 @@ public function wc_pro_notice() { return; } - if ( ! in_array( $screen->id, array( 'edit-shop_order', 'edit-product', 'woocommerce_page_wc-admin' ) ) ) { + if ( ! in_array( $screen->id, array( 'edit-shop_order', 'edit-product', 'woocommerce_page_wc-admin' ), true ) ) { return; } @@ -629,27 +787,38 @@ public function wc_pro_notice() { return; } - if ( get_user_meta( get_current_user_id(), 'roc_dismissed_wc_pro_notice', true ) == '1' ) { + if ( 1 === intval( get_user_meta( get_current_user_id(), 'roc_dismissed_wc_pro_notice', true ) ) ) { return; } printf( - '

%s

%s

', - __( 'Object Cache Pro + WooCommerce = ❤️', 'redis-cache' ), + '

%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 - __( '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' ), - network_admin_url( $this->page ) + // 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', array( $this, 'maybe_print_comment' ), 0 ); } } + /** + * Adds the recorded metrics to redis + * + * @return void + */ public function record_metrics() { global $wp_object_cache; @@ -683,11 +852,16 @@ public function record_metrics() { time(), http_build_query( $metrics, null, ';' ) ); - } catch (Exception $exception) { - error_log($exception); + } catch ( Exception $exception ) { + error_log( $exception ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } } + /** + * Removes recorded metrics after an hour + * + * @return void + */ public function discard_metrics() { global $wp_object_cache; @@ -709,11 +883,16 @@ public function discard_metrics() { 0, time() - HOUR_IN_SECONDS ); - } catch (Exception $exception) { - error_log($exception); + } catch ( Exception $exception ) { + error_log( $exception ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } } + /** + * Displays the redis cache html comment + * + * @return void + */ public function maybe_print_comment() { global $wp_object_cache; @@ -747,12 +926,13 @@ public function maybe_print_comment() { ); if ( ! WP_DEBUG ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped printf( "\n\n", $message ); return; } - $bytes = strlen( serialize( $wp_object_cache->cache ) ); + $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. @@ -762,15 +942,28 @@ function_exists( 'size_format' ) ? size_format( $bytes ) : "{$bytes} bytes", $wp_object_cache->diagnostics['client'] ); - printf( "\n", $message, $debug ); + printf( + "\n", + $message, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + esc_html( $debug ) + ); } + /** + * Initializes the WP filesystem API to be ready for use + * + * @param string $url The URL to post the form to. + * @param bool $silent Wheather to ask the user for credentials if necessary or not. + * @return bool + */ public function initialize_filesystem( $url, $silent = false ) { if ( $silent ) { ob_start(); } - if ( ( $credentials = request_filesystem_credentials( $url ) ) === false ) { + $credentials = request_filesystem_credentials( $url ); + + if ( false === $credentials ) { if ( $silent ) { ob_end_clean(); } @@ -791,8 +984,12 @@ public function initialize_filesystem( $url, $silent = false ) { return true; } - public function test_filesystem_writing() - { + /** + * 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() { global $wp_filesystem; if ( ! $this->initialize_filesystem( '', true ) ) { @@ -833,6 +1030,11 @@ public function test_filesystem_writing() 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; @@ -843,6 +1045,11 @@ public function maybe_update_dropin() { } } + /** + * Updates the `object-cache.php` drop-in + * + * @return void + */ public function update_dropin() { global $wp_filesystem; @@ -862,13 +1069,21 @@ public function update_dropin() { } } + /** + * 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 ) { - if ( $timestamp = wp_next_scheduled( 'rediscache_discard_metrics' ) ) { + $timestamp = wp_next_scheduled( 'rediscache_discard_metrics' ); + + if ( $timestamp ) { wp_unschedule_event( $timestamp, 'rediscache_discard_metrics' ); } @@ -882,7 +1097,17 @@ public function on_deactivation( $plugin ) { 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 diff --git a/includes/class-qm-collector.php b/includes/class-qm-collector.php index 1cda3af4..32829a77 100644 --- a/includes/class-qm-collector.php +++ b/includes/class-qm-collector.php @@ -11,24 +11,42 @@ defined( '\\ABSPATH' ) || exit; +/** + * Query Monitor data collector class definition + */ class QM_Collector extends Base_Collector { + /** + * Collector id + * + * @var string $id + */ public $id = 'cache'; + /** + * Retrieves the collector name + * + * @return string + */ public function name() { return __( 'Object Cache', 'redis-cache' ); } + /** + * Fills the collector with data + * + * @return void + */ public function process() { - global $wp_object_cache; + global $wp_object_cache; $this->process_defaults(); - $plugin = Plugin::instance(); + $roc = Plugin::instance(); - $this->data['status'] = $plugin->get_status(); - $this->data['has_dropin'] = $plugin->object_cache_dropin_exists(); - $this->data['valid_dropin'] = $plugin->validate_object_cache_dropin(); + $this->data['status'] = $roc->get_status(); + $this->data['has_dropin'] = $roc->object_cache_dropin_exists(); + $this->data['valid_dropin'] = $roc->validate_object_cache_dropin(); if ( ! method_exists( $wp_object_cache, 'info' ) ) { return; @@ -54,12 +72,17 @@ public function process() { 'unflushable' => $info->groups->unflushable, ]; - // these are used by Query Monitor + // These are used by Query Monitor. $this->data['stats']['cache_hits'] = $info->hits; $this->data['stats']['cache_misses'] = $info->misses; $this->data['cache_hit_percentage'] = $info->ratio; } + /** + * Sets collector defaults + * + * @return void + */ public function process_defaults() { $this->data['hits'] = 0; $this->data['misses'] = 0; diff --git a/includes/class-qm-output.php b/includes/class-qm-output.php index 17978479..f44534b5 100644 --- a/includes/class-qm-output.php +++ b/includes/class-qm-output.php @@ -11,8 +11,16 @@ defined( '\\ABSPATH' ) || exit; +/** + * Query Monitor output logic class definition + */ class QM_Output extends QM_Output_Html { + /** + * Contructor + * + * @param QM_Collector $collector The corresponding collector instance. + */ public function __construct( QM_Collector $collector ) { parent::__construct( $collector ); @@ -20,10 +28,20 @@ public function __construct( QM_Collector $collector ) { add_filter( 'qm/output/panel_menus', [ $this, 'panel_menu' ] ); } + /** + * Output class name + * + * @return string + */ public function name() { return __( 'Object Cache', 'redis cache' ); } + /** + * Adds a menu to the panel navigation menu in Query Monitor's output + * + * @param array $menu Array of menus. + */ public function admin_menu( array $menu ) { $data = $this->collector->get_data(); @@ -48,6 +66,11 @@ public function admin_menu( array $menu ) { return $menu; } + /** + * Adds a menu item in the panel navigation menu in Query Monitor's output. + * + * @param array $menu Array of menus. + */ public function panel_menu( array $menu ) { $ids = array_keys( $menu ); $request = array_search( 'qm-request', $ids, true ); @@ -64,12 +87,20 @@ public function panel_menu( array $menu ) { ); } + /** + * Renders the output + * + * @return void + */ public function output() { $data = $this->collector->get_data(); if ( ! $data['has_dropin'] ) { $this->before_non_tabular_output(); - echo $this->build_notice( __( 'The Redis Object Cache drop-in is not installed. Use WP CLI or go to "Settings -> Redis" to enable drop-in.', 'redis-cache' ) ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->build_notice( + esc_html__( 'The Redis Object Cache drop-in is not installed. Use WP CLI or go to "Settings -> Redis" to enable drop-in.', 'redis-cache' ) + ); $this->after_non_tabular_output(); return; @@ -77,7 +108,10 @@ public function output() { if ( ! $data['valid_dropin'] ) { $this->before_non_tabular_output(); - echo $this->build_notice( __( 'WordPress is using a foreign object cache drop-in and Redis Object Cache is not being used. Use WP CLI or go to "Settings -> Redis" to enable drop-in.', 'redis-cache' ) ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->build_notice( + esc_html__( 'WordPress is using a foreign object cache drop-in and Redis Object Cache is not being used. Use WP CLI or go to "Settings -> Redis" to enable drop-in.', 'redis-cache' ) + ); $this->after_non_tabular_output(); return; diff --git a/includes/class-ui.php b/includes/class-ui.php index 4d338807..b251dcce 100644 --- a/includes/class-ui.php +++ b/includes/class-ui.php @@ -30,13 +30,7 @@ class UI { * @return void */ public static function register_tab( $slug, $label, $args = [] ) { - self::$tabs[ $slug ] = (object) wp_parse_args( $args, [ - 'label' => $label, - 'file' => WP_REDIS_PLUGIN_PATH . "/includes/ui/tabs/{$slug}.php", - 'slug' => $slug, - 'default' => false, - 'disabled' => false, - ] ); + self::$tabs[ $slug ] = new UI\Tab( $slug, $label, $args ); } /** diff --git a/includes/object-cache.php b/includes/object-cache.php index b47bc984..6aa88ab1 100644 --- a/includes/object-cache.php +++ b/includes/object-cache.php @@ -1,4 +1,4 @@ -add_non_persistent_groups( $groups ); } +/** + * Object cache class definition + */ class WP_Object_Cache { /** @@ -380,7 +386,7 @@ class WP_Object_Cache { /** * Instantiate the Redis class. * - * @param bool $fail_gracefully + * @param bool $fail_gracefully Handles and logs errors if true throws exceptions otherwise. */ public function __construct( $fail_gracefully = true ) { global $blog_id, $table_prefix; @@ -405,17 +411,17 @@ public function __construct( $fail_gracefully = true ) { try { switch ( $client ) { case 'hhvm': - $this->connectUsingHHVM( $parameters ); + $this->connect_using_hhvm( $parameters ); break; case 'phpredis': - $this->connectUsingPhpRedis( $parameters ); + $this->connect_using_phpredis( $parameters ); break; case 'credis': - $this->connectUsingCredis( $parameters ); + $this->connect_using_credis( $parameters ); break; case 'predis': default: - $this->connectUsingPredis( $parameters ); + $this->connect_using_predis( $parameters ); break; } @@ -432,7 +438,7 @@ public function __construct( $fail_gracefully = true ) { $this->handle_exception( $exception ); } - // Assign global and blog prefixes for use with keys + // Assign global and blog prefixes for use with keys. if ( function_exists( 'is_multisite' ) ) { $this->global_prefix = is_multisite() ? '' : $table_prefix; $this->blog_prefix = is_multisite() ? $blog_id : $table_prefix; @@ -509,10 +515,10 @@ protected function build_parameters() { /** * Connect to Redis using the PhpRedis (PECL) extention. * - * @param array $parameters + * @param array $parameters Connection parameters built by the `build_parameters` method. * @return void */ - protected function connectUsingPhpRedis( $parameters ) { + protected function connect_using_phpredis( $parameters ) { $version = phpversion( 'redis' ); $this->diagnostics[ 'client' ] = sprintf( 'PhpRedis (v%s)', $version ); @@ -593,25 +599,20 @@ protected function connectUsingPhpRedis( $parameters ) { /** * Connect to Redis using the Predis library. * - * @param array $parameters + * @param array $parameters Connection parameters built by the `build_parameters` method. + * @throws \Exception If the Predis library was not found or is unreadable. * @return void */ - protected function connectUsingPredis( $parameters ) { + protected function connect_using_predis( $parameters ) { $client = 'Predis'; - // Require PHP 5.4 or greater - if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) { - throw new Exception( 'Predis requires PHP 5.4 or newer.' ); - } - - // Load bundled Predis library + // Load bundled Predis library. if ( ! class_exists( 'Predis\Client' ) ) { $predis = sprintf( '%s/redis-cache/dependencies/predis/predis/autoload.php', defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins' ); - if ( is_readable( $predis ) ) { require_once $predis; } else { @@ -666,17 +667,15 @@ protected function connectUsingPredis( $parameters ) { /** * Connect to Redis using the Credis library. * - * @param array $parameters + * @param array $parameters Connection parameters built by the `build_parameters` method. + * @throws \Exception If the Credis library was not found or is unreadable. + * @throws \Exception If redis sharding should be configured as Credis does not support sharding. + * @throws \Exception If more than one seninel is configured as Credis does not support multiple sentinel servers. * @return void */ - protected function connectUsingCredis( $parameters ) { + protected function connect_using_credis( $parameters ) { $client = 'Credis'; - // Require PHP 5.4 or greater - if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) { - throw new Exception( 'Predis requires PHP 5.4 or newer.' ); - } - $creds_path = sprintf( '%s/redis-cache/dependencies/colinmollenhour/credis/', defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins' @@ -689,8 +688,8 @@ protected function connectUsingCredis( $parameters ) { } if ( - ( defined( 'WP_REDIS_SHARDS' ) || defined( 'WP_REDIS_SENTINEL' ) || defined( 'WP_REDIS_SERVERS' ) || defined( 'WP_REDIS_CLUSTER' ) ) && - ! class_exists( 'Credis_Cluster' ) + ( defined( 'WP_REDIS_SHARDS' ) || defined( 'WP_REDIS_SENTINEL' ) || defined( 'WP_REDIS_SERVERS' ) || defined( 'WP_REDIS_CLUSTER' ) ) && + ! class_exists( 'Credis_Cluster' ) ) { $to_load[] = 'Cluster.php'; @@ -720,7 +719,7 @@ protected function connectUsingCredis( $parameters ) { if ( defined( 'WP_REDIS_SENTINEL' ) ) { if ( is_array( WP_REDIS_SERVERS ) && count( WP_REDIS_SERVERS ) > 1 ) { throw new Exception( - 'Multipe sentinel servers are not supported by the bundled Credis library. Please review your Redis Cache configurations.' + 'Multipe sentinel servers are not supported by the bundled Credis library. Please review your Redis Cache configuration.' ); } @@ -735,7 +734,7 @@ protected function connectUsingCredis( $parameters ) { $clients = $is_cluster ? WP_REDIS_CLUSTER : WP_REDIS_SERVERS; foreach ( $clients as $index => &$connection_string ) { - $url_components = parse_url( $connection_string ); + $url_components = wp_parse_url( $connection_string ); parse_str( $url_components['query'], $add_params ); if ( ! $is_cluster && isset( $add_params['alias'] ) ) { @@ -767,7 +766,7 @@ protected function connectUsingCredis( $parameters ) { $this->redis = new Credis_Client( ...array_values( $args ) ); } - // Don't use PhpRedis + // Don't use PhpRedis if it is available. $this->redis->forceStandalone(); $this->redis->connect(); @@ -786,13 +785,13 @@ protected function connectUsingCredis( $parameters ) { /** * Connect to Redis using HHVM's Redis extention. * - * @param array $parameters + * @param array $parameters Connection parameters built by the `build_parameters` method. * @return void */ - protected function connectUsingHHVM( $parameters ) { + protected function connect_using_hhvm( $parameters ) { $this->redis = new Redis(); - // Adjust host and port, if the scheme is `unix` + // Adjust host and port if the scheme is `unix`. if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) { $parameters['host'] = 'unix://' . $parameters['path']; $parameters['port'] = 0; @@ -863,7 +862,7 @@ public function fetch_info() { * @return bool */ public function redis_status() { - return (bool) $this->redis_connected; + return (bool) $this->redis_connected; } /** @@ -872,7 +871,7 @@ public function redis_status() { * @return mixed */ public function redis_instance() { - return $this->redis; + return $this->redis; } /** @@ -881,7 +880,7 @@ public function redis_instance() { * @return null|string */ public function redis_version() { - return $this->redis_version; + return $this->redis_version; } /** @@ -921,7 +920,7 @@ public function replace( $key, $value, $group = 'default', $expiration = 0 ) { * * Add does not set the value if the key exists; replace does not replace if the value doesn't exist. * - * @param bool $add True if should only add if value doesn't exist, false to only add when value already exists + * @param bool $add True if should only add if value doesn't exist, false to only add when value already exists. * @param string $key The key under which to store the value. * @param mixed $value The value to store. * @param string $group The group value appended to the $key. @@ -940,7 +939,7 @@ protected function add_or_replace( $add, $key, $value, $group = 'default', $expi $result = true; $derived_key = $this->build_key( $key, $group ); - // save if group not excluded and redis is up + // Save if group not excluded and redis is up. if ( ! $this->is_ignored_group( $group ) && $this->redis_status() ) { try { $expiration = apply_filters( 'redis_cache_expiration', $this->validate_expiration( $expiration ), $key, $group ); @@ -993,7 +992,7 @@ protected function add_or_replace( $add, $key, $value, $group = 'default', $expi $exists = isset( $this->cache[ $derived_key ] ); - if ( $add == $exists ) { + if ( (bool) $add === $exists ) { return false; } @@ -1148,6 +1147,12 @@ protected function get_flush_closure( $salt ) { } } + /** + * Quotes a string for usage in the `glob` function + * + * @param string $string The string to quote. + * @return string + */ protected function glob_quote( $string ) { $characters = [ '*', '+', '?', '!', '{', '}', '[', ']', '(', ')', '|', '@' ]; @@ -1174,20 +1179,20 @@ protected function lua_flush_closure( $salt ) { return function () use ( $salt ) { $script = <<redis_version(), '5', '<' ) && version_compare( $this->redis_version(), '3.2', '>=' ) ) { @@ -1222,27 +1227,27 @@ function ( $group ) { ); $script = <<redis_version(), '5', '<' ) && version_compare( $this->redis_version(), '3.2', '>=' ) ) { $script = 'redis.replicate_commands()' . "\n" . $script; @@ -1265,7 +1270,7 @@ function ( $group ) { * @param string $group The group value appended to the $key. * @param string $force Optional. Whether to force a refetch rather than relying on the local * cache. Default false. - * @param bool &$found Optional. Whether the key was found in the cache. Disambiguates a return of + * @param bool $found Optional. Whether the key was found in the cache. Disambiguates a return of * false, a storable value. Passed by reference. Default null. * @return bool|mixed Cached object value. */ @@ -1368,9 +1373,12 @@ public function get_multiple( $keys, $group = 'default', $force = false ) { } } - $remaining_keys = array_filter( $keys, function ( $key ) use ( $cache ) { - return ! isset( $cache[ $key ] ); - } ); + $remaining_keys = array_filter( + $keys, + function ( $key ) use ( $cache ) { + return ! isset( $cache[ $key ] ); + } + ); if ( empty( $remaining_keys ) ) { return $cache; @@ -1379,9 +1387,12 @@ public function get_multiple( $keys, $group = 'default', $force = false ) { $start_time = microtime( true ); try { - $remaining_ids = array_map( function ( $key ) use ( $derived_keys ) { - return $derived_keys[ $key ]; - }, $remaining_keys ); + $remaining_ids = array_map( + function ( $key ) use ( $derived_keys ) { + return $derived_keys[ $key ]; + }, + $remaining_keys + ); $results = array_combine( $remaining_keys, @@ -1442,7 +1453,7 @@ public function set( $key, $value, $group = 'default', $expiration = 0 ) { $start_time = microtime( true ); - // save if group not excluded from redis and redis is up + // Save if group not excluded from redis and redis is up. if ( ! $this->is_ignored_group( $group ) && $this->redis_status() ) { $expiration = apply_filters( 'redis_cache_expiration', $this->validate_expiration( $expiration ), $key, $group ); @@ -1462,7 +1473,7 @@ public function set( $key, $value, $group = 'default', $expiration = 0 ) { $this->cache_time += ( microtime( true ) - $start_time ); } - // if the set was successful, or we didn't go to redis + // If the set was successful, or we didn't go to redis. if ( $result ) { $this->add_to_internal_cache( $derived_key, $value ); } @@ -1479,16 +1490,16 @@ public function set( $key, $value, $group = 'default', $expiration = 0 ) { /** * Increment a Redis counter by the amount specified * - * @param string $key - * @param int $offset - * @param string $group + * @param string $key The key name. + * @param int $offset Optional. The increment. Defaults to 1. + * @param string $group Optional. The key group. Default is 'default'. * @return int|bool */ public function increment( $key, $offset = 1, $group = 'default' ) { $offset = (int) $offset; $derived_key = $this->build_key( $key, $group ); - // If group is a non-Redis group, save to internal cache, not Redis + // If group is a non-Redis group, save to internal cache, not Redis. if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) { $value = $this->get_from_internal_cache( $derived_key ); $value += $offset; @@ -1520,10 +1531,11 @@ public function increment( $key, $offset = 1, $group = 'default' ) { /** * Alias of `increment()`. * - * @param string $key - * @param int $offset - * @param string $group - * @return bool + * @see self::increment() + * @param string $key The key name. + * @param int $offset Optional. The increment. Defaults to 1. + * @param string $group Optional. The key group. Default is 'default'. + * @return int|bool */ public function incr( $key, $offset = 1, $group = 'default' ) { return $this->increment( $key, $offset, $group ); @@ -1532,16 +1544,16 @@ public function incr( $key, $offset = 1, $group = 'default' ) { /** * Decrement a Redis counter by the amount specified * - * @param string $key - * @param int $offset - * @param string $group + * @param string $key The key name. + * @param int $offset Optional. The decrement. Defaults to 1. + * @param string $group Optional. The key group. Default is 'default'. * @return int|bool */ public function decrement( $key, $offset = 1, $group = 'default' ) { $derived_key = $this->build_key( $key, $group ); $offset = (int) $offset; - // If group is a non-Redis group, save to internal cache, not Redis + // If group is a non-Redis group, save to internal cache, not Redis. if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) { $value = $this->get_from_internal_cache( $derived_key ); $value -= $offset; @@ -1553,7 +1565,7 @@ public function decrement( $key, $offset = 1, $group = 'default' ) { $start_time = microtime( true ); try { - // Save to Redis + // Save to Redis. $result = $this->parse_redis_response( $this->redis->decrBy( $derived_key, $offset ) ); $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) ); @@ -1573,33 +1585,30 @@ public function decrement( $key, $offset = 1, $group = 'default' ) { /** * Render data about current cache requests + * Used by the Debug bar plugin * - * @return string + * @return void */ public function stats() { - $bytes = array_map( - function ( $key ) { - return strlen( serialize( $key ) ); - }, - $this->cache - ); - - ?>

- Redis Status: - redis_status() ? 'Connected' : 'Not connected'; ?> -
- Redis Client: - diagnostics['client'] ?: 'Unknown'; ?> -
- Cache Hits: - cache_hits; ?> -
- Cache Misses: - cache_misses; ?> -
- Cache Size: - kB -

+ $bytes = strlen( serialize( $wp_object_cache->cache ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + + ?> +

+ Redis Status: + redis_status() ? 'Connected' : 'Not connected'; ?> +
+ Redis Client: + diagnostics['client'] ?: 'Unknown'; ?> +
+ Cache Hits: + cache_hits ); ?> +
+ Cache Misses: + cache_misses ); ?> +
+ Cache Size: + kB +

cache @@ -1669,13 +1679,13 @@ public function build_key( $key, $group = 'default' ) { * @return string Sanitized string. */ protected function sanitize_key_part( $part ) { - return str_replace( ':', '-', $part ); + return str_replace( ':', '-', $part ); } /** * Checks if the given group is part the ignored group array * - * @param string $group Name of the group to check + * @param string $group Name of the group to check. * @return bool */ protected function is_ignored_group( $group ) { @@ -1685,7 +1695,7 @@ protected function is_ignored_group( $group ) { /** * Checks if the given group is part the global group array * - * @param string $group Name of the group to check + * @param string $group Name of the group to check. * @return bool */ protected function is_global_group( $group ) { @@ -1695,7 +1705,7 @@ protected function is_global_group( $group ) { /** * Checks if the given group is part the unflushable group array * - * @param string $group Name of the group to check + * @param string $group Name of the group to check. * @return bool */ protected function is_unflushable_group( $group ) { @@ -1705,7 +1715,7 @@ protected function is_unflushable_group( $group ) { /** * Convert Redis responses into something meaningful * - * @param mixed $response + * @param mixed $response Response sent from the redis instance. * @return mixed */ protected function parse_redis_response( $response ) { @@ -1760,7 +1770,7 @@ public function get_from_internal_cache( $derived_key ) { /** * In multisite, switch blog prefix when switching blogs * - * @param int $_blog_id + * @param int $_blog_id Blog ID. * @return bool */ public function switch_to_blog( $_blog_id ) { @@ -1813,7 +1823,7 @@ public function add_unflushable_groups( $groups ) { /** * Wrapper to validate the cache keys expiration value * - * @param mixed $expiration Incoming expiration value (whatever it is) + * @param mixed $expiration Incoming expiration value (whatever it is). */ protected function validate_expiration( $expiration ) { $expiration = is_int( $expiration ) || ctype_digit( $expiration ) ? (int) $expiration : 0; @@ -1844,8 +1854,9 @@ protected function maybe_unserialize( $original ) { return igbinary_unserialize( $original ); } - // don't attempt to unserialize data that wasn't serialized going in + // Don't attempt to unserialize data that wasn't serialized going in. if ( $this->is_serialized( $original ) ) { + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize $value = @unserialize( $original ); return is_object( $value ) ? clone $value : $value; @@ -1874,10 +1885,12 @@ protected function maybe_serialize( $data ) { } if ( is_array( $data ) || is_object( $data ) ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize return serialize( $data ); } if ( $this->is_serialized( $data, false ) ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize return serialize( $data ); } @@ -1902,7 +1915,7 @@ protected function is_serialized( $data, $strict = true ) { $data = trim( $data ); - if ( 'N;' == $data ) { + if ( 'N;' === $data ) { return true; } @@ -1949,8 +1962,8 @@ protected function is_serialized( $data, $strict = true ) { } elseif ( false === strpos( $data, '"' ) ) { return false; } - // or else fall through - // no break + // Or else fall through. + // No break! case 'a': case 'O': return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data ); @@ -1969,11 +1982,13 @@ protected function is_serialized( $data, $strict = true ) { * Handle the redis failure gracefully or throw an exception. * * @param \Exception $exception Exception thrown. + * @throws \Exception If `fail_gracefully` flag is set to a falsy value. + * @return void */ protected function handle_exception( $exception ) { $this->redis_connected = false; - // When Redis is unavailable, fall back to the internal cache by forcing all groups to be "no redis" groups + // When Redis is unavailable, fall back to the internal cache by forcing all groups to be "no redis" groups. $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $this->global_groups ) ); if ( ! $this->fail_gracefully ) { @@ -1982,7 +1997,7 @@ protected function handle_exception( $exception ) { $this->errors[] = $exception->getMessage(); - error_log( $exception ); + error_log( $exception ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log if ( function_exists( 'do_action' ) ) { do_action( 'redis_object_cache_error', $exception ); @@ -1992,12 +2007,13 @@ protected function handle_exception( $exception ) { /** * Allows access to private properties for backwards compatibility. * - * @param string $name + * @param string $name Name of the property. * @return mixed */ public function __get( $name ) { - return isset($this->{$name}) ? $this->{$name} : null; + return isset( $this->{$name} ) ? $this->{$name} : null; } } endif; +// phpcs:enable Generic.WhiteSpace.ScopeIndent.IncorrectExact, Generic.WhiteSpace.ScopeIndent.Incorrect diff --git a/includes/ui/class-tab.php b/includes/ui/class-tab.php new file mode 100644 index 00000000..7759cd12 --- /dev/null +++ b/includes/ui/class-tab.php @@ -0,0 +1,219 @@ + $slug, + 'label' => $label, + 'file' => WP_REDIS_PLUGIN_PATH . "/includes/ui/tabs/{$slug}.php", + ] + ); + foreach ( $args ?: [] as $property => $value ) { + if ( property_exists( $this, $property ) ) { + $this->{$property} = $value; + } else { + $this->custom[ $property ] = $value; + } + } + } + + /** + * Getter for tab slug + * + * @return string + */ + public function slug() { + return $this->slug; + } + + /** + * Getter for tab label + * + * @return string + */ + public function label() { + return $this->label; + } + + /** + * Getter for tab file + * + * @return string + */ + public function file() { + return $this->file; + } + + /** + * Getter for tab disabled state + * + * @return bool + */ + public function is_disabled() { + return $this->disabled; + } + + /** + * Getter for tab default state + * + * @return bool + */ + public function is_default() { + return $this->default; + } + + /** + * Getter for tab custom data + * + * @param $string $key Custom data key. + * @return mixed + */ + public function custom( $key ) { + if ( ! isset( $this->custom[ $key ] ) ) { + return null; + } + return $this->custom[ $key ]; + } + + /** + * Disabled notice for tab + * + * @return string + */ + public function disabled_notice() { + return sprintf( + // translators: %s = Tab label. + __( '%s are disabled for this site.', 'redis-cache' ), + $this->label + ); + } + + /** + * Displays the tab template + * + * @return void + */ + public function display() { + $roc = Plugin::instance(); + include $this->file; + } + + /** + * Returns the tab nav id attribute + * + * @return string + */ + public function nav_id() { + return apply_filters( 'roc_tab_nav_id', "{$this->slug}-tab", $this ); + } + + /** + * Returns the tab nav css classes + * + * @return string + */ + public function nav_classes() { + $classes = [ + 'nav-tab', + ]; + if ( $this->default ) { + $classes[] = 'nav-tab-active'; + } + if ( $this->disabled ) { + $classes[] = 'nav-tab-disabled'; + } + return implode( ' ', apply_filters( 'roc_tab_nav_classes', $classes, $this ) ); + } + + /** + * Returns the tab id attribute + * + * @return string + */ + public function id() { + return apply_filters( 'roc_tab_id', "{$this->slug}-pane", $this ); + } + + /** + * Returns the tab css classes + * + * @return string + */ + public function classes() { + $classes = [ + 'tab-pane', + "tab-pane-{$this->slug}", + ]; + if ( $this->default ) { + $classes[] = 'active'; + } + return implode( ' ', apply_filters( 'roc_tab_classes', $classes, $this ) ); + } + +} diff --git a/includes/ui/diagnostics.php b/includes/ui/diagnostics.php index ce3cb1a0..d9620056 100644 --- a/includes/ui/diagnostics.php +++ b/includes/ui/diagnostics.php @@ -10,14 +10,14 @@ global $wp_object_cache; $info = []; -$filesystem = $plugin->test_filesystem_writing(); -$dropin = $plugin->validate_object_cache_dropin(); +$filesystem = $roc->test_filesystem_writing(); +$dropin = $roc->validate_object_cache_dropin(); $disabled = defined( 'WP_REDIS_DISABLED' ) && WP_REDIS_DISABLED; -$info['Status'] = $plugin->get_status(); -$info['Client'] = $plugin->get_redis_client_name(); -$info['Drop-in'] = $plugin->object_cache_dropin_exists() - ? ($dropin ? 'Valid' : 'Invalid') +$info['Status'] = $roc->get_status(); +$info['Client'] = $roc->get_redis_client_name(); +$info['Drop-in'] = $roc->object_cache_dropin_exists() + ? ( $dropin ? 'Valid' : 'Invalid' ) : 'Not installed'; $info['Disabled'] = $disabled ? 'Yes' : 'No'; $info['Filesystem'] = is_wp_error( $filesystem ) ? $filesystem->get_error_message() : 'Working'; @@ -52,7 +52,7 @@ } $info['Plugin Version'] = WP_REDIS_VERSION; -$info['Redis Version'] = $plugin->get_redis_version() ?: 'Unknown'; +$info['Redis Version'] = $roc->get_redis_version() ?: 'Unknown'; $info['Multisite'] = is_multisite() ? 'Yes' : 'No'; @@ -140,6 +140,6 @@ if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::line( "{$name}: $value" ); } else { - echo "{$name}: {$value}\r\n"; + echo esc_textarea( "{$name}: {$value}\r\n" ); } } diff --git a/includes/ui/settings.php b/includes/ui/settings.php index 2632a36d..9ba99cd7 100644 --- a/includes/ui/settings.php +++ b/includes/ui/settings.php @@ -26,28 +26,24 @@