diff --git a/wp-admin/includes/class-wp-site-health.php b/wp-admin/includes/class-wp-site-health.php index c7b924cd6f..ea43a695ed 100644 --- a/wp-admin/includes/class-wp-site-health.php +++ b/wp-admin/includes/class-wp-site-health.php @@ -2263,6 +2263,105 @@ class WP_Site_Health { return $result; } + /** + * Tests if sites uses persistent object cache. + * + * Checks if site uses persistent object cache or recommends to use it if not. + * + * @since 6.1.0 + * + * @return array The test result. + */ + public function get_test_persistent_object_cache() { + /** + * Filters the action URL for the persistent object cache health check. + * + * @since 6.1.0 + * + * @param string $action_url Learn more link for persistent object cache health check. + */ + $action_url = apply_filters( + 'site_status_persistent_object_cache_url', + /* translators: Localized Support reference. */ + __( 'https://wordpress.org/support/article/optimization/#object-caching' ) + ); + + $result = array( + 'test' => 'persistent_object_cache', + 'status' => 'good', + 'badge' => array( + 'label' => __( 'Performance' ), + 'color' => 'blue', + ), + 'label' => __( 'A persistent object cache is being used' ), + 'description' => sprintf( + '
%s
', + __( "A persistent object cache makes your site's database more efficient, resulting in faster load times because WordPress can retrieve your site's content and settings much more quickly." ) + ), + 'actions' => sprintf( + '', + esc_url( $action_url ), + __( 'Learn more about persistent object caching.' ), + /* translators: Accessibility text. */ + __( '(opens in a new tab)' ) + ), + ); + + if ( wp_using_ext_object_cache() ) { + return $result; + } + + if ( ! $this->should_suggest_persistent_object_cache() ) { + $result['label'] = __( 'A persistent object cache is not required' ); + + return $result; + } + + $available_services = $this->available_object_cache_services(); + + $notes = __( 'Your hosting provider can tell you if a persistent object cache can be enabled on your site.' ); + + if ( ! empty( $available_services ) ) { + $notes .= ' ' . sprintf( + /* translators: Available object caching services. */ + __( 'Your host appears to support the following object caching services: %s.' ), + implode( ', ', $available_services ) + ); + } + + /** + * Filters the second paragraph of the health check's description + * when suggesting the use of a persistent object cache. + * + * Hosts may want to replace the notes to recommend their preferred object caching solution. + * + * Plugin authors may want to append notes (not replace) on why object caching is recommended for their plugin. + * + * @since 6.1.0 + * + * @param string $notes The notes appended to the health check description. + * @param array $available_services The list of available persistent object cache services. + */ + $notes = apply_filters( 'site_status_persistent_object_cache_notes', $notes, $available_services ); + + $result['status'] = 'recommended'; + $result['label'] = __( 'You should use a persistent object cache' ); + $result['description'] .= sprintf( + '%s
', + wp_kses( + $notes, + array( + 'a' => array( 'href' => true ), + 'code' => true, + 'em' => true, + 'strong' => true, + ) + ) + ); + + return $result; + } + /** * Return a set of tests that belong to the site status page. * @@ -2383,6 +2482,14 @@ class WP_Site_Health { ); } + // Only check for a persistent object cache in production environments to not unnecessarily promote complicated setups. + if ( 'production' === wp_get_environment_type() ) { + $tests['direct']['persistent_object_cache'] = array( + 'label' => __( 'Persistent object cache' ), + 'test' => 'persistent_object_cache', + ); + } + /** * Add or modify which site status tests are run on a site. * @@ -2858,4 +2965,127 @@ class WP_Site_Health { return in_array( wp_get_environment_type(), array( 'development', 'local' ), true ); } + /** + * Determines whether to suggest using a persistent object cache. + * + * @since 6.1.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @return bool Whether to suggest using a persistent object cache. + */ + public function should_suggest_persistent_object_cache() { + global $wpdb; + + if ( is_multisite() ) { + return true; + } + + /** + * Filters whether to suggest use of a persistent object cache and bypass default threshold checks. + * + * Using this filter allows to override the default logic, effectively short-circuiting the method. + * + * @since 6.1.0 + * + * @param bool|null $suggest Boolean to short-circuit, for whether to suggest using a persistent object cache. + * Default null. + */ + $short_circuit = apply_filters( 'site_status_should_suggest_persistent_object_cache', null ); + if ( is_bool( $short_circuit ) ) { + return $short_circuit; + } + + /** + * Filters the thresholds used to determine whether to suggest the use of a persistent object cache. + * + * @since 6.1.0 + * + * @param array $thresholds The list of threshold names and numbers. + */ + $thresholds = apply_filters( + 'site_status_persistent_object_cache_thresholds', + array( + 'alloptions_count' => 500, + 'alloptions_bytes' => 100000, + 'comments_count' => 1000, + 'options_count' => 1000, + 'posts_count' => 1000, + 'terms_count' => 1000, + 'users_count' => 1000, + ) + ); + + $alloptions = wp_load_alloptions(); + + if ( $thresholds['alloptions_count'] < count( $alloptions ) ) { + return true; + } + + if ( $thresholds['alloptions_bytes'] < strlen( serialize( $alloptions ) ) ) { + return true; + } + + $table_names = implode( "','", array( $wpdb->comments, $wpdb->options, $wpdb->posts, $wpdb->terms, $wpdb->users ) ); + + // With InnoDB the `TABLE_ROWS` are estimates, which are accurate enough and faster to retrieve than individual `COUNT()` queries. + $results = $wpdb->get_results( + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- This query cannot use interpolation. + "SELECT TABLE_NAME AS 'table', TABLE_ROWS AS 'rows', SUM(data_length + index_length) as 'bytes' FROM information_schema.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME IN ('$table_names') GROUP BY TABLE_NAME;", + DB_NAME + ), + OBJECT_K + ); + + $threshold_map = array( + 'comments_count' => $wpdb->comments, + 'options_count' => $wpdb->options, + 'posts_count' => $wpdb->posts, + 'terms_count' => $wpdb->terms, + 'users_count' => $wpdb->users, + ); + + foreach ( $threshold_map as $threshold => $table ) { + if ( $thresholds[ $threshold ] <= $results[ $table ]->rows ) { + return true; + } + } + + return false; + } + + /** + * Returns a list of available persistent object cache services. + * + * @since 6.1.0 + * + * @return array The list of available persistent object cache services. + */ + private function available_object_cache_services() { + $extensions = array_map( + 'extension_loaded', + array( + 'APCu' => 'apcu', + 'Redis' => 'redis', + 'Relay' => 'relay', + 'Memcache' => 'memcache', + 'Memcached' => 'memcached', + ) + ); + + $services = array_keys( array_filter( $extensions ) ); + + /** + * Filters the persistent object cache services available to the user. + * + * This can be useful to hide or add services not included in the defaults. + * + * @since 6.1.0 + * + * @param array $services The list of available persistent object cache services. + */ + return apply_filters( 'site_status_available_object_cache_services', $services ); + } + } diff --git a/wp-includes/version.php b/wp-includes/version.php index ca526e08ea..f696d1157a 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.1-alpha-53954'; +$wp_version = '6.1-alpha-53955'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.