wp/wp-admin/includes/class-wp-site-health.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
     5  * @package WordPress
     5  * @package WordPress
     6  * @subpackage Site_Health
     6  * @subpackage Site_Health
     7  * @since 5.2.0
     7  * @since 5.2.0
     8  */
     8  */
     9 
     9 
       
    10 #[AllowDynamicProperties]
    10 class WP_Site_Health {
    11 class WP_Site_Health {
    11 	private static $instance = null;
    12 	private static $instance = null;
    12 
    13 
    13 	private $mysql_min_version_check;
    14 	private $is_acceptable_mysql_version;
    14 	private $mysql_rec_version_check;
    15 	private $is_recommended_mysql_version;
    15 
    16 
    16 	public $is_mariadb                           = false;
    17 	public $is_mariadb                   = false;
    17 	private $mysql_server_version                = '';
    18 	private $mysql_server_version        = '';
    18 	private $health_check_mysql_required_version = '5.5';
    19 	private $mysql_required_version      = '5.5';
    19 	private $health_check_mysql_rec_version      = '';
    20 	private $mysql_recommended_version   = '8.0';
       
    21 	private $mariadb_recommended_version = '10.5';
    20 
    22 
    21 	public $php_memory_limit;
    23 	public $php_memory_limit;
    22 
    24 
    23 	public $schedules;
    25 	public $schedules;
    24 	public $crons;
    26 	public $crons;
    53 
    55 
    54 		add_action( 'site_health_tab_content', array( $this, 'show_site_health_tab' ) );
    56 		add_action( 'site_health_tab_content', array( $this, 'show_site_health_tab' ) );
    55 	}
    57 	}
    56 
    58 
    57 	/**
    59 	/**
    58 	 * Output the content of a tab in the Site Health screen.
    60 	 * Outputs the content of a tab in the Site Health screen.
    59 	 *
    61 	 *
    60 	 * @since 5.8.0
    62 	 * @since 5.8.0
    61 	 *
    63 	 *
    62 	 * @param string $tab Slug of the current tab being displayed.
    64 	 * @param string $tab Slug of the current tab being displayed.
    63 	 */
    65 	 */
    64 	public function show_site_health_tab( $tab ) {
    66 	public function show_site_health_tab( $tab ) {
    65 		if ( 'debug' === $tab ) {
    67 		if ( 'debug' === $tab ) {
    66 			require_once ABSPATH . '/wp-admin/site-health-info.php';
    68 			require_once ABSPATH . 'wp-admin/site-health-info.php';
    67 		}
    69 		}
    68 	}
    70 	}
    69 
    71 
    70 	/**
    72 	/**
    71 	 * Return an instance of the WP_Site_Health class, or create one if none exist yet.
    73 	 * Returns an instance of the WP_Site_Health class, or create one if none exist yet.
    72 	 *
    74 	 *
    73 	 * @since 5.4.0
    75 	 * @since 5.4.0
    74 	 *
    76 	 *
    75 	 * @return WP_Site_Health|null
    77 	 * @return WP_Site_Health|null
    76 	 */
    78 	 */
   158 
   160 
   159 		wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables );
   161 		wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables );
   160 	}
   162 	}
   161 
   163 
   162 	/**
   164 	/**
   163 	 * Run a Site Health test directly.
   165 	 * Runs a Site Health test directly.
   164 	 *
   166 	 *
   165 	 * @since 5.4.0
   167 	 * @since 5.4.0
   166 	 *
   168 	 *
   167 	 * @param callable $callback
   169 	 * @param callable $callback
   168 	 * @return mixed|void
   170 	 * @return mixed|void
   191 		 */
   193 		 */
   192 		return apply_filters( 'site_status_test_result', call_user_func( $callback ) );
   194 		return apply_filters( 'site_status_test_result', call_user_func( $callback ) );
   193 	}
   195 	}
   194 
   196 
   195 	/**
   197 	/**
   196 	 * Run the SQL version checks.
   198 	 * Runs the SQL version checks.
   197 	 *
   199 	 *
   198 	 * These values are used in later tests, but the part of preparing them is more easily managed
   200 	 * These values are used in later tests, but the part of preparing them is more easily managed
   199 	 * early in the class for ease of access and discovery.
   201 	 * early in the class for ease of access and discovery.
   200 	 *
   202 	 *
   201 	 * @since 5.2.0
   203 	 * @since 5.2.0
   203 	 * @global wpdb $wpdb WordPress database abstraction object.
   205 	 * @global wpdb $wpdb WordPress database abstraction object.
   204 	 */
   206 	 */
   205 	private function prepare_sql_data() {
   207 	private function prepare_sql_data() {
   206 		global $wpdb;
   208 		global $wpdb;
   207 
   209 
   208 		if ( $wpdb->use_mysqli ) {
   210 		$mysql_server_type = $wpdb->db_server_info();
   209 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info
       
   210 			$mysql_server_type = mysqli_get_server_info( $wpdb->dbh );
       
   211 		} else {
       
   212 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
       
   213 			$mysql_server_type = mysql_get_server_info( $wpdb->dbh );
       
   214 		}
       
   215 
   211 
   216 		$this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' );
   212 		$this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' );
   217 
   213 
   218 		$this->health_check_mysql_rec_version = '5.6';
       
   219 
       
   220 		if ( stristr( $mysql_server_type, 'mariadb' ) ) {
   214 		if ( stristr( $mysql_server_type, 'mariadb' ) ) {
   221 			$this->is_mariadb                     = true;
   215 			$this->is_mariadb                = true;
   222 			$this->health_check_mysql_rec_version = '10.0';
   216 			$this->mysql_recommended_version = $this->mariadb_recommended_version;
   223 		}
   217 		}
   224 
   218 
   225 		$this->mysql_min_version_check = version_compare( '5.5', $this->mysql_server_version, '<=' );
   219 		$this->is_acceptable_mysql_version  = version_compare( $this->mysql_required_version, $this->mysql_server_version, '<=' );
   226 		$this->mysql_rec_version_check = version_compare( $this->health_check_mysql_rec_version, $this->mysql_server_version, '<=' );
   220 		$this->is_recommended_mysql_version = version_compare( $this->mysql_recommended_version, $this->mysql_server_version, '<=' );
   227 	}
   221 	}
   228 
   222 
   229 	/**
   223 	/**
   230 	 * Test if `wp_version_check` is blocked.
   224 	 * Tests whether `wp_version_check` is blocked.
   231 	 *
   225 	 *
   232 	 * It's possible to block updates with the `wp_version_check` filter, but this can't be checked
   226 	 * It's possible to block updates with the `wp_version_check` filter, but this can't be checked
   233 	 * during an Ajax call, as the filter is never introduced then.
   227 	 * during an Ajax call, as the filter is never introduced then.
   234 	 *
   228 	 *
   235 	 * This filter overrides a standard page request if it's made by an admin through the Ajax call
   229 	 * This filter overrides a standard page request if it's made by an admin through the Ajax call
   347 
   341 
   348 		return $result;
   342 		return $result;
   349 	}
   343 	}
   350 
   344 
   351 	/**
   345 	/**
   352 	 * Test if plugins are outdated, or unnecessary.
   346 	 * Tests if plugins are outdated, or unnecessary.
   353 	 *
   347 	 *
   354 	 * The tests checks if your plugins are up to date, and encourages you to remove any
   348 	 * The test checks if your plugins are up to date, and encourages you to remove any
   355 	 * that are not in use.
   349 	 * that are not in use.
   356 	 *
   350 	 *
   357 	 * @since 5.2.0
   351 	 * @since 5.2.0
   358 	 *
   352 	 *
   359 	 * @return array The test result.
   353 	 * @return array The test result.
   379 		);
   373 		);
   380 
   374 
   381 		$plugins        = get_plugins();
   375 		$plugins        = get_plugins();
   382 		$plugin_updates = get_plugin_updates();
   376 		$plugin_updates = get_plugin_updates();
   383 
   377 
   384 		$plugins_have_updates = false;
   378 		$plugins_active      = 0;
   385 		$plugins_active       = 0;
   379 		$plugins_total       = 0;
   386 		$plugins_total        = 0;
   380 		$plugins_need_update = 0;
   387 		$plugins_need_update  = 0;
       
   388 
   381 
   389 		// Loop over the available plugins and check their versions and active state.
   382 		// Loop over the available plugins and check their versions and active state.
   390 		foreach ( $plugins as $plugin_path => $plugin ) {
   383 		foreach ( $plugins as $plugin_path => $plugin ) {
   391 			$plugins_total++;
   384 			++$plugins_total;
   392 
   385 
   393 			if ( is_plugin_active( $plugin_path ) ) {
   386 			if ( is_plugin_active( $plugin_path ) ) {
   394 				$plugins_active++;
   387 				++$plugins_active;
   395 			}
   388 			}
   396 
       
   397 			$plugin_version = $plugin['Version'];
       
   398 
   389 
   399 			if ( array_key_exists( $plugin_path, $plugin_updates ) ) {
   390 			if ( array_key_exists( $plugin_path, $plugin_updates ) ) {
   400 				$plugins_need_update++;
   391 				++$plugins_need_update;
   401 				$plugins_have_updates = true;
       
   402 			}
   392 			}
   403 		}
   393 		}
   404 
   394 
   405 		// Add a notice if there are outdated plugins.
   395 		// Add a notice if there are outdated plugins.
   406 		if ( $plugins_need_update > 0 ) {
   396 		if ( $plugins_need_update > 0 ) {
   430 			if ( 1 === $plugins_active ) {
   420 			if ( 1 === $plugins_active ) {
   431 				$result['description'] .= sprintf(
   421 				$result['description'] .= sprintf(
   432 					'<p>%s</p>',
   422 					'<p>%s</p>',
   433 					__( 'Your site has 1 active plugin, and it is up to date.' )
   423 					__( 'Your site has 1 active plugin, and it is up to date.' )
   434 				);
   424 				);
   435 			} else {
   425 			} elseif ( $plugins_active > 0 ) {
   436 				$result['description'] .= sprintf(
   426 				$result['description'] .= sprintf(
   437 					'<p>%s</p>',
   427 					'<p>%s</p>',
   438 					sprintf(
   428 					sprintf(
   439 						/* translators: %d: The number of active plugins. */
   429 						/* translators: %d: The number of active plugins. */
   440 						_n(
   430 						_n(
   443 							$plugins_active
   433 							$plugins_active
   444 						),
   434 						),
   445 						$plugins_active
   435 						$plugins_active
   446 					)
   436 					)
   447 				);
   437 				);
       
   438 			} else {
       
   439 				$result['description'] .= sprintf(
       
   440 					'<p>%s</p>',
       
   441 					__( 'Your site does not have any active plugins.' )
       
   442 				);
   448 			}
   443 			}
   449 		}
   444 		}
   450 
   445 
   451 		// Check if there are inactive plugins.
   446 		// Check if there are inactive plugins.
   452 		if ( $plugins_total > $plugins_active && ! is_multisite() ) {
   447 		if ( $plugins_total > $plugins_active && ! is_multisite() ) {
   479 
   474 
   480 		return $result;
   475 		return $result;
   481 	}
   476 	}
   482 
   477 
   483 	/**
   478 	/**
   484 	 * Test if themes are outdated, or unnecessary.
   479 	 * Tests if themes are outdated, or unnecessary.
   485 	 *
   480 	 *
   486 	 * Сhecks if your site has a default theme (to fall back on if there is a need),
   481 	 * Checks if your site has a default theme (to fall back on if there is a need),
   487 	 * if your themes are up to date and, finally, encourages you to remove any themes
   482 	 * if your themes are up to date and, finally, encourages you to remove any themes
   488 	 * that are not needed.
   483 	 * that are not needed.
   489 	 *
   484 	 *
   490 	 * @since 5.2.0
   485 	 * @since 5.2.0
   491 	 *
   486 	 *
   546 				$using_default_theme = true;
   541 				$using_default_theme = true;
   547 			}
   542 			}
   548 		}
   543 		}
   549 
   544 
   550 		foreach ( $all_themes as $theme_slug => $theme ) {
   545 		foreach ( $all_themes as $theme_slug => $theme ) {
   551 			$themes_total++;
   546 			++$themes_total;
   552 
   547 
   553 			if ( array_key_exists( $theme_slug, $theme_updates ) ) {
   548 			if ( array_key_exists( $theme_slug, $theme_updates ) ) {
   554 				$themes_need_updates++;
   549 				++$themes_need_updates;
   555 			}
   550 			}
   556 		}
   551 		}
   557 
   552 
   558 		// If this is a child theme, increase the allowed theme count by one, to account for the parent.
   553 		// If this is a child theme, increase the allowed theme count by one, to account for the parent.
   559 		if ( is_child_theme() ) {
   554 		if ( is_child_theme() ) {
   560 			$allowed_theme_count++;
   555 			++$allowed_theme_count;
   561 		}
   556 		}
   562 
   557 
   563 		// If there's a default theme installed and not in use, we count that as allowed as well.
   558 		// If there's a default theme installed and not in use, we count that as allowed as well.
   564 		if ( $has_default_theme && ! $using_default_theme ) {
   559 		if ( $has_default_theme && ! $using_default_theme ) {
   565 			$allowed_theme_count++;
   560 			++$allowed_theme_count;
   566 		}
   561 		}
   567 
   562 
   568 		if ( $themes_total > $allowed_theme_count ) {
   563 		if ( $themes_total > $allowed_theme_count ) {
   569 			$has_unused_themes = true;
   564 			$has_unused_themes = true;
   570 			$themes_inactive   = ( $themes_total - $allowed_theme_count );
   565 			$themes_inactive   = ( $themes_total - $allowed_theme_count );
   593 			if ( 1 === $themes_total ) {
   588 			if ( 1 === $themes_total ) {
   594 				$result['description'] .= sprintf(
   589 				$result['description'] .= sprintf(
   595 					'<p>%s</p>',
   590 					'<p>%s</p>',
   596 					__( 'Your site has 1 installed theme, and it is up to date.' )
   591 					__( 'Your site has 1 installed theme, and it is up to date.' )
   597 				);
   592 				);
   598 			} else {
   593 			} elseif ( $themes_total > 0 ) {
   599 				$result['description'] .= sprintf(
   594 				$result['description'] .= sprintf(
   600 					'<p>%s</p>',
   595 					'<p>%s</p>',
   601 					sprintf(
   596 					sprintf(
   602 						/* translators: %d: The number of themes. */
   597 						/* translators: %d: The number of themes. */
   603 						_n(
   598 						_n(
   605 							'Your site has %d installed themes, and they are all up to date.',
   600 							'Your site has %d installed themes, and they are all up to date.',
   606 							$themes_total
   601 							$themes_total
   607 						),
   602 						),
   608 						$themes_total
   603 						$themes_total
   609 					)
   604 					)
       
   605 				);
       
   606 			} else {
       
   607 				$result['description'] .= sprintf(
       
   608 					'<p>%s</p>',
       
   609 					__( 'Your site does not have any installed themes.' )
   610 				);
   610 				);
   611 			}
   611 			}
   612 		}
   612 		}
   613 
   613 
   614 		if ( $has_unused_themes && $show_unused_themes && ! is_multisite() ) {
   614 		if ( $has_unused_themes && $show_unused_themes && ! is_multisite() ) {
   715 
   715 
   716 		return $result;
   716 		return $result;
   717 	}
   717 	}
   718 
   718 
   719 	/**
   719 	/**
   720 	 * Test if the supplied PHP version is supported.
   720 	 * Tests if the supplied PHP version is supported.
   721 	 *
   721 	 *
   722 	 * @since 5.2.0
   722 	 * @since 5.2.0
   723 	 *
   723 	 *
   724 	 * @return array The test results.
   724 	 * @return array The test results.
   725 	 */
   725 	 */
   739 			),
   739 			),
   740 			'description' => sprintf(
   740 			'description' => sprintf(
   741 				'<p>%s</p>',
   741 				'<p>%s</p>',
   742 				sprintf(
   742 				sprintf(
   743 					/* translators: %s: The minimum recommended PHP version. */
   743 					/* translators: %s: The minimum recommended PHP version. */
   744 					__( 'PHP is the programming language used to build and maintain WordPress. Newer versions of PHP are created with increased performance in mind, so you may see a positive effect on your site&#8217;s performance. The minimum recommended version of PHP is %s.' ),
   744 					__( 'PHP is one of the programming languages used to build WordPress. Newer versions of PHP receive regular security updates and may increase your site&#8217;s performance. The minimum recommended version of PHP is %s.' ),
   745 					$response ? $response['recommended_version'] : ''
   745 					$response ? $response['recommended_version'] : ''
   746 				)
   746 				)
   747 			),
   747 			),
   748 			'actions'     => sprintf(
   748 			'actions'     => sprintf(
   749 				'<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
   749 				'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
   750 				esc_url( wp_get_update_php_url() ),
   750 				esc_url( wp_get_update_php_url() ),
   751 				__( 'Learn more about updating PHP' ),
   751 				__( 'Learn more about updating PHP' ),
   752 				/* translators: Accessibility text. */
   752 				/* translators: Hidden accessibility text. */
   753 				__( '(opens in a new tab)' )
   753 				__( '(opens in a new tab)' )
   754 			),
   754 			),
   755 			'test'        => 'php_version',
   755 			'test'        => 'php_version',
   756 		);
   756 		);
   757 
   757 
   762 
   762 
   763 		// The PHP version is older than the recommended version, but still receiving active support.
   763 		// The PHP version is older than the recommended version, but still receiving active support.
   764 		if ( $response['is_supported'] ) {
   764 		if ( $response['is_supported'] ) {
   765 			$result['label'] = sprintf(
   765 			$result['label'] = sprintf(
   766 				/* translators: %s: The server PHP version. */
   766 				/* translators: %s: The server PHP version. */
   767 				__( 'Your site is running an older version of PHP (%s)' ),
   767 				__( 'Your site is running on an older version of PHP (%s)' ),
   768 				PHP_VERSION
   768 				PHP_VERSION
   769 			);
   769 			);
   770 			$result['status'] = 'recommended';
   770 			$result['status'] = 'recommended';
       
   771 
       
   772 			return $result;
       
   773 		}
       
   774 
       
   775 		/*
       
   776 		 * The PHP version is still receiving security fixes, but is lower than
       
   777 		 * the expected minimum version that will be required by WordPress in the near future.
       
   778 		 */
       
   779 		if ( $response['is_secure'] && $response['is_lower_than_future_minimum'] ) {
       
   780 			// The `is_secure` array key name doesn't actually imply this is a secure version of PHP. It only means it receives security updates.
       
   781 
       
   782 			$result['label'] = sprintf(
       
   783 				/* translators: %s: The server PHP version. */
       
   784 				__( 'Your site is running on an outdated version of PHP (%s), which soon will not be supported by WordPress.' ),
       
   785 				PHP_VERSION
       
   786 			);
       
   787 
       
   788 			$result['status']         = 'critical';
       
   789 			$result['badge']['label'] = __( 'Requirements' );
   771 
   790 
   772 			return $result;
   791 			return $result;
   773 		}
   792 		}
   774 
   793 
   775 		// The PHP version is only receiving security fixes.
   794 		// The PHP version is only receiving security fixes.
   776 		if ( $response['is_secure'] ) {
   795 		if ( $response['is_secure'] ) {
   777 			$result['label'] = sprintf(
   796 			$result['label'] = sprintf(
   778 				/* translators: %s: The server PHP version. */
   797 				/* translators: %s: The server PHP version. */
   779 				__( 'Your site is running an older version of PHP (%s), which should be updated' ),
   798 				__( 'Your site is running on an older version of PHP (%s), which should be updated' ),
   780 				PHP_VERSION
   799 				PHP_VERSION
   781 			);
   800 			);
   782 			$result['status'] = 'recommended';
   801 			$result['status'] = 'recommended';
   783 
   802 
   784 			return $result;
   803 			return $result;
   785 		}
   804 		}
   786 
   805 
   787 		// Anything no longer secure must be updated.
   806 		// No more security updates for the PHP version, and lower than the expected minimum version required by WordPress.
   788 		$result['label'] = sprintf(
   807 		if ( $response['is_lower_than_future_minimum'] ) {
   789 			/* translators: %s: The server PHP version. */
   808 			$message = sprintf(
   790 			__( 'Your site is running an outdated version of PHP (%s), which requires an update' ),
   809 				/* translators: %s: The server PHP version. */
   791 			PHP_VERSION
   810 				__( 'Your site is running on an outdated version of PHP (%s), which does not receive security updates and soon will not be supported by WordPress.' ),
   792 		);
   811 				PHP_VERSION
   793 		$result['status']         = 'critical';
   812 			);
       
   813 		} else {
       
   814 			// No more security updates for the PHP version, must be updated.
       
   815 			$message = sprintf(
       
   816 				/* translators: %s: The server PHP version. */
       
   817 				__( 'Your site is running on an outdated version of PHP (%s), which does not receive security updates. It should be updated.' ),
       
   818 				PHP_VERSION
       
   819 			);
       
   820 		}
       
   821 
       
   822 		$result['label']  = $message;
       
   823 		$result['status'] = 'critical';
       
   824 
   794 		$result['badge']['label'] = __( 'Security' );
   825 		$result['badge']['label'] = __( 'Security' );
   795 
   826 
   796 		return $result;
   827 		return $result;
   797 	}
   828 	}
   798 
   829 
   799 	/**
   830 	/**
   800 	 * Check if the passed extension or function are available.
   831 	 * Checks if the passed extension or function are available.
   801 	 *
   832 	 *
   802 	 * Make the check for available PHP modules into a simple boolean operator for a cleaner test runner.
   833 	 * Make the check for available PHP modules into a simple boolean operator for a cleaner test runner.
   803 	 *
   834 	 *
   804 	 * @since 5.2.0
   835 	 * @since 5.2.0
   805 	 * @since 5.3.0 The `$constant_name` and `$class_name` parameters were added.
   836 	 * @since 5.3.0 The `$constant_name` and `$class_name` parameters were added.
   834 
   865 
   835 		return true;
   866 		return true;
   836 	}
   867 	}
   837 
   868 
   838 	/**
   869 	/**
   839 	 * Test if required PHP modules are installed on the host.
   870 	 * Tests if required PHP modules are installed on the host.
   840 	 *
   871 	 *
   841 	 * This test builds on the recommendations made by the WordPress Hosting Team
   872 	 * This test builds on the recommendations made by the WordPress Hosting Team
   842 	 * as seen at https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions
   873 	 * as seen at https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions
   843 	 *
   874 	 *
   844 	 * @since 5.2.0
   875 	 * @since 5.2.0
   861 					__( 'The WordPress Hosting Team maintains a list of those modules, both recommended and required, in <a href="%1$s" %2$s>the team handbook%3$s</a>.' ),
   892 					__( 'The WordPress Hosting Team maintains a list of those modules, both recommended and required, in <a href="%1$s" %2$s>the team handbook%3$s</a>.' ),
   862 					/* translators: Localized team handbook, if one exists. */
   893 					/* translators: Localized team handbook, if one exists. */
   863 					esc_url( __( 'https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions' ) ),
   894 					esc_url( __( 'https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions' ) ),
   864 					'target="_blank" rel="noopener"',
   895 					'target="_blank" rel="noopener"',
   865 					sprintf(
   896 					sprintf(
   866 						' <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span>',
   897 						'<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span>',
   867 						/* translators: Accessibility text. */
   898 						/* translators: Hidden accessibility text. */
   868 						__( '(opens in a new tab)' )
   899 						__( '(opens in a new tab)' )
   869 					)
   900 					)
   870 				)
   901 				)
   871 			),
   902 			),
   872 			'actions'     => '',
   903 			'actions'     => '',
   969 				'fallback_for' => 'zip',
  1000 				'fallback_for' => 'zip',
   970 			),
  1001 			),
   971 		);
  1002 		);
   972 
  1003 
   973 		/**
  1004 		/**
   974 		 * An array representing all the modules we wish to test for.
  1005 		 * Filters the array representing all the modules we wish to test for.
   975 		 *
  1006 		 *
   976 		 * @since 5.2.0
  1007 		 * @since 5.2.0
   977 		 * @since 5.3.0 The `$constant` and `$class` parameters were added.
  1008 		 * @since 5.3.0 The `$constant` and `$class` parameters were added.
   978 		 *
  1009 		 *
   979 		 * @param array $modules {
  1010 		 * @param array $modules {
  1020 					|| version_compare( PHP_VERSION, $module['php_bundled_version'], '<' ) )
  1051 					|| version_compare( PHP_VERSION, $module['php_bundled_version'], '<' ) )
  1021 			) {
  1052 			) {
  1022 				if ( $module['required'] ) {
  1053 				if ( $module['required'] ) {
  1023 					$result['status'] = 'critical';
  1054 					$result['status'] = 'critical';
  1024 
  1055 
  1025 					$class         = 'error';
  1056 					$class = 'error';
       
  1057 					/* translators: Hidden accessibility text. */
  1026 					$screen_reader = __( 'Error' );
  1058 					$screen_reader = __( 'Error' );
  1027 					$message       = sprintf(
  1059 					$message       = sprintf(
  1028 						/* translators: %s: The module name. */
  1060 						/* translators: %s: The module name. */
  1029 						__( 'The required module, %s, is not installed, or has been disabled.' ),
  1061 						__( 'The required module, %s, is not installed, or has been disabled.' ),
  1030 						$library
  1062 						$library
  1031 					);
  1063 					);
  1032 				} else {
  1064 				} else {
  1033 					$class         = 'warning';
  1065 					$class = 'warning';
       
  1066 					/* translators: Hidden accessibility text. */
  1034 					$screen_reader = __( 'Warning' );
  1067 					$screen_reader = __( 'Warning' );
  1035 					$message       = sprintf(
  1068 					$message       = sprintf(
  1036 						/* translators: %s: The module name. */
  1069 						/* translators: %s: The module name. */
  1037 						__( 'The optional module, %s, is not installed, or has been disabled.' ),
  1070 						__( 'The optional module, %s, is not installed, or has been disabled.' ),
  1038 						$library
  1071 						$library
  1073 
  1106 
  1074 		return $result;
  1107 		return $result;
  1075 	}
  1108 	}
  1076 
  1109 
  1077 	/**
  1110 	/**
  1078 	 * Test if the PHP default timezone is set to UTC.
  1111 	 * Tests if the PHP default timezone is set to UTC.
  1079 	 *
  1112 	 *
  1080 	 * @since 5.3.1
  1113 	 * @since 5.3.1
  1081 	 *
  1114 	 *
  1082 	 * @return array The test results.
  1115 	 * @return array The test results.
  1083 	 */
  1116 	 */
  1114 
  1147 
  1115 		return $result;
  1148 		return $result;
  1116 	}
  1149 	}
  1117 
  1150 
  1118 	/**
  1151 	/**
  1119 	 * Test if there's an active PHP session that can affect loopback requests.
  1152 	 * Tests if there's an active PHP session that can affect loopback requests.
  1120 	 *
  1153 	 *
  1121 	 * @since 5.5.0
  1154 	 * @since 5.5.0
  1122 	 *
  1155 	 *
  1123 	 * @return array The test results.
  1156 	 * @return array The test results.
  1124 	 */
  1157 	 */
  1160 
  1193 
  1161 		return $result;
  1194 		return $result;
  1162 	}
  1195 	}
  1163 
  1196 
  1164 	/**
  1197 	/**
  1165 	 * Test if the SQL server is up to date.
  1198 	 * Tests if the SQL server is up to date.
  1166 	 *
  1199 	 *
  1167 	 * @since 5.2.0
  1200 	 * @since 5.2.0
  1168 	 *
  1201 	 *
  1169 	 * @return array The test results.
  1202 	 * @return array The test results.
  1170 	 */
  1203 	 */
  1183 			'description' => sprintf(
  1216 			'description' => sprintf(
  1184 				'<p>%s</p>',
  1217 				'<p>%s</p>',
  1185 				__( 'The SQL server is a required piece of software for the database WordPress uses to store all your site&#8217;s content and settings.' )
  1218 				__( 'The SQL server is a required piece of software for the database WordPress uses to store all your site&#8217;s content and settings.' )
  1186 			),
  1219 			),
  1187 			'actions'     => sprintf(
  1220 			'actions'     => sprintf(
  1188 				'<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1221 				'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1189 				/* translators: Localized version of WordPress requirements if one exists. */
  1222 				/* translators: Localized version of WordPress requirements if one exists. */
  1190 				esc_url( __( 'https://wordpress.org/about/requirements/' ) ),
  1223 				esc_url( __( 'https://wordpress.org/about/requirements/' ) ),
  1191 				__( 'Learn more about what WordPress requires to run.' ),
  1224 				__( 'Learn more about what WordPress requires to run.' ),
  1192 				/* translators: Accessibility text. */
  1225 				/* translators: Hidden accessibility text. */
  1193 				__( '(opens in a new tab)' )
  1226 				__( '(opens in a new tab)' )
  1194 			),
  1227 			),
  1195 			'test'        => 'sql_server',
  1228 			'test'        => 'sql_server',
  1196 		);
  1229 		);
  1197 
  1230 
  1198 		$db_dropin = file_exists( WP_CONTENT_DIR . '/db.php' );
  1231 		$db_dropin = file_exists( WP_CONTENT_DIR . '/db.php' );
  1199 
  1232 
  1200 		if ( ! $this->mysql_rec_version_check ) {
  1233 		if ( ! $this->is_recommended_mysql_version ) {
  1201 			$result['status'] = 'recommended';
  1234 			$result['status'] = 'recommended';
  1202 
  1235 
  1203 			$result['label'] = __( 'Outdated SQL server' );
  1236 			$result['label'] = __( 'Outdated SQL server' );
  1204 
  1237 
  1205 			$result['description'] .= sprintf(
  1238 			$result['description'] .= sprintf(
  1206 				'<p>%s</p>',
  1239 				'<p>%s</p>',
  1207 				sprintf(
  1240 				sprintf(
  1208 					/* translators: 1: The database engine in use (MySQL or MariaDB). 2: Database server recommended version number. */
  1241 					/* translators: 1: The database engine in use (MySQL or MariaDB). 2: Database server recommended version number. */
  1209 					__( 'For optimal performance and security reasons, you should consider running %1$s version %2$s or higher. Contact your web hosting company to correct this.' ),
  1242 					__( 'For optimal performance and security reasons, you should consider running %1$s version %2$s or higher. Contact your web hosting company to correct this.' ),
  1210 					( $this->is_mariadb ? 'MariaDB' : 'MySQL' ),
  1243 					( $this->is_mariadb ? 'MariaDB' : 'MySQL' ),
  1211 					$this->health_check_mysql_rec_version
  1244 					$this->mysql_recommended_version
  1212 				)
  1245 				)
  1213 			);
  1246 			);
  1214 		}
  1247 		}
  1215 
  1248 
  1216 		if ( ! $this->mysql_min_version_check ) {
  1249 		if ( ! $this->is_acceptable_mysql_version ) {
  1217 			$result['status'] = 'critical';
  1250 			$result['status'] = 'critical';
  1218 
  1251 
  1219 			$result['label']          = __( 'Severely outdated SQL server' );
  1252 			$result['label']          = __( 'Severely outdated SQL server' );
  1220 			$result['badge']['label'] = __( 'Security' );
  1253 			$result['badge']['label'] = __( 'Security' );
  1221 
  1254 
  1223 				'<p>%s</p>',
  1256 				'<p>%s</p>',
  1224 				sprintf(
  1257 				sprintf(
  1225 					/* translators: 1: The database engine in use (MySQL or MariaDB). 2: Database server minimum version number. */
  1258 					/* translators: 1: The database engine in use (MySQL or MariaDB). 2: Database server minimum version number. */
  1226 					__( 'WordPress requires %1$s version %2$s or higher. Contact your web hosting company to correct this.' ),
  1259 					__( 'WordPress requires %1$s version %2$s or higher. Contact your web hosting company to correct this.' ),
  1227 					( $this->is_mariadb ? 'MariaDB' : 'MySQL' ),
  1260 					( $this->is_mariadb ? 'MariaDB' : 'MySQL' ),
  1228 					$this->health_check_mysql_required_version
  1261 					$this->mysql_required_version
  1229 				)
  1262 				)
  1230 			);
  1263 			);
  1231 		}
  1264 		}
  1232 
  1265 
  1233 		if ( $db_dropin ) {
  1266 		if ( $db_dropin ) {
  1249 
  1282 
  1250 		return $result;
  1283 		return $result;
  1251 	}
  1284 	}
  1252 
  1285 
  1253 	/**
  1286 	/**
  1254 	 * Test if the database server is capable of using utf8mb4.
  1287 	 * Tests if the site can communicate with WordPress.org.
  1255 	 *
       
  1256 	 * @since 5.2.0
       
  1257 	 *
       
  1258 	 * @return array The test results.
       
  1259 	 */
       
  1260 	public function get_test_utf8mb4_support() {
       
  1261 		global $wpdb;
       
  1262 
       
  1263 		if ( ! $this->mysql_server_version ) {
       
  1264 			$this->prepare_sql_data();
       
  1265 		}
       
  1266 
       
  1267 		$result = array(
       
  1268 			'label'       => __( 'UTF8MB4 is supported' ),
       
  1269 			'status'      => 'good',
       
  1270 			'badge'       => array(
       
  1271 				'label' => __( 'Performance' ),
       
  1272 				'color' => 'blue',
       
  1273 			),
       
  1274 			'description' => sprintf(
       
  1275 				'<p>%s</p>',
       
  1276 				__( 'UTF8MB4 is the character set WordPress prefers for database storage because it safely supports the widest set of characters and encodings, including Emoji, enabling better support for non-English languages.' )
       
  1277 			),
       
  1278 			'actions'     => '',
       
  1279 			'test'        => 'utf8mb4_support',
       
  1280 		);
       
  1281 
       
  1282 		if ( ! $this->is_mariadb ) {
       
  1283 			if ( version_compare( $this->mysql_server_version, '5.5.3', '<' ) ) {
       
  1284 				$result['status'] = 'recommended';
       
  1285 
       
  1286 				$result['label'] = __( 'utf8mb4 requires a MySQL update' );
       
  1287 
       
  1288 				$result['description'] .= sprintf(
       
  1289 					'<p>%s</p>',
       
  1290 					sprintf(
       
  1291 						/* translators: %s: Version number. */
       
  1292 						__( 'WordPress&#8217; utf8mb4 support requires MySQL version %s or greater. Please contact your server administrator.' ),
       
  1293 						'5.5.3'
       
  1294 					)
       
  1295 				);
       
  1296 			} else {
       
  1297 				$result['description'] .= sprintf(
       
  1298 					'<p>%s</p>',
       
  1299 					__( 'Your MySQL version supports utf8mb4.' )
       
  1300 				);
       
  1301 			}
       
  1302 		} else { // MariaDB introduced utf8mb4 support in 5.5.0.
       
  1303 			if ( version_compare( $this->mysql_server_version, '5.5.0', '<' ) ) {
       
  1304 				$result['status'] = 'recommended';
       
  1305 
       
  1306 				$result['label'] = __( 'utf8mb4 requires a MariaDB update' );
       
  1307 
       
  1308 				$result['description'] .= sprintf(
       
  1309 					'<p>%s</p>',
       
  1310 					sprintf(
       
  1311 						/* translators: %s: Version number. */
       
  1312 						__( 'WordPress&#8217; utf8mb4 support requires MariaDB version %s or greater. Please contact your server administrator.' ),
       
  1313 						'5.5.0'
       
  1314 					)
       
  1315 				);
       
  1316 			} else {
       
  1317 				$result['description'] .= sprintf(
       
  1318 					'<p>%s</p>',
       
  1319 					__( 'Your MariaDB version supports utf8mb4.' )
       
  1320 				);
       
  1321 			}
       
  1322 		}
       
  1323 
       
  1324 		if ( $wpdb->use_mysqli ) {
       
  1325 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_client_info
       
  1326 			$mysql_client_version = mysqli_get_client_info();
       
  1327 		} else {
       
  1328 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
       
  1329 			$mysql_client_version = mysql_get_client_info();
       
  1330 		}
       
  1331 
       
  1332 		/*
       
  1333 		 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
       
  1334 		 * mysqlnd has supported utf8mb4 since 5.0.9.
       
  1335 		 */
       
  1336 		if ( false !== strpos( $mysql_client_version, 'mysqlnd' ) ) {
       
  1337 			$mysql_client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $mysql_client_version );
       
  1338 			if ( version_compare( $mysql_client_version, '5.0.9', '<' ) ) {
       
  1339 				$result['status'] = 'recommended';
       
  1340 
       
  1341 				$result['label'] = __( 'utf8mb4 requires a newer client library' );
       
  1342 
       
  1343 				$result['description'] .= sprintf(
       
  1344 					'<p>%s</p>',
       
  1345 					sprintf(
       
  1346 						/* translators: 1: Name of the library, 2: Number of version. */
       
  1347 						__( 'WordPress&#8217; utf8mb4 support requires MySQL client library (%1$s) version %2$s or newer. Please contact your server administrator.' ),
       
  1348 						'mysqlnd',
       
  1349 						'5.0.9'
       
  1350 					)
       
  1351 				);
       
  1352 			}
       
  1353 		} else {
       
  1354 			if ( version_compare( $mysql_client_version, '5.5.3', '<' ) ) {
       
  1355 				$result['status'] = 'recommended';
       
  1356 
       
  1357 				$result['label'] = __( 'utf8mb4 requires a newer client library' );
       
  1358 
       
  1359 				$result['description'] .= sprintf(
       
  1360 					'<p>%s</p>',
       
  1361 					sprintf(
       
  1362 						/* translators: 1: Name of the library, 2: Number of version. */
       
  1363 						__( 'WordPress&#8217; utf8mb4 support requires MySQL client library (%1$s) version %2$s or newer. Please contact your server administrator.' ),
       
  1364 						'libmysql',
       
  1365 						'5.5.3'
       
  1366 					)
       
  1367 				);
       
  1368 			}
       
  1369 		}
       
  1370 
       
  1371 		return $result;
       
  1372 	}
       
  1373 
       
  1374 	/**
       
  1375 	 * Test if the site can communicate with WordPress.org.
       
  1376 	 *
  1288 	 *
  1377 	 * @since 5.2.0
  1289 	 * @since 5.2.0
  1378 	 *
  1290 	 *
  1379 	 * @return array The test results.
  1291 	 * @return array The test results.
  1380 	 */
  1292 	 */
  1409 
  1321 
  1410 			$result['description'] .= sprintf(
  1322 			$result['description'] .= sprintf(
  1411 				'<p>%s</p>',
  1323 				'<p>%s</p>',
  1412 				sprintf(
  1324 				sprintf(
  1413 					'<span class="error"><span class="screen-reader-text">%s</span></span> %s',
  1325 					'<span class="error"><span class="screen-reader-text">%s</span></span> %s',
       
  1326 					/* translators: Hidden accessibility text. */
  1414 					__( 'Error' ),
  1327 					__( 'Error' ),
  1415 					sprintf(
  1328 					sprintf(
  1416 						/* translators: 1: The IP address WordPress.org resolves to. 2: The error returned by the lookup. */
  1329 						/* translators: 1: The IP address WordPress.org resolves to. 2: The error returned by the lookup. */
  1417 						__( 'Your site is unable to reach WordPress.org at %1$s, and returned the error: %2$s' ),
  1330 						__( 'Your site is unable to reach WordPress.org at %1$s, and returned the error: %2$s' ),
  1418 						gethostbyname( 'api.wordpress.org' ),
  1331 						gethostbyname( 'api.wordpress.org' ),
  1420 					)
  1333 					)
  1421 				)
  1334 				)
  1422 			);
  1335 			);
  1423 
  1336 
  1424 			$result['actions'] = sprintf(
  1337 			$result['actions'] = sprintf(
  1425 				'<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1338 				'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1426 				/* translators: Localized Support reference. */
  1339 				/* translators: Localized Support reference. */
  1427 				esc_url( __( 'https://wordpress.org/support' ) ),
  1340 				esc_url( __( 'https://wordpress.org/support/forums/' ) ),
  1428 				__( 'Get help resolving this issue.' ),
  1341 				__( 'Get help resolving this issue.' ),
  1429 				/* translators: Accessibility text. */
  1342 				/* translators: Hidden accessibility text. */
  1430 				__( '(opens in a new tab)' )
  1343 				__( '(opens in a new tab)' )
  1431 			);
  1344 			);
  1432 		}
  1345 		}
  1433 
  1346 
  1434 		return $result;
  1347 		return $result;
  1435 	}
  1348 	}
  1436 
  1349 
  1437 	/**
  1350 	/**
  1438 	 * Test if debug information is enabled.
  1351 	 * Tests if debug information is enabled.
  1439 	 *
  1352 	 *
  1440 	 * When WP_DEBUG is enabled, errors and information may be disclosed to site visitors,
  1353 	 * When WP_DEBUG is enabled, errors and information may be disclosed to site visitors,
  1441 	 * or logged to a publicly accessible file.
  1354 	 * or logged to a publicly accessible file.
  1442 	 *
  1355 	 *
  1443 	 * Debugging is also frequently left enabled after looking for errors on a site,
  1356 	 * Debugging is also frequently left enabled after looking for errors on a site,
  1458 			'description' => sprintf(
  1371 			'description' => sprintf(
  1459 				'<p>%s</p>',
  1372 				'<p>%s</p>',
  1460 				__( 'Debug mode is often enabled to gather more details about an error or site failure, but may contain sensitive information which should not be available on a publicly available website.' )
  1373 				__( 'Debug mode is often enabled to gather more details about an error or site failure, but may contain sensitive information which should not be available on a publicly available website.' )
  1461 			),
  1374 			),
  1462 			'actions'     => sprintf(
  1375 			'actions'     => sprintf(
  1463 				'<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1376 				'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1464 				/* translators: Documentation explaining debugging in WordPress. */
  1377 				/* translators: Documentation explaining debugging in WordPress. */
  1465 				esc_url( __( 'https://wordpress.org/support/article/debugging-in-wordpress/' ) ),
  1378 				esc_url( __( 'https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/' ) ),
  1466 				__( 'Learn more about debugging in WordPress.' ),
  1379 				__( 'Learn more about debugging in WordPress.' ),
  1467 				/* translators: Accessibility text. */
  1380 				/* translators: Hidden accessibility text. */
  1468 				__( '(opens in a new tab)' )
  1381 				__( '(opens in a new tab)' )
  1469 			),
  1382 			),
  1470 			'test'        => 'is_in_debug_mode',
  1383 			'test'        => 'is_in_debug_mode',
  1471 		);
  1384 		);
  1472 
  1385 
  1473 		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
  1386 		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
  1474 			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
  1387 			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
  1475 				$result['label'] = __( 'Your site is set to log errors to a potentially public file' );
  1388 				$result['label'] = __( 'Your site is set to log errors to a potentially public file' );
  1476 
  1389 
  1477 				$result['status'] = ( 0 === strpos( ini_get( 'error_log' ), ABSPATH ) ) ? 'critical' : 'recommended';
  1390 				$result['status'] = str_starts_with( ini_get( 'error_log' ), ABSPATH ) ? 'critical' : 'recommended';
  1478 
  1391 
  1479 				$result['description'] .= sprintf(
  1392 				$result['description'] .= sprintf(
  1480 					'<p>%s</p>',
  1393 					'<p>%s</p>',
  1481 					sprintf(
  1394 					sprintf(
  1482 						/* translators: %s: WP_DEBUG_LOG */
  1395 						/* translators: %s: WP_DEBUG_LOG */
  1510 
  1423 
  1511 		return $result;
  1424 		return $result;
  1512 	}
  1425 	}
  1513 
  1426 
  1514 	/**
  1427 	/**
  1515 	 * Test if your site is serving content over HTTPS.
  1428 	 * Tests if the site is serving content over HTTPS.
  1516 	 *
  1429 	 *
  1517 	 * Many sites have varying degrees of HTTPS support, the most common of which is sites that have it
  1430 	 * Many sites have varying degrees of HTTPS support, the most common of which is sites that have it
  1518 	 * enabled, but only if you visit the right site address.
  1431 	 * enabled, but only if you visit the right site address.
  1519 	 *
  1432 	 *
  1520 	 * @since 5.2.0
  1433 	 * @since 5.2.0
  1521 	 * @since 5.7.0 Updated to rely on {@see wp_is_using_https()} and {@see wp_is_https_supported()}.
  1434 	 * @since 5.7.0 Updated to rely on {@see wp_is_using_https()} and {@see wp_is_https_supported()}.
  1522 	 *
  1435 	 *
  1523 	 * @return array The test results.
  1436 	 * @return array The test results.
  1524 	 */
  1437 	 */
  1525 	public function get_test_https_status() {
  1438 	public function get_test_https_status() {
  1526 		// Enforce fresh HTTPS detection results. This is normally invoked by using cron,
  1439 		/*
  1527 		// but for Site Health it should always rely on the latest results.
  1440 		 * Check HTTPS detection results.
  1528 		wp_update_https_detection_errors();
  1441 		 */
       
  1442 		$errors = wp_get_https_detection_errors();
  1529 
  1443 
  1530 		$default_update_url = wp_get_default_update_https_url();
  1444 		$default_update_url = wp_get_default_update_https_url();
  1531 
  1445 
  1532 		$result = array(
  1446 		$result = array(
  1533 			'label'       => __( 'Your website is using an active HTTPS connection' ),
  1447 			'label'       => __( 'Your website is using an active HTTPS connection' ),
  1542 			),
  1456 			),
  1543 			'actions'     => sprintf(
  1457 			'actions'     => sprintf(
  1544 				'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1458 				'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1545 				esc_url( $default_update_url ),
  1459 				esc_url( $default_update_url ),
  1546 				__( 'Learn more about why you should use HTTPS' ),
  1460 				__( 'Learn more about why you should use HTTPS' ),
  1547 				/* translators: Accessibility text. */
  1461 				/* translators: Hidden accessibility text. */
  1548 				__( '(opens in a new tab)' )
  1462 				__( '(opens in a new tab)' )
  1549 			),
  1463 			),
  1550 			'test'        => 'https_status',
  1464 			'test'        => 'https_status',
  1551 		);
  1465 		);
  1552 
  1466 
  1553 		if ( ! wp_is_using_https() ) {
  1467 		if ( ! wp_is_using_https() ) {
  1554 			// If the website is not using HTTPS, provide more information
  1468 			/*
  1555 			// about whether it is supported and how it can be enabled.
  1469 			 * If the website is not using HTTPS, provide more information
       
  1470 			 * about whether it is supported and how it can be enabled.
       
  1471 			 */
  1556 			$result['status'] = 'recommended';
  1472 			$result['status'] = 'recommended';
  1557 			$result['label']  = __( 'Your website does not use HTTPS' );
  1473 			$result['label']  = __( 'Your website does not use HTTPS' );
  1558 
  1474 
  1559 			if ( wp_is_site_url_using_https() ) {
  1475 			if ( wp_is_site_url_using_https() ) {
  1560 				if ( is_ssl() ) {
  1476 				if ( is_ssl() ) {
  1624 					if ( ! empty( $direct_update_url ) ) {
  1540 					if ( ! empty( $direct_update_url ) ) {
  1625 						$result['actions'] = sprintf(
  1541 						$result['actions'] = sprintf(
  1626 							'<p class="button-container"><a class="button button-primary" href="%1$s" target="_blank" rel="noopener">%2$s<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1542 							'<p class="button-container"><a class="button button-primary" href="%1$s" target="_blank" rel="noopener">%2$s<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1627 							esc_url( $direct_update_url ),
  1543 							esc_url( $direct_update_url ),
  1628 							__( 'Update your site to use HTTPS' ),
  1544 							__( 'Update your site to use HTTPS' ),
  1629 							/* translators: Accessibility text. */
  1545 							/* translators: Hidden accessibility text. */
  1630 							__( '(opens in a new tab)' )
  1546 							__( '(opens in a new tab)' )
  1631 						);
  1547 						);
  1632 					} else {
  1548 					} else {
  1633 						$result['actions'] = sprintf(
  1549 						$result['actions'] = sprintf(
  1634 							'<p class="button-container"><a class="button button-primary" href="%1$s">%2$s</a></p>',
  1550 							'<p class="button-container"><a class="button button-primary" href="%1$s">%2$s</a></p>',
  1643 				if ( $update_url !== $default_update_url ) {
  1559 				if ( $update_url !== $default_update_url ) {
  1644 					$result['description'] .= sprintf(
  1560 					$result['description'] .= sprintf(
  1645 						'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1561 						'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1646 						esc_url( $update_url ),
  1562 						esc_url( $update_url ),
  1647 						__( 'Talk to your web host about supporting HTTPS for your website.' ),
  1563 						__( 'Talk to your web host about supporting HTTPS for your website.' ),
  1648 						/* translators: Accessibility text. */
  1564 						/* translators: Hidden accessibility text. */
  1649 						__( '(opens in a new tab)' )
  1565 						__( '(opens in a new tab)' )
  1650 					);
  1566 					);
  1651 				} else {
  1567 				} else {
  1652 					$result['description'] .= sprintf(
  1568 					$result['description'] .= sprintf(
  1653 						'<p>%s</p>',
  1569 						'<p>%s</p>',
  1659 
  1575 
  1660 		return $result;
  1576 		return $result;
  1661 	}
  1577 	}
  1662 
  1578 
  1663 	/**
  1579 	/**
  1664 	 * Check if the HTTP API can handle SSL/TLS requests.
  1580 	 * Checks if the HTTP API can handle SSL/TLS requests.
  1665 	 *
  1581 	 *
  1666 	 * @since 5.2.0
  1582 	 * @since 5.2.0
  1667 	 *
  1583 	 *
  1668 	 * @return array The test results.
  1584 	 * @return array The test result.
  1669 	 */
  1585 	 */
  1670 	public function get_test_ssl_support() {
  1586 	public function get_test_ssl_support() {
  1671 		$result = array(
  1587 		$result = array(
  1672 			'label'       => '',
  1588 			'label'       => '',
  1673 			'status'      => '',
  1589 			'status'      => '',
  1702 
  1618 
  1703 		return $result;
  1619 		return $result;
  1704 	}
  1620 	}
  1705 
  1621 
  1706 	/**
  1622 	/**
  1707 	 * Test if scheduled events run as intended.
  1623 	 * Tests if scheduled events run as intended.
  1708 	 *
  1624 	 *
  1709 	 * If scheduled events are not running, this may indicate something with WP_Cron is not working
  1625 	 * If scheduled events are not running, this may indicate something with WP_Cron is not working
  1710 	 * as intended, or that there are orphaned events hanging around from older code.
  1626 	 * as intended, or that there are orphaned events hanging around from older code.
  1711 	 *
  1627 	 *
  1712 	 * @since 5.2.0
  1628 	 * @since 5.2.0
  1774 
  1690 
  1775 		return $result;
  1691 		return $result;
  1776 	}
  1692 	}
  1777 
  1693 
  1778 	/**
  1694 	/**
  1779 	 * Test if WordPress can run automated background updates.
  1695 	 * Tests if WordPress can run automated background updates.
  1780 	 *
  1696 	 *
  1781 	 * Background updates in WordPress are primarily used for minor releases and security updates.
  1697 	 * Background updates in WordPress are primarily used for minor releases and security updates.
  1782 	 * It's important to either have these working, or be aware that they are intentionally disabled
  1698 	 * It's important to either have these working, or be aware that they are intentionally disabled
  1783 	 * for whatever reason.
  1699 	 * for whatever reason.
  1784 	 *
  1700 	 *
  1804 
  1720 
  1805 		if ( ! class_exists( 'WP_Site_Health_Auto_Updates' ) ) {
  1721 		if ( ! class_exists( 'WP_Site_Health_Auto_Updates' ) ) {
  1806 			require_once ABSPATH . 'wp-admin/includes/class-wp-site-health-auto-updates.php';
  1722 			require_once ABSPATH . 'wp-admin/includes/class-wp-site-health-auto-updates.php';
  1807 		}
  1723 		}
  1808 
  1724 
  1809 		// Run the auto-update tests in a separate class,
  1725 		/*
  1810 		// as there are many considerations to be made.
  1726 		 * Run the auto-update tests in a separate class,
       
  1727 		 * as there are many considerations to be made.
       
  1728 		 */
  1811 		$automatic_updates = new WP_Site_Health_Auto_Updates();
  1729 		$automatic_updates = new WP_Site_Health_Auto_Updates();
  1812 		$tests             = $automatic_updates->run_tests();
  1730 		$tests             = $automatic_updates->run_tests();
  1813 
  1731 
  1814 		$output = '<ul>';
  1732 		$output = '<ul>';
  1815 
  1733 
  1816 		foreach ( $tests as $test ) {
  1734 		foreach ( $tests as $test ) {
       
  1735 			/* translators: Hidden accessibility text. */
  1817 			$severity_string = __( 'Passed' );
  1736 			$severity_string = __( 'Passed' );
  1818 
  1737 
  1819 			if ( 'fail' === $test->severity ) {
  1738 			if ( 'fail' === $test->severity ) {
  1820 				$result['label'] = __( 'Background updates are not working as expected' );
  1739 				$result['label'] = __( 'Background updates are not working as expected' );
  1821 
  1740 
  1822 				$result['status'] = 'critical';
  1741 				$result['status'] = 'critical';
  1823 
  1742 
       
  1743 				/* translators: Hidden accessibility text. */
  1824 				$severity_string = __( 'Error' );
  1744 				$severity_string = __( 'Error' );
  1825 			}
  1745 			}
  1826 
  1746 
  1827 			if ( 'warning' === $test->severity && 'good' === $result['status'] ) {
  1747 			if ( 'warning' === $test->severity && 'good' === $result['status'] ) {
  1828 				$result['label'] = __( 'Background updates may not be working properly' );
  1748 				$result['label'] = __( 'Background updates may not be working properly' );
  1829 
  1749 
  1830 				$result['status'] = 'recommended';
  1750 				$result['status'] = 'recommended';
  1831 
  1751 
       
  1752 				/* translators: Hidden accessibility text. */
  1832 				$severity_string = __( 'Warning' );
  1753 				$severity_string = __( 'Warning' );
  1833 			}
  1754 			}
  1834 
  1755 
  1835 			$output .= sprintf(
  1756 			$output .= sprintf(
  1836 				'<li><span class="dashicons %s"><span class="screen-reader-text">%s</span></span> %s</li>',
  1757 				'<li><span class="dashicons %s"><span class="screen-reader-text">%s</span></span> %s</li>',
  1848 
  1769 
  1849 		return $result;
  1770 		return $result;
  1850 	}
  1771 	}
  1851 
  1772 
  1852 	/**
  1773 	/**
  1853 	 * Test if plugin and theme auto-updates appear to be configured correctly.
  1774 	 * Tests if plugin and theme auto-updates appear to be configured correctly.
  1854 	 *
  1775 	 *
  1855 	 * @since 5.5.0
  1776 	 * @since 5.5.0
  1856 	 *
  1777 	 *
  1857 	 * @return array The test results.
  1778 	 * @return array The test results.
  1858 	 */
  1779 	 */
  1887 
  1808 
  1888 		return $result;
  1809 		return $result;
  1889 	}
  1810 	}
  1890 
  1811 
  1891 	/**
  1812 	/**
  1892 	 * Test if loopbacks work as expected.
  1813 	 * Tests available disk space for updates.
       
  1814 	 *
       
  1815 	 * @since 6.3.0
       
  1816 	 *
       
  1817 	 * @return array The test results.
       
  1818 	 */
       
  1819 	public function get_test_available_updates_disk_space() {
       
  1820 		$available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR . '/upgrade/' ) : false;
       
  1821 
       
  1822 		$result = array(
       
  1823 			'label'       => __( 'Disk space available to safely perform updates' ),
       
  1824 			'status'      => 'good',
       
  1825 			'badge'       => array(
       
  1826 				'label' => __( 'Security' ),
       
  1827 				'color' => 'blue',
       
  1828 			),
       
  1829 			'description' => sprintf(
       
  1830 				/* translators: %s: Available disk space in MB or GB. */
       
  1831 				'<p>' . __( '%s available disk space was detected, update routines can be performed safely.' ) . '</p>',
       
  1832 				size_format( $available_space )
       
  1833 			),
       
  1834 			'actions'     => '',
       
  1835 			'test'        => 'available_updates_disk_space',
       
  1836 		);
       
  1837 
       
  1838 		if ( false === $available_space ) {
       
  1839 			$result['description'] = __( 'Could not determine available disk space for updates.' );
       
  1840 			$result['status']      = 'recommended';
       
  1841 		} elseif ( $available_space < 20 * MB_IN_BYTES ) {
       
  1842 			$result['description'] = sprintf(
       
  1843 				/* translators: %s: Available disk space in MB or GB. */
       
  1844 				__( 'Available disk space is critically low, less than %s available. Proceed with caution, updates may fail.' ),
       
  1845 				size_format( 20 * MB_IN_BYTES )
       
  1846 			);
       
  1847 			$result['status'] = 'critical';
       
  1848 		} elseif ( $available_space < 100 * MB_IN_BYTES ) {
       
  1849 			$result['description'] = sprintf(
       
  1850 				/* translators: %s: Available disk space in MB or GB. */
       
  1851 				__( 'Available disk space is low, less than %s available.' ),
       
  1852 				size_format( 100 * MB_IN_BYTES )
       
  1853 			);
       
  1854 			$result['status'] = 'recommended';
       
  1855 		}
       
  1856 
       
  1857 		return $result;
       
  1858 	}
       
  1859 
       
  1860 	/**
       
  1861 	 * Tests if plugin and theme temporary backup directories are writable or can be created.
       
  1862 	 *
       
  1863 	 * @since 6.3.0
       
  1864 	 *
       
  1865 	 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
       
  1866 	 *
       
  1867 	 * @return array The test results.
       
  1868 	 */
       
  1869 	public function get_test_update_temp_backup_writable() {
       
  1870 		global $wp_filesystem;
       
  1871 
       
  1872 		$result = array(
       
  1873 			'label'       => __( 'Plugin and theme temporary backup directory is writable' ),
       
  1874 			'status'      => 'good',
       
  1875 			'badge'       => array(
       
  1876 				'label' => __( 'Security' ),
       
  1877 				'color' => 'blue',
       
  1878 			),
       
  1879 			'description' => sprintf(
       
  1880 				/* translators: %s: wp-content/upgrade-temp-backup */
       
  1881 				'<p>' . __( 'The %s directory used to improve the stability of plugin and theme updates is writable.' ) . '</p>',
       
  1882 				'<code>wp-content/upgrade-temp-backup</code>'
       
  1883 			),
       
  1884 			'actions'     => '',
       
  1885 			'test'        => 'update_temp_backup_writable',
       
  1886 		);
       
  1887 
       
  1888 		if ( ! function_exists( 'WP_Filesystem' ) ) {
       
  1889 			require_once ABSPATH . '/wp-admin/includes/file.php';
       
  1890 		}
       
  1891 
       
  1892 		ob_start();
       
  1893 		$credentials = request_filesystem_credentials( '' );
       
  1894 		ob_end_clean();
       
  1895 
       
  1896 		if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
       
  1897 			$result['status']      = 'recommended';
       
  1898 			$result['label']       = __( 'Could not access filesystem' );
       
  1899 			$result['description'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
       
  1900 			return $result;
       
  1901 		}
       
  1902 
       
  1903 		$wp_content = $wp_filesystem->wp_content_dir();
       
  1904 
       
  1905 		if ( ! $wp_content ) {
       
  1906 			$result['status']      = 'critical';
       
  1907 			$result['label']       = __( 'Unable to locate WordPress content directory' );
       
  1908 			$result['description'] = sprintf(
       
  1909 				/* translators: %s: wp-content */
       
  1910 				'<p>' . __( 'The %s directory cannot be located.' ) . '</p>',
       
  1911 				'<code>wp-content</code>'
       
  1912 			);
       
  1913 			return $result;
       
  1914 		}
       
  1915 
       
  1916 		$upgrade_dir_exists      = $wp_filesystem->is_dir( "$wp_content/upgrade" );
       
  1917 		$upgrade_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade" );
       
  1918 		$backup_dir_exists       = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup" );
       
  1919 		$backup_dir_is_writable  = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup" );
       
  1920 
       
  1921 		$plugins_dir_exists      = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup/plugins" );
       
  1922 		$plugins_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup/plugins" );
       
  1923 		$themes_dir_exists       = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup/themes" );
       
  1924 		$themes_dir_is_writable  = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup/themes" );
       
  1925 
       
  1926 		if ( $plugins_dir_exists && ! $plugins_dir_is_writable && $themes_dir_exists && ! $themes_dir_is_writable ) {
       
  1927 			$result['status']      = 'critical';
       
  1928 			$result['label']       = __( 'Plugin and theme temporary backup directories exist but are not writable' );
       
  1929 			$result['description'] = sprintf(
       
  1930 				/* translators: 1: wp-content/upgrade-temp-backup/plugins, 2: wp-content/upgrade-temp-backup/themes. */
       
  1931 				'<p>' . __( 'The %1$s and %2$s directories exist but are not writable. These directories are used to improve the stability of plugin updates. Please make sure the server has write permissions to these directories.' ) . '</p>',
       
  1932 				'<code>wp-content/upgrade-temp-backup/plugins</code>',
       
  1933 				'<code>wp-content/upgrade-temp-backup/themes</code>'
       
  1934 			);
       
  1935 			return $result;
       
  1936 		}
       
  1937 
       
  1938 		if ( $plugins_dir_exists && ! $plugins_dir_is_writable ) {
       
  1939 			$result['status']      = 'critical';
       
  1940 			$result['label']       = __( 'Plugin temporary backup directory exists but is not writable' );
       
  1941 			$result['description'] = sprintf(
       
  1942 				/* translators: %s: wp-content/upgrade-temp-backup/plugins */
       
  1943 				'<p>' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of plugin updates. Please make sure the server has write permissions to this directory.' ) . '</p>',
       
  1944 				'<code>wp-content/upgrade-temp-backup/plugins</code>'
       
  1945 			);
       
  1946 			return $result;
       
  1947 		}
       
  1948 
       
  1949 		if ( $themes_dir_exists && ! $themes_dir_is_writable ) {
       
  1950 			$result['status']      = 'critical';
       
  1951 			$result['label']       = __( 'Theme temporary backup directory exists but is not writable' );
       
  1952 			$result['description'] = sprintf(
       
  1953 				/* translators: %s: wp-content/upgrade-temp-backup/themes */
       
  1954 				'<p>' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of theme updates. Please make sure the server has write permissions to this directory.' ) . '</p>',
       
  1955 				'<code>wp-content/upgrade-temp-backup/themes</code>'
       
  1956 			);
       
  1957 			return $result;
       
  1958 		}
       
  1959 
       
  1960 		if ( ( ! $plugins_dir_exists || ! $themes_dir_exists ) && $backup_dir_exists && ! $backup_dir_is_writable ) {
       
  1961 			$result['status']      = 'critical';
       
  1962 			$result['label']       = __( 'The temporary backup directory exists but is not writable' );
       
  1963 			$result['description'] = sprintf(
       
  1964 				/* translators: %s: wp-content/upgrade-temp-backup */
       
  1965 				'<p>' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of plugin and theme updates. Please make sure the server has write permissions to this directory.' ) . '</p>',
       
  1966 				'<code>wp-content/upgrade-temp-backup</code>'
       
  1967 			);
       
  1968 			return $result;
       
  1969 		}
       
  1970 
       
  1971 		if ( ! $backup_dir_exists && $upgrade_dir_exists && ! $upgrade_dir_is_writable ) {
       
  1972 			$result['status']      = 'critical';
       
  1973 			$result['label']       = __( 'The upgrade directory exists but is not writable' );
       
  1974 			$result['description'] = sprintf(
       
  1975 				/* translators: %s: wp-content/upgrade */
       
  1976 				'<p>' . __( 'The %s directory exists but is not writable. This directory is used for plugin and theme updates. Please make sure the server has write permissions to this directory.' ) . '</p>',
       
  1977 				'<code>wp-content/upgrade</code>'
       
  1978 			);
       
  1979 			return $result;
       
  1980 		}
       
  1981 
       
  1982 		if ( ! $upgrade_dir_exists && ! $wp_filesystem->is_writable( $wp_content ) ) {
       
  1983 			$result['status']      = 'critical';
       
  1984 			$result['label']       = __( 'The upgrade directory cannot be created' );
       
  1985 			$result['description'] = sprintf(
       
  1986 				/* translators: 1: wp-content/upgrade, 2: wp-content. */
       
  1987 				'<p>' . __( 'The %1$s directory does not exist, and the server does not have write permissions in %2$s to create it. This directory is used for plugin and theme updates. Please make sure the server has write permissions in %2$s.' ) . '</p>',
       
  1988 				'<code>wp-content/upgrade</code>',
       
  1989 				'<code>wp-content</code>'
       
  1990 			);
       
  1991 			return $result;
       
  1992 		}
       
  1993 
       
  1994 		return $result;
       
  1995 	}
       
  1996 
       
  1997 	/**
       
  1998 	 * Tests if loopbacks work as expected.
  1893 	 *
  1999 	 *
  1894 	 * A loopback is when WordPress queries itself, for example to start a new WP_Cron instance,
  2000 	 * A loopback is when WordPress queries itself, for example to start a new WP_Cron instance,
  1895 	 * or when editing a plugin or theme. This has shown itself to be a recurring issue,
  2001 	 * or when editing a plugin or theme. This has shown itself to be a recurring issue,
  1896 	 * as code can very easily break this interaction.
  2002 	 * as code can very easily break this interaction.
  1897 	 *
  2003 	 *
  1930 
  2036 
  1931 		return $result;
  2037 		return $result;
  1932 	}
  2038 	}
  1933 
  2039 
  1934 	/**
  2040 	/**
  1935 	 * Test if HTTP requests are blocked.
  2041 	 * Tests if HTTP requests are blocked.
  1936 	 *
  2042 	 *
  1937 	 * It's possible to block all outgoing communication (with the possibility of allowing certain
  2043 	 * It's possible to block all outgoing communication (with the possibility of allowing certain
  1938 	 * hosts) via the HTTP API. This may create problems for users as many features are running as
  2044 	 * hosts) via the HTTP API. This may create problems for users as many features are running as
  1939 	 * services these days.
  2045 	 * services these days.
  1940 	 *
  2046 	 *
  2002 
  2108 
  2003 		return $result;
  2109 		return $result;
  2004 	}
  2110 	}
  2005 
  2111 
  2006 	/**
  2112 	/**
  2007 	 * Test if the REST API is accessible.
  2113 	 * Tests if the REST API is accessible.
  2008 	 *
  2114 	 *
  2009 	 * Various security measures may block the REST API from working, or it may have been disabled in general.
  2115 	 * Various security measures may block the REST API from working, or it may have been disabled in general.
  2010 	 * This is required for the new block editor to work, so we explicitly test for this.
  2116 	 * This is required for the new block editor to work, so we explicitly test for this.
  2011 	 *
  2117 	 *
  2012 	 * @since 5.2.0
  2118 	 * @since 5.2.0
  2021 				'label' => __( 'Performance' ),
  2127 				'label' => __( 'Performance' ),
  2022 				'color' => 'blue',
  2128 				'color' => 'blue',
  2023 			),
  2129 			),
  2024 			'description' => sprintf(
  2130 			'description' => sprintf(
  2025 				'<p>%s</p>',
  2131 				'<p>%s</p>',
  2026 				__( 'The REST API is one way WordPress, and other applications, communicate with the server. One example is the block editor screen, which relies on this to display, and save, your posts and pages.' )
  2132 				__( 'The REST API is one way that WordPress and other applications communicate with the server. For example, the block editor screen relies on the REST API to display and save your posts and pages.' )
  2027 			),
  2133 			),
  2028 			'actions'     => '',
  2134 			'actions'     => '',
  2029 			'test'        => 'rest_availability',
  2135 			'test'        => 'rest_availability',
  2030 		);
  2136 		);
  2031 
  2137 
  2032 		$cookies = wp_unslash( $_COOKIE );
  2138 		$cookies = wp_unslash( $_COOKIE );
  2033 		$timeout = 10;
  2139 		$timeout = 10; // 10 seconds.
  2034 		$headers = array(
  2140 		$headers = array(
  2035 			'Cache-Control' => 'no-cache',
  2141 			'Cache-Control' => 'no-cache',
  2036 			'X-WP-Nonce'    => wp_create_nonce( 'wp_rest' ),
  2142 			'X-WP-Nonce'    => wp_create_nonce( 'wp_rest' ),
  2037 		);
  2143 		);
  2038 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
  2144 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
  2059 			$result['status'] = 'critical';
  2165 			$result['status'] = 'critical';
  2060 
  2166 
  2061 			$result['label'] = __( 'The REST API encountered an error' );
  2167 			$result['label'] = __( 'The REST API encountered an error' );
  2062 
  2168 
  2063 			$result['description'] .= sprintf(
  2169 			$result['description'] .= sprintf(
  2064 				'<p>%s</p>',
  2170 				'<p>%s</p><p>%s<br>%s</p>',
       
  2171 				__( 'When testing the REST API, an error was encountered:' ),
  2065 				sprintf(
  2172 				sprintf(
  2066 					'%s<br>%s',
  2173 					// translators: %s: The REST API URL.
  2067 					__( 'The REST API request failed due to an error.' ),
  2174 					__( 'REST API Endpoint: %s' ),
  2068 					sprintf(
  2175 					$url
  2069 						/* translators: 1: The WordPress error message. 2: The WordPress error code. */
  2176 				),
  2070 						__( 'Error: %1$s (%2$s)' ),
  2177 				sprintf(
  2071 						$r->get_error_message(),
  2178 					// translators: 1: The WordPress error code. 2: The WordPress error message.
  2072 						$r->get_error_code()
  2179 					__( 'REST API Response: (%1$s) %2$s' ),
  2073 					)
  2180 					$r->get_error_code(),
       
  2181 					$r->get_error_message()
  2074 				)
  2182 				)
  2075 			);
  2183 			);
  2076 		} elseif ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
  2184 		} elseif ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
  2077 			$result['status'] = 'recommended';
  2185 			$result['status'] = 'recommended';
  2078 
  2186 
  2079 			$result['label'] = __( 'The REST API encountered an unexpected result' );
  2187 			$result['label'] = __( 'The REST API encountered an unexpected result' );
  2080 
  2188 
  2081 			$result['description'] .= sprintf(
  2189 			$result['description'] .= sprintf(
  2082 				'<p>%s</p>',
  2190 				'<p>%s</p><p>%s<br>%s</p>',
       
  2191 				__( 'When testing the REST API, an unexpected result was returned:' ),
  2083 				sprintf(
  2192 				sprintf(
  2084 					/* translators: 1: The HTTP error code. 2: The HTTP error message. */
  2193 					// translators: %s: The REST API URL.
  2085 					__( 'The REST API call gave the following unexpected result: (%1$d) %2$s.' ),
  2194 					__( 'REST API Endpoint: %s' ),
       
  2195 					$url
       
  2196 				),
       
  2197 				sprintf(
       
  2198 					// translators: 1: The WordPress error code. 2: The HTTP status code error message.
       
  2199 					__( 'REST API Response: (%1$s) %2$s' ),
  2086 					wp_remote_retrieve_response_code( $r ),
  2200 					wp_remote_retrieve_response_code( $r ),
  2087 					esc_html( wp_remote_retrieve_body( $r ) )
  2201 					wp_remote_retrieve_response_message( $r )
  2088 				)
  2202 				)
  2089 			);
  2203 			);
  2090 		} else {
  2204 		} else {
  2091 			$json = json_decode( wp_remote_retrieve_body( $r ), true );
  2205 			$json = json_decode( wp_remote_retrieve_body( $r ), true );
  2092 
  2206 
  2108 
  2222 
  2109 		return $result;
  2223 		return $result;
  2110 	}
  2224 	}
  2111 
  2225 
  2112 	/**
  2226 	/**
  2113 	 * Test if 'file_uploads' directive in PHP.ini is turned off.
  2227 	 * Tests if 'file_uploads' directive in PHP.ini is turned off.
  2114 	 *
  2228 	 *
  2115 	 * @since 5.5.0
  2229 	 * @since 5.5.0
  2116 	 *
  2230 	 *
  2117 	 * @return array The test results.
  2231 	 * @return array The test results.
  2118 	 */
  2232 	 */
  2216 				'label' => __( 'Security' ),
  2330 				'label' => __( 'Security' ),
  2217 				'color' => 'blue',
  2331 				'color' => 'blue',
  2218 			),
  2332 			),
  2219 			'description' => sprintf(
  2333 			'description' => sprintf(
  2220 				'<p>%s</p>',
  2334 				'<p>%s</p>',
  2221 				__( 'The Authorization header comes from the third-party applications you approve. Without it, those apps cannot connect to your site.' )
  2335 				__( 'The Authorization header is used by third-party applications you have approved for this site. Without this header, those apps cannot connect to your site.' )
  2222 			),
  2336 			),
  2223 			'actions'     => '',
  2337 			'actions'     => '',
  2224 			'test'        => 'authorization_header',
  2338 			'test'        => 'authorization_header',
  2225 		);
  2339 		);
  2226 
  2340 
  2230 			$result['label'] = __( 'The authorization header is invalid' );
  2344 			$result['label'] = __( 'The authorization header is invalid' );
  2231 		} else {
  2345 		} else {
  2232 			return $result;
  2346 			return $result;
  2233 		}
  2347 		}
  2234 
  2348 
  2235 		$result['status'] = 'recommended';
  2349 		$result['status']       = 'recommended';
       
  2350 		$result['description'] .= sprintf(
       
  2351 			'<p>%s</p>',
       
  2352 			__( 'If you are still seeing this warning after having tried the actions below, you may need to contact your hosting provider for further assistance.' )
       
  2353 		);
  2236 
  2354 
  2237 		if ( ! function_exists( 'got_mod_rewrite' ) ) {
  2355 		if ( ! function_exists( 'got_mod_rewrite' ) ) {
  2238 			require_once ABSPATH . 'wp-admin/includes/misc.php';
  2356 			require_once ABSPATH . 'wp-admin/includes/misc.php';
  2239 		}
  2357 		}
  2240 
  2358 
  2244 				esc_url( admin_url( 'options-permalink.php' ) ),
  2362 				esc_url( admin_url( 'options-permalink.php' ) ),
  2245 				__( 'Flush permalinks' )
  2363 				__( 'Flush permalinks' )
  2246 			);
  2364 			);
  2247 		} else {
  2365 		} else {
  2248 			$result['actions'] .= sprintf(
  2366 			$result['actions'] .= sprintf(
  2249 				'<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  2367 				'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  2250 				__( 'https://developer.wordpress.org/rest-api/frequently-asked-questions/#why-is-authentication-not-working' ),
  2368 				__( 'https://developer.wordpress.org/rest-api/frequently-asked-questions/#why-is-authentication-not-working' ),
  2251 				__( 'Learn how to configure the Authorization header.' ),
  2369 				__( 'Learn how to configure the Authorization header.' ),
  2252 				/* translators: Accessibility text. */
  2370 				/* translators: Hidden accessibility text. */
  2253 				__( '(opens in a new tab)' )
  2371 				__( '(opens in a new tab)' )
  2254 			);
  2372 			);
  2255 		}
  2373 		}
  2256 
  2374 
  2257 		return $result;
  2375 		return $result;
  2258 	}
  2376 	}
  2259 
  2377 
  2260 	/**
  2378 	/**
  2261 	 * Return a set of tests that belong to the site status page.
  2379 	 * Tests if a full page cache is available.
       
  2380 	 *
       
  2381 	 * @since 6.1.0
       
  2382 	 *
       
  2383 	 * @return array The test result.
       
  2384 	 */
       
  2385 	public function get_test_page_cache() {
       
  2386 		$description  = '<p>' . __( 'Page cache enhances the speed and performance of your site by saving and serving static pages instead of calling for a page every time a user visits.' ) . '</p>';
       
  2387 		$description .= '<p>' . __( 'Page cache is detected by looking for an active page cache plugin as well as making three requests to the homepage and looking for one or more of the following HTTP client caching response headers:' ) . '</p>';
       
  2388 		$description .= '<code>' . implode( '</code>, <code>', array_keys( $this->get_page_cache_headers() ) ) . '.</code>';
       
  2389 
       
  2390 		$result = array(
       
  2391 			'badge'       => array(
       
  2392 				'label' => __( 'Performance' ),
       
  2393 				'color' => 'blue',
       
  2394 			),
       
  2395 			'description' => wp_kses_post( $description ),
       
  2396 			'test'        => 'page_cache',
       
  2397 			'status'      => 'good',
       
  2398 			'label'       => '',
       
  2399 			'actions'     => sprintf(
       
  2400 				'<p><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
       
  2401 				__( 'https://developer.wordpress.org/advanced-administration/performance/optimization/#caching' ),
       
  2402 				__( 'Learn more about page cache' ),
       
  2403 				/* translators: Hidden accessibility text. */
       
  2404 				__( '(opens in a new tab)' )
       
  2405 			),
       
  2406 		);
       
  2407 
       
  2408 		$page_cache_detail = $this->get_page_cache_detail();
       
  2409 
       
  2410 		if ( is_wp_error( $page_cache_detail ) ) {
       
  2411 			$result['label']  = __( 'Unable to detect the presence of page cache' );
       
  2412 			$result['status'] = 'recommended';
       
  2413 			$error_info       = sprintf(
       
  2414 			/* translators: 1: Error message, 2: Error code. */
       
  2415 				__( 'Unable to detect page cache due to possible loopback request problem. Please verify that the loopback request test is passing. Error: %1$s (Code: %2$s)' ),
       
  2416 				$page_cache_detail->get_error_message(),
       
  2417 				$page_cache_detail->get_error_code()
       
  2418 			);
       
  2419 			$result['description'] = wp_kses_post( "<p>$error_info</p>" ) . $result['description'];
       
  2420 			return $result;
       
  2421 		}
       
  2422 
       
  2423 		$result['status'] = $page_cache_detail['status'];
       
  2424 
       
  2425 		switch ( $page_cache_detail['status'] ) {
       
  2426 			case 'recommended':
       
  2427 				$result['label'] = __( 'Page cache is not detected but the server response time is OK' );
       
  2428 				break;
       
  2429 			case 'good':
       
  2430 				$result['label'] = __( 'Page cache is detected and the server response time is good' );
       
  2431 				break;
       
  2432 			default:
       
  2433 				if ( empty( $page_cache_detail['headers'] ) && ! $page_cache_detail['advanced_cache_present'] ) {
       
  2434 					$result['label'] = __( 'Page cache is not detected and the server response time is slow' );
       
  2435 				} else {
       
  2436 					$result['label'] = __( 'Page cache is detected but the server response time is still slow' );
       
  2437 				}
       
  2438 		}
       
  2439 
       
  2440 		$page_cache_test_summary = array();
       
  2441 
       
  2442 		if ( empty( $page_cache_detail['response_time'] ) ) {
       
  2443 			$page_cache_test_summary[] = '<span class="dashicons dashicons-dismiss"></span> ' . __( 'Server response time could not be determined. Verify that loopback requests are working.' );
       
  2444 		} else {
       
  2445 
       
  2446 			$threshold = $this->get_good_response_time_threshold();
       
  2447 			if ( $page_cache_detail['response_time'] < $threshold ) {
       
  2448 				$page_cache_test_summary[] = '<span class="dashicons dashicons-yes-alt"></span> ' . sprintf(
       
  2449 					/* translators: 1: The response time in milliseconds, 2: The recommended threshold in milliseconds. */
       
  2450 					__( 'Median server response time was %1$s milliseconds. This is less than the recommended %2$s milliseconds threshold.' ),
       
  2451 					number_format_i18n( $page_cache_detail['response_time'] ),
       
  2452 					number_format_i18n( $threshold )
       
  2453 				);
       
  2454 			} else {
       
  2455 				$page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . sprintf(
       
  2456 					/* translators: 1: The response time in milliseconds, 2: The recommended threshold in milliseconds. */
       
  2457 					__( 'Median server response time was %1$s milliseconds. It should be less than the recommended %2$s milliseconds threshold.' ),
       
  2458 					number_format_i18n( $page_cache_detail['response_time'] ),
       
  2459 					number_format_i18n( $threshold )
       
  2460 				);
       
  2461 			}
       
  2462 
       
  2463 			if ( empty( $page_cache_detail['headers'] ) ) {
       
  2464 				$page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . __( 'No client caching response headers were detected.' );
       
  2465 			} else {
       
  2466 				$headers_summary  = '<span class="dashicons dashicons-yes-alt"></span>';
       
  2467 				$headers_summary .= ' ' . sprintf(
       
  2468 					/* translators: %d: Number of caching headers. */
       
  2469 					_n(
       
  2470 						'There was %d client caching response header detected:',
       
  2471 						'There were %d client caching response headers detected:',
       
  2472 						count( $page_cache_detail['headers'] )
       
  2473 					),
       
  2474 					count( $page_cache_detail['headers'] )
       
  2475 				);
       
  2476 				$headers_summary          .= ' <code>' . implode( '</code>, <code>', $page_cache_detail['headers'] ) . '</code>.';
       
  2477 				$page_cache_test_summary[] = $headers_summary;
       
  2478 			}
       
  2479 		}
       
  2480 
       
  2481 		if ( $page_cache_detail['advanced_cache_present'] ) {
       
  2482 			$page_cache_test_summary[] = '<span class="dashicons dashicons-yes-alt"></span> ' . __( 'A page cache plugin was detected.' );
       
  2483 		} elseif ( ! ( is_array( $page_cache_detail ) && ! empty( $page_cache_detail['headers'] ) ) ) {
       
  2484 			// Note: This message is not shown if client caching response headers were present since an external caching layer may be employed.
       
  2485 			$page_cache_test_summary[] = '<span class="dashicons dashicons-warning"></span> ' . __( 'A page cache plugin was not detected.' );
       
  2486 		}
       
  2487 
       
  2488 		$result['description'] .= '<ul><li>' . implode( '</li><li>', $page_cache_test_summary ) . '</li></ul>';
       
  2489 		return $result;
       
  2490 	}
       
  2491 
       
  2492 	/**
       
  2493 	 * Tests if the site uses persistent object cache and recommends to use it if not.
       
  2494 	 *
       
  2495 	 * @since 6.1.0
       
  2496 	 *
       
  2497 	 * @return array The test result.
       
  2498 	 */
       
  2499 	public function get_test_persistent_object_cache() {
       
  2500 		/**
       
  2501 		 * Filters the action URL for the persistent object cache health check.
       
  2502 		 *
       
  2503 		 * @since 6.1.0
       
  2504 		 *
       
  2505 		 * @param string $action_url Learn more link for persistent object cache health check.
       
  2506 		 */
       
  2507 		$action_url = apply_filters(
       
  2508 			'site_status_persistent_object_cache_url',
       
  2509 			/* translators: Localized Support reference. */
       
  2510 			__( 'https://developer.wordpress.org/advanced-administration/performance/optimization/#persistent-object-cache' )
       
  2511 		);
       
  2512 
       
  2513 		$result = array(
       
  2514 			'test'        => 'persistent_object_cache',
       
  2515 			'status'      => 'good',
       
  2516 			'badge'       => array(
       
  2517 				'label' => __( 'Performance' ),
       
  2518 				'color' => 'blue',
       
  2519 			),
       
  2520 			'label'       => __( 'A persistent object cache is being used' ),
       
  2521 			'description' => sprintf(
       
  2522 				'<p>%s</p>',
       
  2523 				__( 'A persistent object cache makes your site&#8217;s database more efficient, resulting in faster load times because WordPress can retrieve your site&#8217;s content and settings much more quickly.' )
       
  2524 			),
       
  2525 			'actions'     => sprintf(
       
  2526 				'<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
       
  2527 				esc_url( $action_url ),
       
  2528 				__( 'Learn more about persistent object caching.' ),
       
  2529 				/* translators: Hidden accessibility text. */
       
  2530 				__( '(opens in a new tab)' )
       
  2531 			),
       
  2532 		);
       
  2533 
       
  2534 		if ( wp_using_ext_object_cache() ) {
       
  2535 			return $result;
       
  2536 		}
       
  2537 
       
  2538 		if ( ! $this->should_suggest_persistent_object_cache() ) {
       
  2539 			$result['label'] = __( 'A persistent object cache is not required' );
       
  2540 
       
  2541 			return $result;
       
  2542 		}
       
  2543 
       
  2544 		$available_services = $this->available_object_cache_services();
       
  2545 
       
  2546 		$notes = __( 'Your hosting provider can tell you if a persistent object cache can be enabled on your site.' );
       
  2547 
       
  2548 		if ( ! empty( $available_services ) ) {
       
  2549 			$notes .= ' ' . sprintf(
       
  2550 				/* translators: Available object caching services. */
       
  2551 				__( 'Your host appears to support the following object caching services: %s.' ),
       
  2552 				implode( ', ', $available_services )
       
  2553 			);
       
  2554 		}
       
  2555 
       
  2556 		/**
       
  2557 		 * Filters the second paragraph of the health check's description
       
  2558 		 * when suggesting the use of a persistent object cache.
       
  2559 		 *
       
  2560 		 * Hosts may want to replace the notes to recommend their preferred object caching solution.
       
  2561 		 *
       
  2562 		 * Plugin authors may want to append notes (not replace) on why object caching is recommended for their plugin.
       
  2563 		 *
       
  2564 		 * @since 6.1.0
       
  2565 		 *
       
  2566 		 * @param string   $notes              The notes appended to the health check description.
       
  2567 		 * @param string[] $available_services The list of available persistent object cache services.
       
  2568 		 */
       
  2569 		$notes = apply_filters( 'site_status_persistent_object_cache_notes', $notes, $available_services );
       
  2570 
       
  2571 		$result['status']       = 'recommended';
       
  2572 		$result['label']        = __( 'You should use a persistent object cache' );
       
  2573 		$result['description'] .= sprintf(
       
  2574 			'<p>%s</p>',
       
  2575 			wp_kses(
       
  2576 				$notes,
       
  2577 				array(
       
  2578 					'a'      => array( 'href' => true ),
       
  2579 					'code'   => true,
       
  2580 					'em'     => true,
       
  2581 					'strong' => true,
       
  2582 				)
       
  2583 			)
       
  2584 		);
       
  2585 
       
  2586 		return $result;
       
  2587 	}
       
  2588 
       
  2589 	/**
       
  2590 	 * Calculates total amount of autoloaded data.
       
  2591 	 *
       
  2592 	 * @since 6.6.0
       
  2593 	 *
       
  2594 	 * @return int Autoloaded data in bytes.
       
  2595 	 */
       
  2596 	public function get_autoloaded_options_size() {
       
  2597 		$alloptions = wp_load_alloptions();
       
  2598 
       
  2599 		$total_length = 0;
       
  2600 
       
  2601 		foreach ( $alloptions as $option_value ) {
       
  2602 			if ( is_array( $option_value ) || is_object( $option_value ) ) {
       
  2603 				$option_value = maybe_serialize( $option_value );
       
  2604 			}
       
  2605 			$total_length += strlen( (string) $option_value );
       
  2606 		}
       
  2607 
       
  2608 		return $total_length;
       
  2609 	}
       
  2610 
       
  2611 	/**
       
  2612 	 * Tests the number of autoloaded options.
       
  2613 	 *
       
  2614 	 * @since 6.6.0
       
  2615 	 *
       
  2616 	 * @return array The test results.
       
  2617 	 */
       
  2618 	public function get_test_autoloaded_options() {
       
  2619 		$autoloaded_options_size  = $this->get_autoloaded_options_size();
       
  2620 		$autoloaded_options_count = count( wp_load_alloptions() );
       
  2621 
       
  2622 		$base_description = __( 'Autoloaded options are configuration settings for plugins and themes that are automatically loaded with every page load in WordPress. Having too many autoloaded options can slow down your site.' );
       
  2623 
       
  2624 		$result = array(
       
  2625 			'label'       => __( 'Autoloaded options are acceptable' ),
       
  2626 			'status'      => 'good',
       
  2627 			'badge'       => array(
       
  2628 				'label' => __( 'Performance' ),
       
  2629 				'color' => 'blue',
       
  2630 			),
       
  2631 			'description' => sprintf(
       
  2632 				/* translators: 1: Number of autoloaded options, 2: Autoloaded options size. */
       
  2633 				'<p>' . esc_html( $base_description ) . ' ' . __( 'Your site has %1$s autoloaded options (size: %2$s) in the options table, which is acceptable.' ) . '</p>',
       
  2634 				$autoloaded_options_count,
       
  2635 				size_format( $autoloaded_options_size )
       
  2636 			),
       
  2637 			'actions'     => '',
       
  2638 			'test'        => 'autoloaded_options',
       
  2639 		);
       
  2640 
       
  2641 		/**
       
  2642 		 * Filters max bytes threshold to trigger warning in Site Health.
       
  2643 		 *
       
  2644 		 * @since 6.6.0
       
  2645 		 *
       
  2646 		 * @param int $limit Autoloaded options threshold size. Default 800000.
       
  2647 		 */
       
  2648 		$limit = apply_filters( 'site_status_autoloaded_options_size_limit', 800000 );
       
  2649 
       
  2650 		if ( $autoloaded_options_size < $limit ) {
       
  2651 			return $result;
       
  2652 		}
       
  2653 
       
  2654 		$result['status']      = 'critical';
       
  2655 		$result['label']       = __( 'Autoloaded options could affect performance' );
       
  2656 		$result['description'] = sprintf(
       
  2657 			/* translators: 1: Number of autoloaded options, 2: Autoloaded options size. */
       
  2658 			'<p>' . esc_html( $base_description ) . ' ' . __( 'Your site has %1$s autoloaded options (size: %2$s) in the options table, which could cause your site to be slow. You can review the options being autoloaded in your database and remove any options that are no longer needed by your site.' ) . '</p>',
       
  2659 			$autoloaded_options_count,
       
  2660 			size_format( $autoloaded_options_size )
       
  2661 		);
       
  2662 
       
  2663 		/**
       
  2664 		 * Filters description to be shown on Site Health warning when threshold is met.
       
  2665 		 *
       
  2666 		 * @since 6.6.0
       
  2667 		 *
       
  2668 		 * @param string $description Description message when autoloaded options bigger than threshold.
       
  2669 		 */
       
  2670 		$result['description'] = apply_filters( 'site_status_autoloaded_options_limit_description', $result['description'] );
       
  2671 
       
  2672 		$result['actions'] = sprintf(
       
  2673 			/* translators: 1: HelpHub URL, 2: Link description. */
       
  2674 			'<p><a target="_blank" rel="noopener" href="%1$s">%2$s</a></p>',
       
  2675 			esc_url( __( 'https://developer.wordpress.org/advanced-administration/performance/optimization/#autoloaded-options' ) ),
       
  2676 			__( 'More info about optimizing autoloaded options' )
       
  2677 		);
       
  2678 
       
  2679 		/**
       
  2680 		 * Filters actionable information to tackle the problem. It can be a link to an external guide.
       
  2681 		 *
       
  2682 		 * @since 6.6.0
       
  2683 		 *
       
  2684 		 * @param string $actions Call to Action to be used to point to the right direction to solve the issue.
       
  2685 		 */
       
  2686 		$result['actions'] = apply_filters( 'site_status_autoloaded_options_action_to_perform', $result['actions'] );
       
  2687 		return $result;
       
  2688 	}
       
  2689 
       
  2690 	/**
       
  2691 	 * Returns a set of tests that belong to the site status page.
  2262 	 *
  2692 	 *
  2263 	 * Each site status test is defined here, they may be `direct` tests, that run on page load, or `async` tests
  2693 	 * Each site status test is defined here, they may be `direct` tests, that run on page load, or `async` tests
  2264 	 * which will run later down the line via JavaScript calls to improve page performance and hopefully also user
  2694 	 * which will run later down the line via JavaScript calls to improve page performance and hopefully also user
  2265 	 * experiences.
  2695 	 * experiences.
  2266 	 *
  2696 	 *
  2270 	 * @return array The list of tests to run.
  2700 	 * @return array The list of tests to run.
  2271 	 */
  2701 	 */
  2272 	public static function get_tests() {
  2702 	public static function get_tests() {
  2273 		$tests = array(
  2703 		$tests = array(
  2274 			'direct' => array(
  2704 			'direct' => array(
  2275 				'wordpress_version'         => array(
  2705 				'wordpress_version'            => array(
  2276 					'label' => __( 'WordPress Version' ),
  2706 					'label' => __( 'WordPress Version' ),
  2277 					'test'  => 'wordpress_version',
  2707 					'test'  => 'wordpress_version',
  2278 				),
  2708 				),
  2279 				'plugin_version'            => array(
  2709 				'plugin_version'               => array(
  2280 					'label' => __( 'Plugin Versions' ),
  2710 					'label' => __( 'Plugin Versions' ),
  2281 					'test'  => 'plugin_version',
  2711 					'test'  => 'plugin_version',
  2282 				),
  2712 				),
  2283 				'theme_version'             => array(
  2713 				'theme_version'                => array(
  2284 					'label' => __( 'Theme Versions' ),
  2714 					'label' => __( 'Theme Versions' ),
  2285 					'test'  => 'theme_version',
  2715 					'test'  => 'theme_version',
  2286 				),
  2716 				),
  2287 				'php_version'               => array(
  2717 				'php_version'                  => array(
  2288 					'label' => __( 'PHP Version' ),
  2718 					'label' => __( 'PHP Version' ),
  2289 					'test'  => 'php_version',
  2719 					'test'  => 'php_version',
  2290 				),
  2720 				),
  2291 				'php_extensions'            => array(
  2721 				'php_extensions'               => array(
  2292 					'label' => __( 'PHP Extensions' ),
  2722 					'label' => __( 'PHP Extensions' ),
  2293 					'test'  => 'php_extensions',
  2723 					'test'  => 'php_extensions',
  2294 				),
  2724 				),
  2295 				'php_default_timezone'      => array(
  2725 				'php_default_timezone'         => array(
  2296 					'label' => __( 'PHP Default Timezone' ),
  2726 					'label' => __( 'PHP Default Timezone' ),
  2297 					'test'  => 'php_default_timezone',
  2727 					'test'  => 'php_default_timezone',
  2298 				),
  2728 				),
  2299 				'php_sessions'              => array(
  2729 				'php_sessions'                 => array(
  2300 					'label' => __( 'PHP Sessions' ),
  2730 					'label' => __( 'PHP Sessions' ),
  2301 					'test'  => 'php_sessions',
  2731 					'test'  => 'php_sessions',
  2302 				),
  2732 				),
  2303 				'sql_server'                => array(
  2733 				'sql_server'                   => array(
  2304 					'label' => __( 'Database Server version' ),
  2734 					'label' => __( 'Database Server version' ),
  2305 					'test'  => 'sql_server',
  2735 					'test'  => 'sql_server',
  2306 				),
  2736 				),
  2307 				'utf8mb4_support'           => array(
  2737 				'ssl_support'                  => array(
  2308 					'label' => __( 'MySQL utf8mb4 support' ),
       
  2309 					'test'  => 'utf8mb4_support',
       
  2310 				),
       
  2311 				'ssl_support'               => array(
       
  2312 					'label' => __( 'Secure communication' ),
  2738 					'label' => __( 'Secure communication' ),
  2313 					'test'  => 'ssl_support',
  2739 					'test'  => 'ssl_support',
  2314 				),
  2740 				),
  2315 				'scheduled_events'          => array(
  2741 				'scheduled_events'             => array(
  2316 					'label' => __( 'Scheduled events' ),
  2742 					'label' => __( 'Scheduled events' ),
  2317 					'test'  => 'scheduled_events',
  2743 					'test'  => 'scheduled_events',
  2318 				),
  2744 				),
  2319 				'http_requests'             => array(
  2745 				'http_requests'                => array(
  2320 					'label' => __( 'HTTP Requests' ),
  2746 					'label' => __( 'HTTP Requests' ),
  2321 					'test'  => 'http_requests',
  2747 					'test'  => 'http_requests',
  2322 				),
  2748 				),
  2323 				'rest_availability'         => array(
  2749 				'rest_availability'            => array(
  2324 					'label'     => __( 'REST API availability' ),
  2750 					'label'     => __( 'REST API availability' ),
  2325 					'test'      => 'rest_availability',
  2751 					'test'      => 'rest_availability',
  2326 					'skip_cron' => true,
  2752 					'skip_cron' => true,
  2327 				),
  2753 				),
  2328 				'debug_enabled'             => array(
  2754 				'debug_enabled'                => array(
  2329 					'label' => __( 'Debugging enabled' ),
  2755 					'label' => __( 'Debugging enabled' ),
  2330 					'test'  => 'is_in_debug_mode',
  2756 					'test'  => 'is_in_debug_mode',
  2331 				),
  2757 				),
  2332 				'file_uploads'              => array(
  2758 				'file_uploads'                 => array(
  2333 					'label' => __( 'File uploads' ),
  2759 					'label' => __( 'File uploads' ),
  2334 					'test'  => 'file_uploads',
  2760 					'test'  => 'file_uploads',
  2335 				),
  2761 				),
  2336 				'plugin_theme_auto_updates' => array(
  2762 				'plugin_theme_auto_updates'    => array(
  2337 					'label' => __( 'Plugin and theme auto-updates' ),
  2763 					'label' => __( 'Plugin and theme auto-updates' ),
  2338 					'test'  => 'plugin_theme_auto_updates',
  2764 					'test'  => 'plugin_theme_auto_updates',
       
  2765 				),
       
  2766 				'update_temp_backup_writable'  => array(
       
  2767 					'label' => __( 'Plugin and theme temporary backup directory access' ),
       
  2768 					'test'  => 'update_temp_backup_writable',
       
  2769 				),
       
  2770 				'available_updates_disk_space' => array(
       
  2771 					'label' => __( 'Available disk space' ),
       
  2772 					'test'  => 'available_updates_disk_space',
       
  2773 				),
       
  2774 				'autoloaded_options'           => array(
       
  2775 					'label' => __( 'Autoloaded options' ),
       
  2776 					'test'  => 'autoloaded_options',
  2339 				),
  2777 				),
  2340 			),
  2778 			),
  2341 			'async'  => array(
  2779 			'async'  => array(
  2342 				'dotorg_communication' => array(
  2780 				'dotorg_communication' => array(
  2343 					'label'             => __( 'Communication with WordPress.org' ),
  2781 					'label'             => __( 'Communication with WordPress.org' ),
  2375 				'headers'   => array( 'Authorization' => 'Basic ' . base64_encode( 'user:pwd' ) ),
  2813 				'headers'   => array( 'Authorization' => 'Basic ' . base64_encode( 'user:pwd' ) ),
  2376 				'skip_cron' => true,
  2814 				'skip_cron' => true,
  2377 			);
  2815 			);
  2378 		}
  2816 		}
  2379 
  2817 
       
  2818 		// Only check for caches in production environments.
       
  2819 		if ( 'production' === wp_get_environment_type() ) {
       
  2820 			$tests['async']['page_cache'] = array(
       
  2821 				'label'             => __( 'Page cache' ),
       
  2822 				'test'              => rest_url( 'wp-site-health/v1/tests/page-cache' ),
       
  2823 				'has_rest'          => true,
       
  2824 				'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_page_cache' ),
       
  2825 			);
       
  2826 
       
  2827 			$tests['direct']['persistent_object_cache'] = array(
       
  2828 				'label' => __( 'Persistent object cache' ),
       
  2829 				'test'  => 'persistent_object_cache',
       
  2830 			);
       
  2831 		}
       
  2832 
  2380 		/**
  2833 		/**
  2381 		 * Add or modify which site status tests are run on a site.
  2834 		 * Filters which site status tests are run on a site.
  2382 		 *
  2835 		 *
  2383 		 * The site health is determined by a set of tests based on best practices from
  2836 		 * The site health is determined by a set of tests based on best practices from
  2384 		 * both the WordPress Hosting Team and web standards in general.
  2837 		 * both the WordPress Hosting Team and web standards in general.
  2385 		 *
  2838 		 *
  2386 		 * Some sites may not have the same requirements, for example the automatic update
  2839 		 * Some sites may not have the same requirements, for example the automatic update
  2442 
  2895 
  2443 		return $tests;
  2896 		return $tests;
  2444 	}
  2897 	}
  2445 
  2898 
  2446 	/**
  2899 	/**
  2447 	 * Add a class to the body HTML tag.
  2900 	 * Adds a class to the body HTML tag.
  2448 	 *
  2901 	 *
  2449 	 * Filters the body class string for admin pages and adds our own class for easier styling.
  2902 	 * Filters the body class string for admin pages and adds our own class for easier styling.
  2450 	 *
  2903 	 *
  2451 	 * @since 5.2.0
  2904 	 * @since 5.2.0
  2452 	 *
  2905 	 *
  2463 
  2916 
  2464 		return $body_class;
  2917 		return $body_class;
  2465 	}
  2918 	}
  2466 
  2919 
  2467 	/**
  2920 	/**
  2468 	 * Initiate the WP_Cron schedule test cases.
  2921 	 * Initiates the WP_Cron schedule test cases.
  2469 	 *
  2922 	 *
  2470 	 * @since 5.2.0
  2923 	 * @since 5.2.0
  2471 	 */
  2924 	 */
  2472 	private function wp_schedule_test_init() {
  2925 	private function wp_schedule_test_init() {
  2473 		$this->schedules = wp_get_schedules();
  2926 		$this->schedules = wp_get_schedules();
  2474 		$this->get_cron_tasks();
  2927 		$this->get_cron_tasks();
  2475 	}
  2928 	}
  2476 
  2929 
  2477 	/**
  2930 	/**
  2478 	 * Populate our list of cron events and store them to a class-wide variable.
  2931 	 * Populates the list of cron events and store them to a class-wide variable.
  2479 	 *
  2932 	 *
  2480 	 * @since 5.2.0
  2933 	 * @since 5.2.0
  2481 	 */
  2934 	 */
  2482 	private function get_cron_tasks() {
  2935 	private function get_cron_tasks() {
  2483 		$cron_tasks = _get_cron_array();
  2936 		$cron_tasks = _get_cron_array();
  2506 			}
  2959 			}
  2507 		}
  2960 		}
  2508 	}
  2961 	}
  2509 
  2962 
  2510 	/**
  2963 	/**
  2511 	 * Check if any scheduled tasks have been missed.
  2964 	 * Checks if any scheduled tasks have been missed.
  2512 	 *
  2965 	 *
  2513 	 * Returns a boolean value of `true` if a scheduled task has been missed and ends processing.
  2966 	 * Returns a boolean value of `true` if a scheduled task has been missed and ends processing.
  2514 	 *
  2967 	 *
  2515 	 * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value.
  2968 	 * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value.
  2516 	 *
  2969 	 *
  2532 
  2985 
  2533 		return false;
  2986 		return false;
  2534 	}
  2987 	}
  2535 
  2988 
  2536 	/**
  2989 	/**
  2537 	 * Check if any scheduled tasks are late.
  2990 	 * Checks if any scheduled tasks are late.
  2538 	 *
  2991 	 *
  2539 	 * Returns a boolean value of `true` if a scheduled task is late and ends processing.
  2992 	 * Returns a boolean value of `true` if a scheduled task is late and ends processing.
  2540 	 *
  2993 	 *
  2541 	 * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value.
  2994 	 * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value.
  2542 	 *
  2995 	 *
  2562 
  3015 
  2563 		return false;
  3016 		return false;
  2564 	}
  3017 	}
  2565 
  3018 
  2566 	/**
  3019 	/**
  2567 	 * Check for potential issues with plugin and theme auto-updates.
  3020 	 * Checks for potential issues with plugin and theme auto-updates.
  2568 	 *
  3021 	 *
  2569 	 * Though there is no way to 100% determine if plugin and theme auto-updates are configured
  3022 	 * Though there is no way to 100% determine if plugin and theme auto-updates are configured
  2570 	 * correctly, a few educated guesses could be made to flag any conditions that would
  3023 	 * correctly, a few educated guesses could be made to flag any conditions that would
  2571 	 * potentially cause unexpected behaviors.
  3024 	 * potentially cause unexpected behaviors.
  2572 	 *
  3025 	 *
  2646 			'message' => __( 'There appear to be no issues with plugin and theme auto-updates.' ),
  3099 			'message' => __( 'There appear to be no issues with plugin and theme auto-updates.' ),
  2647 		);
  3100 		);
  2648 	}
  3101 	}
  2649 
  3102 
  2650 	/**
  3103 	/**
  2651 	 * Run a loopback test on our site.
  3104 	 * Runs a loopback test on the site.
  2652 	 *
  3105 	 *
  2653 	 * Loopbacks are what WordPress uses to communicate with itself to start up WP_Cron, scheduled posts,
  3106 	 * Loopbacks are what WordPress uses to communicate with itself to start up WP_Cron, scheduled posts,
  2654 	 * make sure plugin or theme edits don't cause site failures and similar.
  3107 	 * make sure plugin or theme edits don't cause site failures and similar.
  2655 	 *
  3108 	 *
  2656 	 * @since 5.2.0
  3109 	 * @since 5.2.0
  2658 	 * @return object The test results.
  3111 	 * @return object The test results.
  2659 	 */
  3112 	 */
  2660 	public function can_perform_loopback() {
  3113 	public function can_perform_loopback() {
  2661 		$body    = array( 'site-health' => 'loopback-test' );
  3114 		$body    = array( 'site-health' => 'loopback-test' );
  2662 		$cookies = wp_unslash( $_COOKIE );
  3115 		$cookies = wp_unslash( $_COOKIE );
  2663 		$timeout = 10;
  3116 		$timeout = 10; // 10 seconds.
  2664 		$headers = array(
  3117 		$headers = array(
  2665 			'Cache-Control' => 'no-cache',
  3118 			'Cache-Control' => 'no-cache',
  2666 		);
  3119 		);
  2667 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
  3120 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
  2668 		$sslverify = apply_filters( 'https_local_ssl_verify', false );
  3121 		$sslverify = apply_filters( 'https_local_ssl_verify', false );
  2718 			'message' => __( 'The loopback request to your site completed successfully.' ),
  3171 			'message' => __( 'The loopback request to your site completed successfully.' ),
  2719 		);
  3172 		);
  2720 	}
  3173 	}
  2721 
  3174 
  2722 	/**
  3175 	/**
  2723 	 * Create a weekly cron event, if one does not already exist.
  3176 	 * Creates a weekly cron event, if one does not already exist.
  2724 	 *
  3177 	 *
  2725 	 * @since 5.4.0
  3178 	 * @since 5.4.0
  2726 	 */
  3179 	 */
  2727 	public function maybe_create_scheduled_event() {
  3180 	public function maybe_create_scheduled_event() {
  2728 		if ( ! wp_next_scheduled( 'wp_site_health_scheduled_check' ) && ! wp_installing() ) {
  3181 		if ( ! wp_next_scheduled( 'wp_site_health_scheduled_check' ) && ! wp_installing() ) {
  2729 			wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', 'wp_site_health_scheduled_check' );
  3182 			wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', 'wp_site_health_scheduled_check' );
  2730 		}
  3183 		}
  2731 	}
  3184 	}
  2732 
  3185 
  2733 	/**
  3186 	/**
  2734 	 * Run our scheduled event to check and update the latest site health status for the website.
  3187 	 * Runs the scheduled event to check and update the latest site health status for the website.
  2735 	 *
  3188 	 *
  2736 	 * @since 5.4.0
  3189 	 * @since 5.4.0
  2737 	 */
  3190 	 */
  2738 	public function wp_cron_scheduled_check() {
  3191 	public function wp_cron_scheduled_check() {
  2739 		// Bootstrap wp-admin, as WP_Cron doesn't do this for us.
  3192 		// Bootstrap wp-admin, as WP_Cron doesn't do this for us.
  2828 			}
  3281 			}
  2829 		}
  3282 		}
  2830 
  3283 
  2831 		foreach ( $results as $result ) {
  3284 		foreach ( $results as $result ) {
  2832 			if ( 'critical' === $result['status'] ) {
  3285 			if ( 'critical' === $result['status'] ) {
  2833 				$site_status['critical']++;
  3286 				++$site_status['critical'];
  2834 			} elseif ( 'recommended' === $result['status'] ) {
  3287 			} elseif ( 'recommended' === $result['status'] ) {
  2835 				$site_status['recommended']++;
  3288 				++$site_status['recommended'];
  2836 			} else {
  3289 			} else {
  2837 				$site_status['good']++;
  3290 				++$site_status['good'];
  2838 			}
  3291 			}
  2839 		}
  3292 		}
  2840 
  3293 
  2841 		set_transient( 'health-check-site-status-result', wp_json_encode( $site_status ) );
  3294 		set_transient( 'health-check-site-status-result', wp_json_encode( $site_status ) );
  2842 	}
  3295 	}
  2850 	 */
  3303 	 */
  2851 	public function is_development_environment() {
  3304 	public function is_development_environment() {
  2852 		return in_array( wp_get_environment_type(), array( 'development', 'local' ), true );
  3305 		return in_array( wp_get_environment_type(), array( 'development', 'local' ), true );
  2853 	}
  3306 	}
  2854 
  3307 
       
  3308 	/**
       
  3309 	 * Returns a list of headers and its verification callback to verify if page cache is enabled or not.
       
  3310 	 *
       
  3311 	 * Note: key is header name and value could be callable function to verify header value.
       
  3312 	 * Empty value mean existence of header detect page cache is enabled.
       
  3313 	 *
       
  3314 	 * @since 6.1.0
       
  3315 	 *
       
  3316 	 * @return array List of client caching headers and their (optional) verification callbacks.
       
  3317 	 */
       
  3318 	public function get_page_cache_headers() {
       
  3319 
       
  3320 		$cache_hit_callback = static function ( $header_value ) {
       
  3321 			return str_contains( strtolower( $header_value ), 'hit' );
       
  3322 		};
       
  3323 
       
  3324 		$cache_headers = array(
       
  3325 			'cache-control'          => static function ( $header_value ) {
       
  3326 				return (bool) preg_match( '/max-age=[1-9]/', $header_value );
       
  3327 			},
       
  3328 			'expires'                => static function ( $header_value ) {
       
  3329 				return strtotime( $header_value ) > time();
       
  3330 			},
       
  3331 			'age'                    => static function ( $header_value ) {
       
  3332 				return is_numeric( $header_value ) && $header_value > 0;
       
  3333 			},
       
  3334 			'last-modified'          => '',
       
  3335 			'etag'                   => '',
       
  3336 			'x-cache-enabled'        => static function ( $header_value ) {
       
  3337 				return 'true' === strtolower( $header_value );
       
  3338 			},
       
  3339 			'x-cache-disabled'       => static function ( $header_value ) {
       
  3340 				return ( 'on' !== strtolower( $header_value ) );
       
  3341 			},
       
  3342 			'x-srcache-store-status' => $cache_hit_callback,
       
  3343 			'x-srcache-fetch-status' => $cache_hit_callback,
       
  3344 		);
       
  3345 
       
  3346 		/**
       
  3347 		 * Filters the list of cache headers supported by core.
       
  3348 		 *
       
  3349 		 * @since 6.1.0
       
  3350 		 *
       
  3351 		 * @param array $cache_headers Array of supported cache headers.
       
  3352 		 */
       
  3353 		return apply_filters( 'site_status_page_cache_supported_cache_headers', $cache_headers );
       
  3354 	}
       
  3355 
       
  3356 	/**
       
  3357 	 * Checks if site has page cache enabled or not.
       
  3358 	 *
       
  3359 	 * @since 6.1.0
       
  3360 	 *
       
  3361 	 * @return WP_Error|array {
       
  3362 	 *     Page cache detection details or else error information.
       
  3363 	 *
       
  3364 	 *     @type bool    $advanced_cache_present        Whether a page cache plugin is present.
       
  3365 	 *     @type array[] $page_caching_response_headers Sets of client caching headers for the responses.
       
  3366 	 *     @type float[] $response_timing               Response timings.
       
  3367 	 * }
       
  3368 	 */
       
  3369 	private function check_for_page_caching() {
       
  3370 
       
  3371 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
       
  3372 		$sslverify = apply_filters( 'https_local_ssl_verify', false );
       
  3373 
       
  3374 		$headers = array();
       
  3375 
       
  3376 		/*
       
  3377 		 * Include basic auth in loopback requests. Note that this will only pass along basic auth when user is
       
  3378 		 * initiating the test. If a site requires basic auth, the test will fail when it runs in WP Cron as part of
       
  3379 		 * wp_site_health_scheduled_check. This logic is copied from WP_Site_Health::can_perform_loopback().
       
  3380 		 */
       
  3381 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
       
  3382 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
       
  3383 		}
       
  3384 
       
  3385 		$caching_headers               = $this->get_page_cache_headers();
       
  3386 		$page_caching_response_headers = array();
       
  3387 		$response_timing               = array();
       
  3388 
       
  3389 		for ( $i = 1; $i <= 3; $i++ ) {
       
  3390 			$start_time    = microtime( true );
       
  3391 			$http_response = wp_remote_get( home_url( '/' ), compact( 'sslverify', 'headers' ) );
       
  3392 			$end_time      = microtime( true );
       
  3393 
       
  3394 			if ( is_wp_error( $http_response ) ) {
       
  3395 				return $http_response;
       
  3396 			}
       
  3397 			if ( wp_remote_retrieve_response_code( $http_response ) !== 200 ) {
       
  3398 				return new WP_Error(
       
  3399 					'http_' . wp_remote_retrieve_response_code( $http_response ),
       
  3400 					wp_remote_retrieve_response_message( $http_response )
       
  3401 				);
       
  3402 			}
       
  3403 
       
  3404 			$response_headers = array();
       
  3405 
       
  3406 			foreach ( $caching_headers as $header => $callback ) {
       
  3407 				$header_values = wp_remote_retrieve_header( $http_response, $header );
       
  3408 				if ( empty( $header_values ) ) {
       
  3409 					continue;
       
  3410 				}
       
  3411 				$header_values = (array) $header_values;
       
  3412 				if ( empty( $callback ) || ( is_callable( $callback ) && count( array_filter( $header_values, $callback ) ) > 0 ) ) {
       
  3413 					$response_headers[ $header ] = $header_values;
       
  3414 				}
       
  3415 			}
       
  3416 
       
  3417 			$page_caching_response_headers[] = $response_headers;
       
  3418 			$response_timing[]               = ( $end_time - $start_time ) * 1000;
       
  3419 		}
       
  3420 
       
  3421 		return array(
       
  3422 			'advanced_cache_present'        => (
       
  3423 				file_exists( WP_CONTENT_DIR . '/advanced-cache.php' )
       
  3424 				&&
       
  3425 				( defined( 'WP_CACHE' ) && WP_CACHE )
       
  3426 				&&
       
  3427 				/** This filter is documented in wp-settings.php */
       
  3428 				apply_filters( 'enable_loading_advanced_cache_dropin', true )
       
  3429 			),
       
  3430 			'page_caching_response_headers' => $page_caching_response_headers,
       
  3431 			'response_timing'               => $response_timing,
       
  3432 		);
       
  3433 	}
       
  3434 
       
  3435 	/**
       
  3436 	 * Gets page cache details.
       
  3437 	 *
       
  3438 	 * @since 6.1.0
       
  3439 	 *
       
  3440 	 * @return WP_Error|array {
       
  3441 	 *     Page cache detail or else a WP_Error if unable to determine.
       
  3442 	 *
       
  3443 	 *     @type string   $status                 Page cache status. Good, Recommended or Critical.
       
  3444 	 *     @type bool     $advanced_cache_present Whether page cache plugin is available or not.
       
  3445 	 *     @type string[] $headers                Client caching response headers detected.
       
  3446 	 *     @type float    $response_time          Response time of site.
       
  3447 	 * }
       
  3448 	 */
       
  3449 	private function get_page_cache_detail() {
       
  3450 		$page_cache_detail = $this->check_for_page_caching();
       
  3451 		if ( is_wp_error( $page_cache_detail ) ) {
       
  3452 			return $page_cache_detail;
       
  3453 		}
       
  3454 
       
  3455 		// Use the median server response time.
       
  3456 		$response_timings = $page_cache_detail['response_timing'];
       
  3457 		rsort( $response_timings );
       
  3458 		$page_speed = $response_timings[ floor( count( $response_timings ) / 2 ) ];
       
  3459 
       
  3460 		// Obtain unique set of all client caching response headers.
       
  3461 		$headers = array();
       
  3462 		foreach ( $page_cache_detail['page_caching_response_headers'] as $page_caching_response_headers ) {
       
  3463 			$headers = array_merge( $headers, array_keys( $page_caching_response_headers ) );
       
  3464 		}
       
  3465 		$headers = array_unique( $headers );
       
  3466 
       
  3467 		// Page cache is detected if there are response headers or a page cache plugin is present.
       
  3468 		$has_page_caching = ( count( $headers ) > 0 || $page_cache_detail['advanced_cache_present'] );
       
  3469 
       
  3470 		if ( $page_speed && $page_speed < $this->get_good_response_time_threshold() ) {
       
  3471 			$result = $has_page_caching ? 'good' : 'recommended';
       
  3472 		} else {
       
  3473 			$result = 'critical';
       
  3474 		}
       
  3475 
       
  3476 		return array(
       
  3477 			'status'                 => $result,
       
  3478 			'advanced_cache_present' => $page_cache_detail['advanced_cache_present'],
       
  3479 			'headers'                => $headers,
       
  3480 			'response_time'          => $page_speed,
       
  3481 		);
       
  3482 	}
       
  3483 
       
  3484 	/**
       
  3485 	 * Gets the threshold below which a response time is considered good.
       
  3486 	 *
       
  3487 	 * @since 6.1.0
       
  3488 	 *
       
  3489 	 * @return int Threshold in milliseconds.
       
  3490 	 */
       
  3491 	private function get_good_response_time_threshold() {
       
  3492 		/**
       
  3493 		 * Filters the threshold below which a response time is considered good.
       
  3494 		 *
       
  3495 		 * The default is based on https://web.dev/time-to-first-byte/.
       
  3496 		 *
       
  3497 		 * @param int $threshold Threshold in milliseconds. Default 600.
       
  3498 		 *
       
  3499 		 * @since 6.1.0
       
  3500 		 */
       
  3501 		return (int) apply_filters( 'site_status_good_response_time_threshold', 600 );
       
  3502 	}
       
  3503 
       
  3504 	/**
       
  3505 	 * Determines whether to suggest using a persistent object cache.
       
  3506 	 *
       
  3507 	 * @since 6.1.0
       
  3508 	 *
       
  3509 	 * @global wpdb $wpdb WordPress database abstraction object.
       
  3510 	 *
       
  3511 	 * @return bool Whether to suggest using a persistent object cache.
       
  3512 	 */
       
  3513 	public function should_suggest_persistent_object_cache() {
       
  3514 		global $wpdb;
       
  3515 
       
  3516 		/**
       
  3517 		 * Filters whether to suggest use of a persistent object cache and bypass default threshold checks.
       
  3518 		 *
       
  3519 		 * Using this filter allows to override the default logic, effectively short-circuiting the method.
       
  3520 		 *
       
  3521 		 * @since 6.1.0
       
  3522 		 *
       
  3523 		 * @param bool|null $suggest Boolean to short-circuit, for whether to suggest using a persistent object cache.
       
  3524 		 *                           Default null.
       
  3525 		 */
       
  3526 		$short_circuit = apply_filters( 'site_status_should_suggest_persistent_object_cache', null );
       
  3527 		if ( is_bool( $short_circuit ) ) {
       
  3528 			return $short_circuit;
       
  3529 		}
       
  3530 
       
  3531 		if ( is_multisite() ) {
       
  3532 			return true;
       
  3533 		}
       
  3534 
       
  3535 		/**
       
  3536 		 * Filters the thresholds used to determine whether to suggest the use of a persistent object cache.
       
  3537 		 *
       
  3538 		 * @since 6.1.0
       
  3539 		 *
       
  3540 		 * @param int[] $thresholds The list of threshold numbers keyed by threshold name.
       
  3541 		 */
       
  3542 		$thresholds = apply_filters(
       
  3543 			'site_status_persistent_object_cache_thresholds',
       
  3544 			array(
       
  3545 				'alloptions_count' => 500,
       
  3546 				'alloptions_bytes' => 100000,
       
  3547 				'comments_count'   => 1000,
       
  3548 				'options_count'    => 1000,
       
  3549 				'posts_count'      => 1000,
       
  3550 				'terms_count'      => 1000,
       
  3551 				'users_count'      => 1000,
       
  3552 			)
       
  3553 		);
       
  3554 
       
  3555 		$alloptions = wp_load_alloptions();
       
  3556 
       
  3557 		if ( $thresholds['alloptions_count'] < count( $alloptions ) ) {
       
  3558 			return true;
       
  3559 		}
       
  3560 
       
  3561 		if ( $thresholds['alloptions_bytes'] < strlen( serialize( $alloptions ) ) ) {
       
  3562 			return true;
       
  3563 		}
       
  3564 
       
  3565 		$table_names = implode( "','", array( $wpdb->comments, $wpdb->options, $wpdb->posts, $wpdb->terms, $wpdb->users ) );
       
  3566 
       
  3567 		// With InnoDB the `TABLE_ROWS` are estimates, which are accurate enough and faster to retrieve than individual `COUNT()` queries.
       
  3568 		$results = $wpdb->get_results(
       
  3569 			$wpdb->prepare(
       
  3570 				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- This query cannot use interpolation.
       
  3571 				"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;",
       
  3572 				DB_NAME
       
  3573 			),
       
  3574 			OBJECT_K
       
  3575 		);
       
  3576 
       
  3577 		$threshold_map = array(
       
  3578 			'comments_count' => $wpdb->comments,
       
  3579 			'options_count'  => $wpdb->options,
       
  3580 			'posts_count'    => $wpdb->posts,
       
  3581 			'terms_count'    => $wpdb->terms,
       
  3582 			'users_count'    => $wpdb->users,
       
  3583 		);
       
  3584 
       
  3585 		foreach ( $threshold_map as $threshold => $table ) {
       
  3586 			if ( $thresholds[ $threshold ] <= $results[ $table ]->rows ) {
       
  3587 				return true;
       
  3588 			}
       
  3589 		}
       
  3590 
       
  3591 		return false;
       
  3592 	}
       
  3593 
       
  3594 	/**
       
  3595 	 * Returns a list of available persistent object cache services.
       
  3596 	 *
       
  3597 	 * @since 6.1.0
       
  3598 	 *
       
  3599 	 * @return string[] The list of available persistent object cache services.
       
  3600 	 */
       
  3601 	private function available_object_cache_services() {
       
  3602 		$extensions = array_map(
       
  3603 			'extension_loaded',
       
  3604 			array(
       
  3605 				'APCu'      => 'apcu',
       
  3606 				'Redis'     => 'redis',
       
  3607 				'Relay'     => 'relay',
       
  3608 				'Memcache'  => 'memcache',
       
  3609 				'Memcached' => 'memcached',
       
  3610 			)
       
  3611 		);
       
  3612 
       
  3613 		$services = array_keys( array_filter( $extensions ) );
       
  3614 
       
  3615 		/**
       
  3616 		 * Filters the persistent object cache services available to the user.
       
  3617 		 *
       
  3618 		 * This can be useful to hide or add services not included in the defaults.
       
  3619 		 *
       
  3620 		 * @since 6.1.0
       
  3621 		 *
       
  3622 		 * @param string[] $services The list of available persistent object cache services.
       
  3623 		 */
       
  3624 		return apply_filters( 'site_status_available_object_cache_services', $services );
       
  3625 	}
  2855 }
  3626 }