: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Class for looking up a site's health based on a user's WordPress environment.
* @subpackage Site_Health
#[AllowDynamicProperties]
private static $instance = null;
private $is_acceptable_mysql_version;
private $is_recommended_mysql_version;
public $is_mariadb = false;
private $mysql_server_version = '';
private $mysql_required_version = '5.5';
private $mysql_recommended_version = '8.0';
private $mariadb_recommended_version = '10.5';
public $php_memory_limit;
public $last_missed_cron = null;
public $last_late_cron = null;
private $timeout_missed_cron = null;
private $timeout_late_cron = null;
* WP_Site_Health constructor.
public function __construct() {
$this->maybe_create_scheduled_event();
// Save memory limit before it's affected by wp_raise_memory_limit( 'admin' ).
$this->php_memory_limit = ini_get( 'memory_limit' );
$this->timeout_late_cron = 0;
$this->timeout_missed_cron = - 5 * MINUTE_IN_SECONDS;
if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
$this->timeout_late_cron = - 15 * MINUTE_IN_SECONDS;
$this->timeout_missed_cron = - 1 * HOUR_IN_SECONDS;
add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_action( 'wp_site_health_scheduled_check', array( $this, 'wp_cron_scheduled_check' ) );
add_action( 'site_health_tab_content', array( $this, 'show_site_health_tab' ) );
* Outputs the content of a tab in the Site Health screen.
* @param string $tab Slug of the current tab being displayed.
public function show_site_health_tab( $tab ) {
if ( 'debug' === $tab ) {
require_once ABSPATH . 'wp-admin/site-health-info.php';
* Returns an instance of the WP_Site_Health class, or create one if none exist yet.
* @return WP_Site_Health|null
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new WP_Site_Health();
* Enqueues the site health scripts.
public function enqueue_scripts() {
$screen = get_current_screen();
if ( 'site-health' !== $screen->id && 'dashboard' !== $screen->id ) {
$health_check_js_variables = array(
'site_status' => wp_create_nonce( 'health-check-site-status' ),
'site_status_result' => wp_create_nonce( 'health-check-site-status-result' ),
$issue_counts = get_transient( 'health-check-site-status-result' );
if ( false !== $issue_counts ) {
$issue_counts = json_decode( $issue_counts );
$health_check_js_variables['site_status']['issues'] = $issue_counts;
if ( 'site-health' === $screen->id && ( ! isset( $_GET['tab'] ) || empty( $_GET['tab'] ) ) ) {
$tests = WP_Site_Health::get_tests();
// Don't run https test on development environments.
if ( $this->is_development_environment() ) {
unset( $tests['async']['https_status'] );
foreach ( $tests['direct'] as $test ) {
if ( is_string( $test['test'] ) ) {
$test_function = sprintf(
if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) {
$health_check_js_variables['site_status']['direct'][] = $this->perform_test( array( $this, $test_function ) );
if ( is_callable( $test['test'] ) ) {
$health_check_js_variables['site_status']['direct'][] = $this->perform_test( $test['test'] );
foreach ( $tests['async'] as $test ) {
if ( is_string( $test['test'] ) ) {
$health_check_js_variables['site_status']['async'][] = array(
'has_rest' => ( isset( $test['has_rest'] ) ? $test['has_rest'] : false ),
'headers' => isset( $test['headers'] ) ? $test['headers'] : array(),
wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables );
* Runs a Site Health test directly.
* @param callable $callback
private function perform_test( $callback ) {
* Filters the output of a finished Site Health test.
* @param array $test_result {
* An associative array of test result data.
* @type string $label A label describing the test, and is used as a header in the output.
* @type string $status The status of the test, which can be a value of `good`, `recommended` or `critical`.
* Tests are put into categories which have an associated badge shown, these can be modified and assigned here.
* @type string $label The test label, for example `Performance`.
* @type string $color Default `blue`. A string representing a color to use for the label.
* @type string $description A more descriptive explanation of what the test looks for, and why it is important for the end user.
* @type string $actions An action to direct the user to where they can resolve the issue, if one exists.
* @type string $test The name of the test being ran, used as a reference point.
return apply_filters( 'site_status_test_result', call_user_func( $callback ) );
* Runs the SQL version checks.
* These values are used in later tests, but the part of preparing them is more easily managed
* early in the class for ease of access and discovery.
* @global wpdb $wpdb WordPress database abstraction object.
private function prepare_sql_data() {
$mysql_server_type = $wpdb->db_server_info();
$this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' );
if ( stristr( $mysql_server_type, 'mariadb' ) ) {
$this->is_mariadb = true;
$this->mysql_recommended_version = $this->mariadb_recommended_version;
$this->is_acceptable_mysql_version = version_compare( $this->mysql_required_version, $this->mysql_server_version, '<=' );
$this->is_recommended_mysql_version = version_compare( $this->mysql_recommended_version, $this->mysql_server_version, '<=' );
* Tests whether `wp_version_check` is blocked.
* It's possible to block updates with the `wp_version_check` filter, but this can't be checked
* during an Ajax call, as the filter is never introduced then.
* This filter overrides a standard page request if it's made by an admin through the Ajax call
* with the right query argument to check for this.
public function check_wp_version_check_exists() {
if ( ! is_admin() || ! is_user_logged_in() || ! current_user_can( 'update_core' ) || ! isset( $_GET['health-check-test-wp_version_check'] ) ) {
echo ( has_filter( 'wp_version_check', 'wp_version_check' ) ? 'yes' : 'no' );
* Tests for WordPress version and outputs it.
* Gives various results depending on what kind of updates are available, if any, to encourage
* the user to install security updates as a priority.
* @return array The test result.
public function get_test_wordpress_version() {
'label' => __( 'Performance' ),
'test' => 'wordpress_version',
$core_current_version = get_bloginfo( 'version' );
$core_updates = get_core_updates();
if ( ! is_array( $core_updates ) ) {
$result['status'] = 'recommended';
$result['label'] = sprintf(
/* translators: %s: Your current version of WordPress. */
__( 'WordPress version %s' ),
$result['description'] = sprintf(
__( 'Unable to check if any new versions of WordPress are available.' )
$result['actions'] = sprintf(
esc_url( admin_url( 'update-core.php?force-check=1' ) ),
__( 'Check for updates manually' )
foreach ( $core_updates as $core => $update ) {
if ( 'upgrade' === $update->response ) {
$current_version = explode( '.', $core_current_version );
$new_version = explode( '.', $update->version );
$current_major = $current_version[0] . '.' . $current_version[1];
$new_major = $new_version[0] . '.' . $new_version[1];
$result['label'] = sprintf(
/* translators: %s: The latest version of WordPress available. */
__( 'WordPress update available (%s)' ),
$result['actions'] = sprintf(
esc_url( admin_url( 'update-core.php' ) ),
__( 'Install the latest version of WordPress' )
if ( $current_major !== $new_major ) {
// This is a major version mismatch.
$result['status'] = 'recommended';
$result['description'] = sprintf(
__( 'A new version of WordPress is available.' )
// This is a minor version, sometimes considered more critical.
$result['status'] = 'critical';
$result['badge']['label'] = __( 'Security' );
$result['description'] = sprintf(
__( 'A new minor update is available for your site. Because minor updates often address security, it’s important to install them.' )
$result['status'] = 'good';
$result['label'] = sprintf(
/* translators: %s: The current version of WordPress installed on this site. */
__( 'Your version of WordPress (%s) is up to date' ),
$result['description'] = sprintf(
__( 'You are currently running the latest version of WordPress available, keep it up!' )
* Tests if plugins are outdated, or unnecessary.
* The test checks if your plugins are up to date, and encourages you to remove any
* @return array The test result.
public function get_test_plugin_version() {
'label' => __( 'Your plugins are all up to date' ),
'label' => __( 'Security' ),
'description' => sprintf(
__( 'Plugins extend your site’s functionality with things like contact forms, ecommerce and much more. That means they have deep access to your site, so it’s vital to keep them up to date.' )
'<p><a href="%s">%s</a></p>',
esc_url( admin_url( 'plugins.php' ) ),
__( 'Manage your plugins' )
'test' => 'plugin_version',
$plugins = get_plugins();
$plugin_updates = get_plugin_updates();
$plugins_need_update = 0;
// Loop over the available plugins and check their versions and active state.
foreach ( $plugins as $plugin_path => $plugin ) {
if ( is_plugin_active( $plugin_path ) ) {
if ( array_key_exists( $plugin_path, $plugin_updates ) ) {
// Add a notice if there are outdated plugins.
if ( $plugins_need_update > 0 ) {
$result['status'] = 'critical';
$result['label'] = __( 'You have plugins waiting to be updated' );
$result['description'] .= sprintf(
/* translators: %d: The number of outdated plugins. */
'Your site has %d plugin waiting to be updated.',
'Your site has %d plugins waiting to be updated.',
$result['actions'] .= sprintf(
'<p><a href="%s">%s</a></p>',
esc_url( network_admin_url( 'plugins.php?plugin_status=upgrade' ) ),
__( 'Update your plugins' )
if ( 1 === $plugins_active ) {
$result['description'] .= sprintf(
__( 'Your site has 1 active plugin, and it is up to date.' )
} elseif ( $plugins_active > 0 ) {
$result['description'] .= sprintf(
/* translators: %d: The number of active plugins. */
'Your site has %d active plugin, and it is up to date.',
'Your site has %d active plugins, and they are all up to date.',
$result['description'] .= sprintf(
__( 'Your site does not have any active plugins.' )
// Check if there are inactive plugins.
if ( $plugins_total > $plugins_active && ! is_multisite() ) {
$unused_plugins = $plugins_total - $plugins_active;
$result['status'] = 'recommended';
$result['label'] = __( 'You should remove inactive plugins' );
$result['description'] .= sprintf(
/* translators: %d: The number of inactive plugins. */
'Your site has %d inactive plugin.',
'Your site has %d inactive plugins.',
__( 'Inactive plugins are tempting targets for attackers. If you are not going to use a plugin, you should consider removing it.' )
$result['actions'] .= sprintf(
'<p><a href="%s">%s</a></p>',
esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ),
__( 'Manage inactive plugins' )
* Tests if themes are outdated, or unnecessary.
* Checks if your site has a default theme (to fall back on if there is a need),
* if your themes are up to date and, finally, encourages you to remove any themes
* @return array The test results.
public function get_test_theme_version() {
'label' => __( 'Your themes are all up to date' ),
'label' => __( 'Security' ),
'description' => sprintf(
__( 'Themes add your site’s look and feel. It’s important to keep them up to date, to stay consistent with your brand and keep your site secure.' )