wp/wp-admin/includes/class-wp-site-health.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
     6  * @subpackage Site_Health
     6  * @subpackage Site_Health
     7  * @since 5.2.0
     7  * @since 5.2.0
     8  */
     8  */
     9 
     9 
    10 class WP_Site_Health {
    10 class WP_Site_Health {
       
    11 	private static $instance = null;
       
    12 
    11 	private $mysql_min_version_check;
    13 	private $mysql_min_version_check;
    12 	private $mysql_rec_version_check;
    14 	private $mysql_rec_version_check;
    13 
    15 
    14 	public  $is_mariadb                          = false;
    16 	public $is_mariadb                           = false;
    15 	private $mysql_server_version                = '';
    17 	private $mysql_server_version                = '';
    16 	private $health_check_mysql_required_version = '5.5';
    18 	private $health_check_mysql_required_version = '5.5';
    17 	private $health_check_mysql_rec_version      = '';
    19 	private $health_check_mysql_rec_version      = '';
    18 
    20 
       
    21 	public $php_memory_limit;
       
    22 
    19 	public $schedules;
    23 	public $schedules;
    20 	public $crons;
    24 	public $crons;
    21 	public $last_missed_cron = null;
    25 	public $last_missed_cron     = null;
       
    26 	public $last_late_cron       = null;
       
    27 	private $timeout_missed_cron = null;
       
    28 	private $timeout_late_cron   = null;
    22 
    29 
    23 	/**
    30 	/**
    24 	 * WP_Site_Health constructor.
    31 	 * WP_Site_Health constructor.
    25 	 *
    32 	 *
    26 	 * @since 5.2.0
    33 	 * @since 5.2.0
    27 	 */
    34 	 */
    28 	public function __construct() {
    35 	public function __construct() {
    29 		$this->prepare_sql_data();
    36 		$this->maybe_create_scheduled_event();
       
    37 
       
    38 		// Save memory limit before it's affected by wp_raise_memory_limit( 'admin' ).
       
    39 		$this->php_memory_limit = ini_get( 'memory_limit' );
       
    40 
       
    41 		$this->timeout_late_cron   = 0;
       
    42 		$this->timeout_missed_cron = - 5 * MINUTE_IN_SECONDS;
       
    43 
       
    44 		if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
       
    45 			$this->timeout_late_cron   = - 15 * MINUTE_IN_SECONDS;
       
    46 			$this->timeout_missed_cron = - 1 * HOUR_IN_SECONDS;
       
    47 		}
    30 
    48 
    31 		add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
    49 		add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
    32 
    50 
    33 		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
    51 		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
       
    52 		add_action( 'wp_site_health_scheduled_check', array( $this, 'wp_cron_scheduled_check' ) );
       
    53 	}
       
    54 
       
    55 	/**
       
    56 	 * Return an instance of the WP_Site_Health class, or create one if none exist yet.
       
    57 	 *
       
    58 	 * @since 5.4.0
       
    59 	 *
       
    60 	 * @return WP_Site_Health|null
       
    61 	 */
       
    62 	public static function get_instance() {
       
    63 		if ( null === self::$instance ) {
       
    64 			self::$instance = new WP_Site_Health();
       
    65 		}
       
    66 
       
    67 		return self::$instance;
    34 	}
    68 	}
    35 
    69 
    36 	/**
    70 	/**
    37 	 * Enqueues the site health scripts.
    71 	 * Enqueues the site health scripts.
    38 	 *
    72 	 *
    39 	 * @since 5.2.0
    73 	 * @since 5.2.0
    40 	 */
    74 	 */
    41 	public function enqueue_scripts() {
    75 	public function enqueue_scripts() {
    42 		$screen = get_current_screen();
    76 		$screen = get_current_screen();
    43 		if ( 'site-health' !== $screen->id ) {
    77 		if ( 'site-health' !== $screen->id && 'dashboard' !== $screen->id ) {
    44 			return;
    78 			return;
    45 		}
    79 		}
    46 
    80 
    47 		$health_check_js_variables = array(
    81 		$health_check_js_variables = array(
    48 			'screen'      => $screen->id,
    82 			'screen'      => $screen->id,
    70 		}
   104 		}
    71 
   105 
    72 		if ( 'site-health' === $screen->id && ! isset( $_GET['tab'] ) ) {
   106 		if ( 'site-health' === $screen->id && ! isset( $_GET['tab'] ) ) {
    73 			$tests = WP_Site_Health::get_tests();
   107 			$tests = WP_Site_Health::get_tests();
    74 
   108 
    75 			// Don't run https test on localhost
   109 			// Don't run https test on localhost.
    76 			if ( 'localhost' === preg_replace( '|https?://|', '', get_site_url() ) ) {
   110 			if ( 'localhost' === preg_replace( '|https?://|', '', get_site_url() ) ) {
    77 				unset( $tests['direct']['https_status'] );
   111 				unset( $tests['direct']['https_status'] );
    78 			}
   112 			}
    79 
   113 
    80 			foreach ( $tests['direct'] as $test ) {
   114 			foreach ( $tests['direct'] as $test ) {
    83 						'get_test_%s',
   117 						'get_test_%s',
    84 						$test['test']
   118 						$test['test']
    85 					);
   119 					);
    86 
   120 
    87 					if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) {
   121 					if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) {
    88 						$health_check_js_variables['site_status']['direct'][] = call_user_func( array( $this, $test_function ) );
   122 						$health_check_js_variables['site_status']['direct'][] = $this->perform_test( array( $this, $test_function ) );
    89 						continue;
   123 						continue;
    90 					}
   124 					}
    91 				}
   125 				}
    92 
   126 
    93 				if ( is_callable( $test['test'] ) ) {
   127 				if ( is_callable( $test['test'] ) ) {
    94 					$health_check_js_variables['site_status']['direct'][] = call_user_func( $test['test'] );
   128 					$health_check_js_variables['site_status']['direct'][] = $this->perform_test( $test['test'] );
    95 				}
   129 				}
    96 			}
   130 			}
    97 
   131 
    98 			foreach ( $tests['async'] as $test ) {
   132 			foreach ( $tests['async'] as $test ) {
    99 				if ( is_string( $test['test'] ) ) {
   133 				if ( is_string( $test['test'] ) ) {
   107 
   141 
   108 		wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables );
   142 		wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables );
   109 	}
   143 	}
   110 
   144 
   111 	/**
   145 	/**
       
   146 	 * Run a Site Health test directly.
       
   147 	 *
       
   148 	 * @since 5.4.0
       
   149 	 *
       
   150 	 * @param callable $callback
       
   151 	 * @return mixed|void
       
   152 	 */
       
   153 	private function perform_test( $callback ) {
       
   154 		/**
       
   155 		 * Filter the output of a finished Site Health test.
       
   156 		 *
       
   157 		 * @since 5.3.0
       
   158 		 *
       
   159 		 * @param array $test_result {
       
   160 		 *     An associative array of test result data.
       
   161 		 *
       
   162 		 *     @type string $label       A label describing the test, and is used as a header in the output.
       
   163 		 *     @type string $status      The status of the test, which can be a value of `good`, `recommended` or `critical`.
       
   164 		 *     @type array  $badge {
       
   165 		 *         Tests are put into categories which have an associated badge shown, these can be modified and assigned here.
       
   166 		 *
       
   167 		 *         @type string $label The test label, for example `Performance`.
       
   168 		 *         @type string $color Default `blue`. A string representing a color to use for the label.
       
   169 		 *     }
       
   170 		 *     @type string $description A more descriptive explanation of what the test looks for, and why it is important for the end user.
       
   171 		 *     @type string $actions     An action to direct the user to where they can resolve the issue, if one exists.
       
   172 		 *     @type string $test        The name of the test being ran, used as a reference point.
       
   173 		 * }
       
   174 		 */
       
   175 		return apply_filters( 'site_status_test_result', call_user_func( $callback ) );
       
   176 	}
       
   177 
       
   178 	/**
   112 	 * Run the SQL version checks.
   179 	 * Run the SQL version checks.
   113 	 *
   180 	 *
   114 	 * These values are used in later tests, but the part of preparing them is more easily managed early
   181 	 * These values are used in later tests, but the part of preparing them is more easily managed
   115 	 * in the class for ease of access and discovery.
   182 	 * early in the class for ease of access and discovery.
   116 	 *
   183 	 *
   117 	 * @since 5.2.0
   184 	 * @since 5.2.0
   118 	 *
   185 	 *
   119 	 * @global wpdb $wpdb WordPress database abstraction object.
   186 	 * @global wpdb $wpdb WordPress database abstraction object.
   120 	 */
   187 	 */
   121 	private function prepare_sql_data() {
   188 	private function prepare_sql_data() {
   122 		global $wpdb;
   189 		global $wpdb;
   123 
   190 
   124 		if ( method_exists( $wpdb, 'db_version' ) ) {
   191 		if ( $wpdb->use_mysqli ) {
   125 			if ( $wpdb->use_mysqli ) {
   192 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info
   126 				// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info
   193 			$mysql_server_type = mysqli_get_server_info( $wpdb->dbh );
   127 				$mysql_server_type = mysqli_get_server_info( $wpdb->dbh );
   194 		} else {
   128 			} else {
   195 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
   129 				// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info
   196 			$mysql_server_type = mysql_get_server_info( $wpdb->dbh );
   130 				$mysql_server_type = mysql_get_server_info( $wpdb->dbh );
   197 		}
   131 			}
   198 
   132 
   199 		$this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' );
   133 			$this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' );
       
   134 		}
       
   135 
   200 
   136 		$this->health_check_mysql_rec_version = '5.6';
   201 		$this->health_check_mysql_rec_version = '5.6';
   137 
   202 
   138 		if ( stristr( $mysql_server_type, 'mariadb' ) ) {
   203 		if ( stristr( $mysql_server_type, 'mariadb' ) ) {
   139 			$this->is_mariadb                     = true;
   204 			$this->is_mariadb                     = true;
   145 	}
   210 	}
   146 
   211 
   147 	/**
   212 	/**
   148 	 * Test if `wp_version_check` is blocked.
   213 	 * Test if `wp_version_check` is blocked.
   149 	 *
   214 	 *
   150 	 * It's possible to block updates with the `wp_version_check` filter, but this can't be checked during an
   215 	 * It's possible to block updates with the `wp_version_check` filter, but this can't be checked
   151 	 * AJAX call, as the filter is never introduced then.
   216 	 * during an Ajax call, as the filter is never introduced then.
   152 	 *
   217 	 *
   153 	 * This filter overrides a normal page request if it's made by an admin through the AJAX call with the
   218 	 * This filter overrides a standard page request if it's made by an admin through the Ajax call
   154 	 * right query argument to check for this.
   219 	 * with the right query argument to check for this.
   155 	 *
   220 	 *
   156 	 * @since 5.2.0
   221 	 * @since 5.2.0
   157 	 */
   222 	 */
   158 	public function check_wp_version_check_exists() {
   223 	public function check_wp_version_check_exists() {
   159 		if ( ! is_admin() || ! is_user_logged_in() || ! current_user_can( 'update_core' ) || ! isset( $_GET['health-check-test-wp_version_check'] ) ) {
   224 		if ( ! is_admin() || ! is_user_logged_in() || ! current_user_can( 'update_core' ) || ! isset( $_GET['health-check-test-wp_version_check'] ) ) {
   166 	}
   231 	}
   167 
   232 
   168 	/**
   233 	/**
   169 	 * Tests for WordPress version and outputs it.
   234 	 * Tests for WordPress version and outputs it.
   170 	 *
   235 	 *
   171 	 * Gives various results depending on what kind of updates are available, if any, to encourage the
   236 	 * Gives various results depending on what kind of updates are available, if any, to encourage
   172 	 * user to install security updates as a priority.
   237 	 * the user to install security updates as a priority.
   173 	 *
   238 	 *
   174 	 * @since 5.2.0
   239 	 * @since 5.2.0
   175 	 *
   240 	 *
   176 	 * @return array The test result.
   241 	 * @return array The test result.
   177 	 */
   242 	 */
   193 
   258 
   194 		if ( ! is_array( $core_updates ) ) {
   259 		if ( ! is_array( $core_updates ) ) {
   195 			$result['status'] = 'recommended';
   260 			$result['status'] = 'recommended';
   196 
   261 
   197 			$result['label'] = sprintf(
   262 			$result['label'] = sprintf(
   198 				// translators: %s: Your current version of WordPress.
   263 				/* translators: %s: Your current version of WordPress. */
   199 				__( 'WordPress version %s' ),
   264 				__( 'WordPress version %s' ),
   200 				$core_current_version
   265 				$core_current_version
   201 			);
   266 			);
   202 
   267 
   203 			$result['description'] = sprintf(
   268 			$result['description'] = sprintf(
   218 
   283 
   219 					$current_major = $current_version[0] . '.' . $current_version[1];
   284 					$current_major = $current_version[0] . '.' . $current_version[1];
   220 					$new_major     = $new_version[0] . '.' . $new_version[1];
   285 					$new_major     = $new_version[0] . '.' . $new_version[1];
   221 
   286 
   222 					$result['label'] = sprintf(
   287 					$result['label'] = sprintf(
   223 						// translators: %s: The latest version of WordPress available.
   288 						/* translators: %s: The latest version of WordPress available. */
   224 						__( 'WordPress update available (%s)' ),
   289 						__( 'WordPress update available (%s)' ),
   225 						$update->version
   290 						$update->version
   226 					);
   291 					);
   227 
   292 
   228 					$result['actions'] = sprintf(
   293 					$result['actions'] = sprintf(
   248 						);
   313 						);
   249 					}
   314 					}
   250 				} else {
   315 				} else {
   251 					$result['status'] = 'good';
   316 					$result['status'] = 'good';
   252 					$result['label']  = sprintf(
   317 					$result['label']  = sprintf(
   253 						// translators: %s: The current version of WordPress installed on this site.
   318 						/* translators: %s: The current version of WordPress installed on this site. */
   254 						__( 'Your WordPress version is up to date (%s)' ),
   319 						__( 'Your version of WordPress (%s) is up to date' ),
   255 						$core_current_version
   320 						$core_current_version
   256 					);
   321 					);
   257 
   322 
   258 					$result['description'] = sprintf(
   323 					$result['description'] = sprintf(
   259 						'<p>%s</p>',
   324 						'<p>%s</p>',
   267 	}
   332 	}
   268 
   333 
   269 	/**
   334 	/**
   270 	 * Test if plugins are outdated, or unnecessary.
   335 	 * Test if plugins are outdated, or unnecessary.
   271 	 *
   336 	 *
   272 	 * The tests checks if your plugins are up to date, and encourages you to remove any that are not in use.
   337 	 * The tests checks if your plugins are up to date, and encourages you to remove any
       
   338 	 * that are not in use.
   273 	 *
   339 	 *
   274 	 * @since 5.2.0
   340 	 * @since 5.2.0
   275 	 *
   341 	 *
   276 	 * @return array The test result.
   342 	 * @return array The test result.
   277 	 */
   343 	 */
   278 	public function get_test_plugin_version() {
   344 	public function get_test_plugin_version() {
   279 		$result = array(
   345 		$result = array(
   280 			'label'       => __( 'Your plugins are up to date' ),
   346 			'label'       => __( 'Your plugins are all up to date' ),
   281 			'status'      => 'good',
   347 			'status'      => 'good',
   282 			'badge'       => array(
   348 			'badge'       => array(
   283 				'label' => __( 'Security' ),
   349 				'label' => __( 'Security' ),
   284 				'color' => 'blue',
   350 				'color' => 'blue',
   285 			),
   351 			),
   398 	}
   464 	}
   399 
   465 
   400 	/**
   466 	/**
   401 	 * Test if themes are outdated, or unnecessary.
   467 	 * Test if themes are outdated, or unnecessary.
   402 	 *
   468 	 *
   403 	 * The tests checks if your site has a default theme (to fall back on if there is a need), if your themes
   469 	 * Сhecks if your site has a default theme (to fall back on if there is a need),
   404 	 * are up to date and, finally, encourages you to remove any themes that are not needed.
   470 	 * if your themes are up to date and, finally, encourages you to remove any themes
       
   471 	 * that are not needed.
   405 	 *
   472 	 *
   406 	 * @since 5.2.0
   473 	 * @since 5.2.0
   407 	 *
   474 	 *
   408 	 * @return array The test results.
   475 	 * @return array The test results.
   409 	 */
   476 	 */
   410 	public function get_test_theme_version() {
   477 	public function get_test_theme_version() {
   411 		$result = array(
   478 		$result = array(
   412 			'label'       => __( 'Your themes are up to date' ),
   479 			'label'       => __( 'Your themes are all up to date' ),
   413 			'status'      => 'good',
   480 			'status'      => 'good',
   414 			'badge'       => array(
   481 			'badge'       => array(
   415 				'label' => __( 'Security' ),
   482 				'label' => __( 'Security' ),
   416 				'color' => 'blue',
   483 				'color' => 'blue',
   417 			),
   484 			),
   443 
   510 
   444 		// Populate a list of all themes available in the install.
   511 		// Populate a list of all themes available in the install.
   445 		$all_themes   = wp_get_themes();
   512 		$all_themes   = wp_get_themes();
   446 		$active_theme = wp_get_theme();
   513 		$active_theme = wp_get_theme();
   447 
   514 
       
   515 		// If WP_DEFAULT_THEME doesn't exist, fall back to the latest core default theme.
       
   516 		$default_theme = wp_get_theme( WP_DEFAULT_THEME );
       
   517 		if ( ! $default_theme->exists() ) {
       
   518 			$default_theme = WP_Theme::get_core_default_theme();
       
   519 		}
       
   520 
       
   521 		if ( $default_theme ) {
       
   522 			$has_default_theme = true;
       
   523 
       
   524 			if (
       
   525 				$active_theme->get_stylesheet() === $default_theme->get_stylesheet()
       
   526 			||
       
   527 				is_child_theme() && $active_theme->get_template() === $default_theme->get_template()
       
   528 			) {
       
   529 				$using_default_theme = true;
       
   530 			}
       
   531 		}
       
   532 
   448 		foreach ( $all_themes as $theme_slug => $theme ) {
   533 		foreach ( $all_themes as $theme_slug => $theme ) {
   449 			$themes_total++;
   534 			$themes_total++;
   450 
   535 
   451 			if ( WP_DEFAULT_THEME === $theme_slug ) {
       
   452 				$has_default_theme = true;
       
   453 
       
   454 				if ( get_stylesheet() === $theme_slug ) {
       
   455 					$using_default_theme = true;
       
   456 				}
       
   457 			}
       
   458 
       
   459 			if ( array_key_exists( $theme_slug, $theme_updates ) ) {
   536 			if ( array_key_exists( $theme_slug, $theme_updates ) ) {
   460 				$themes_need_updates++;
   537 				$themes_need_updates++;
   461 			}
   538 			}
   462 		}
   539 		}
   463 
   540 
   464 		// If this is a child theme, increase the allowed theme count by one, to account for the parent.
   541 		// If this is a child theme, increase the allowed theme count by one, to account for the parent.
   465 		if ( $active_theme->parent() ) {
   542 		if ( is_child_theme() ) {
   466 			$allowed_theme_count++;
   543 			$allowed_theme_count++;
   467 
       
   468 			if ( $active_theme->get_template() === WP_DEFAULT_THEME ) {
       
   469 				$using_default_theme = true;
       
   470 			}
       
   471 		}
   544 		}
   472 
   545 
   473 		// If there's a default theme installed and not in use, we count that as allowed as well.
   546 		// If there's a default theme installed and not in use, we count that as allowed as well.
   474 		if ( $has_default_theme && ! $using_default_theme ) {
   547 		if ( $has_default_theme && ! $using_default_theme ) {
   475 			$allowed_theme_count++;
   548 			$allowed_theme_count++;
   562 							$themes_inactive
   635 							$themes_inactive
   563 						),
   636 						),
   564 						sprintf(
   637 						sprintf(
   565 							/* translators: 1: The default theme for WordPress. 2: The currently active theme. 3: The active theme's parent theme. */
   638 							/* translators: 1: The default theme for WordPress. 2: The currently active theme. 3: The active theme's parent theme. */
   566 							__( 'To enhance your site&#8217;s security, we recommend you remove any themes you&#8217;re not using. You should keep %1$s, the default WordPress theme, %2$s, your current theme, and %3$s, its parent theme.' ),
   639 							__( 'To enhance your site&#8217;s security, we recommend you remove any themes you&#8217;re not using. You should keep %1$s, the default WordPress theme, %2$s, your current theme, and %3$s, its parent theme.' ),
   567 							WP_DEFAULT_THEME,
   640 							$default_theme ? $default_theme->name : WP_DEFAULT_THEME,
   568 							$active_theme->name,
   641 							$active_theme->name,
   569 							$active_theme->parent()->name
   642 							$active_theme->parent()->name
   570 						)
   643 						)
   571 					);
   644 					);
   572 				}
   645 				}
   600 								'Your site has %1$d inactive theme, other than %2$s, the default WordPress theme, and %3$s, your active theme.',
   673 								'Your site has %1$d inactive theme, other than %2$s, the default WordPress theme, and %3$s, your active theme.',
   601 								'Your site has %1$d inactive themes, other than %2$s, the default WordPress theme, and %3$s, your active theme.',
   674 								'Your site has %1$d inactive themes, other than %2$s, the default WordPress theme, and %3$s, your active theme.',
   602 								$themes_inactive
   675 								$themes_inactive
   603 							),
   676 							),
   604 							$themes_inactive,
   677 							$themes_inactive,
   605 							WP_DEFAULT_THEME,
   678 							$default_theme ? $default_theme->name : WP_DEFAULT_THEME,
   606 							$active_theme->name
   679 							$active_theme->name
   607 						),
   680 						),
   608 						__( 'We recommend removing any unused themes to enhance your site&#8217;s security.' )
   681 						__( 'We recommend removing any unused themes to enhance your site&#8217;s security.' )
   609 					);
   682 					);
   610 				}
   683 				}
   611 			}
   684 			}
   612 		}
   685 		}
   613 
   686 
   614 		// If not default Twenty* theme exists.
   687 		// If no default Twenty* theme exists.
   615 		if ( ! $has_default_theme ) {
   688 		if ( ! $has_default_theme ) {
   616 			$result['status'] = 'recommended';
   689 			$result['status'] = 'recommended';
   617 
   690 
   618 			$result['label'] = __( 'Have a default theme available' );
   691 			$result['label'] = __( 'Have a default theme available' );
   619 
   692 
   620 			$result['description'] .= sprintf(
   693 			$result['description'] .= sprintf(
   621 				'<p>%s</p>',
   694 				'<p>%s</p>',
   622 				__( 'Your site does not have any default theme. Default themes are used by WordPress automatically if anything is wrong with your normal theme.' )
   695 				__( 'Your site does not have any default theme. Default themes are used by WordPress automatically if anything is wrong with your chosen theme.' )
   623 			);
   696 			);
   624 		}
   697 		}
   625 
   698 
   626 		return $result;
   699 		return $result;
   627 	}
   700 	}
   636 	public function get_test_php_version() {
   709 	public function get_test_php_version() {
   637 		$response = wp_check_php_version();
   710 		$response = wp_check_php_version();
   638 
   711 
   639 		$result = array(
   712 		$result = array(
   640 			'label'       => sprintf(
   713 			'label'       => sprintf(
   641 				// translators: %s: The current PHP version.
   714 				/* translators: %s: The current PHP version. */
   642 				__( 'PHP is up to date (%s)' ),
   715 				__( 'Your site is running the current version of PHP (%s)' ),
   643 				PHP_VERSION
   716 				PHP_VERSION
   644 			),
   717 			),
   645 			'status'      => 'good',
   718 			'status'      => 'good',
   646 			'badge'       => array(
   719 			'badge'       => array(
   647 				'label' => __( 'Performance' ),
   720 				'label' => __( 'Performance' ),
   648 				'color' => 'blue',
   721 				'color' => 'blue',
   649 			),
   722 			),
   650 			'description' => sprintf(
   723 			'description' => sprintf(
   651 				'<p>%s</p>',
   724 				'<p>%s</p>',
   652 				__( 'PHP is the programming language we use to build and maintain WordPress. Newer versions of PHP are both faster and more secure, so updating will have a positive effect on your site&#8217;s performance.' )
   725 				sprintf(
       
   726 					/* translators: %s: The minimum recommended PHP version. */
       
   727 					__( 'PHP is the programming language used to build and maintain WordPress. Newer versions of PHP are faster and more secure, so staying up to date will help your site&#8217;s overall performance and security. The minimum recommended version of PHP is %s.' ),
       
   728 					$response ? $response['recommended_version'] : ''
       
   729 				)
   653 			),
   730 			),
   654 			'actions'     => sprintf(
   731 			'actions'     => sprintf(
   655 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
   732 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
   656 				esc_url( wp_get_update_php_url() ),
   733 				esc_url( wp_get_update_php_url() ),
   657 				__( 'Learn more about updating PHP' ),
   734 				__( 'Learn more about updating PHP' ),
   658 				/* translators: accessibility text */
   735 				/* translators: Accessibility text. */
   659 				__( '(opens in a new tab)' )
   736 				__( '(opens in a new tab)' )
   660 			),
   737 			),
   661 			'test'        => 'php_version',
   738 			'test'        => 'php_version',
   662 		);
   739 		);
   663 
   740 
   664 		// PHP is up to date.
   741 		// PHP is up to date.
   665 		if ( ! $response || version_compare( PHP_VERSION, $response['recommended_version'], '>=' ) ) {
   742 		if ( ! $response || version_compare( PHP_VERSION, $response['recommended_version'], '>=' ) ) {
   666 			return $result;
   743 			return $result;
   667 		}
   744 		}
   668 
   745 
   669 		// The PHP version is older than the recommended version, but still acceptable.
   746 		// The PHP version is older than the recommended version, but still receiving active support.
   670 		if ( $response['is_supported'] ) {
   747 		if ( $response['is_supported'] ) {
   671 			$result['label']  = __( 'We recommend that you update PHP' );
   748 			$result['label'] = sprintf(
       
   749 				/* translators: %s: The server PHP version. */
       
   750 				__( 'Your site is running an older version of PHP (%s)' ),
       
   751 				PHP_VERSION
       
   752 			);
   672 			$result['status'] = 'recommended';
   753 			$result['status'] = 'recommended';
   673 
   754 
   674 			return $result;
   755 			return $result;
   675 		}
   756 		}
   676 
   757 
   677 		// The PHP version is only receiving security fixes.
   758 		// The PHP version is only receiving security fixes.
   678 		if ( $response['is_secure'] ) {
   759 		if ( $response['is_secure'] ) {
   679 			$result['label']  = __( 'Your PHP version should be updated' );
   760 			$result['label'] = sprintf(
       
   761 				/* translators: %s: The server PHP version. */
       
   762 				__( 'Your site is running an older version of PHP (%s), which should be updated' ),
       
   763 				PHP_VERSION
       
   764 			);
   680 			$result['status'] = 'recommended';
   765 			$result['status'] = 'recommended';
   681 
   766 
   682 			return $result;
   767 			return $result;
   683 		}
   768 		}
   684 
   769 
   685 		// Anything no longer secure must be updated.
   770 		// Anything no longer secure must be updated.
   686 		$result['label']          = __( 'Your PHP version requires an update' );
   771 		$result['label'] = sprintf(
       
   772 			/* translators: %s: The server PHP version. */
       
   773 			__( 'Your site is running an outdated version of PHP (%s), which requires an update' ),
       
   774 			PHP_VERSION
       
   775 		);
   687 		$result['status']         = 'critical';
   776 		$result['status']         = 'critical';
   688 		$result['badge']['label'] = __( 'Security' );
   777 		$result['badge']['label'] = __( 'Security' );
   689 
   778 
   690 		return $result;
   779 		return $result;
   691 	}
   780 	}
   694 	 * Check if the passed extension or function are available.
   783 	 * Check if the passed extension or function are available.
   695 	 *
   784 	 *
   696 	 * Make the check for available PHP modules into a simple boolean operator for a cleaner test runner.
   785 	 * Make the check for available PHP modules into a simple boolean operator for a cleaner test runner.
   697 	 *
   786 	 *
   698 	 * @since 5.2.0
   787 	 * @since 5.2.0
       
   788 	 * @since 5.3.0 The `$constant` and `$class` parameters were added.
   699 	 *
   789 	 *
   700 	 * @param string $extension Optional. The extension name to test. Default null.
   790 	 * @param string $extension Optional. The extension name to test. Default null.
   701 	 * @param string $function  Optional. The function name to test. Default null.
   791 	 * @param string $function  Optional. The function name to test. Default null.
   702 	 *
   792 	 * @param string $constant  Optional. The constant name to test for. Default null.
       
   793 	 * @param string $class     Optional. The class name to test for. Default null.
   703 	 * @return bool Whether or not the extension and function are available.
   794 	 * @return bool Whether or not the extension and function are available.
   704 	 */
   795 	 */
   705 	private function test_php_extension_availability( $extension = null, $function = null ) {
   796 	private function test_php_extension_availability( $extension = null, $function = null, $constant = null, $class = null ) {
   706 		// If no extension or function is passed, claim to fail testing, as we have nothing to test against.
   797 		// If no extension or function is passed, claim to fail testing, as we have nothing to test against.
   707 		if ( ! $extension && ! $function ) {
   798 		if ( ! $extension && ! $function && ! $constant && ! $class ) {
   708 			return false;
   799 			return false;
   709 		}
   800 		}
   710 
   801 
   711 		if ( $extension && ! extension_loaded( $extension ) ) {
   802 		if ( $extension && ! extension_loaded( $extension ) ) {
   712 			return false;
   803 			return false;
   713 		}
   804 		}
   714 		if ( $function && ! function_exists( $function ) ) {
   805 		if ( $function && ! function_exists( $function ) ) {
       
   806 			return false;
       
   807 		}
       
   808 		if ( $constant && ! defined( $constant ) ) {
       
   809 			return false;
       
   810 		}
       
   811 		if ( $class && ! class_exists( $class ) ) {
   715 			return false;
   812 			return false;
   716 		}
   813 		}
   717 
   814 
   718 		return true;
   815 		return true;
   719 	}
   816 	}
   745 					/* translators: Localized team handbook, if one exists. */
   842 					/* translators: Localized team handbook, if one exists. */
   746 					esc_url( __( 'https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions' ) ),
   843 					esc_url( __( 'https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions' ) ),
   747 					'target="_blank" rel="noopener noreferrer"',
   844 					'target="_blank" rel="noopener noreferrer"',
   748 					sprintf(
   845 					sprintf(
   749 						' <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span>',
   846 						' <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span>',
   750 						/* translators: accessibility text */
   847 						/* translators: Accessibility text. */
   751 						__( '(opens in a new tab)' )
   848 						__( '(opens in a new tab)' )
   752 					)
   849 					)
   753 				)
   850 				)
   754 			),
   851 			),
   755 			'actions'     => '',
   852 			'actions'     => '',
   756 			'test'        => 'php_extensions',
   853 			'test'        => 'php_extensions',
   757 		);
   854 		);
   758 
   855 
   759 		$modules = array(
   856 		$modules = array(
   760 			'bcmath'    => array(
       
   761 				'function' => 'bcadd',
       
   762 				'required' => false,
       
   763 			),
       
   764 			'curl'      => array(
   857 			'curl'      => array(
   765 				'function' => 'curl_version',
   858 				'function' => 'curl_version',
   766 				'required' => false,
   859 				'required' => false,
   767 			),
   860 			),
       
   861 			'dom'       => array(
       
   862 				'class'    => 'DOMNode',
       
   863 				'required' => false,
       
   864 			),
   768 			'exif'      => array(
   865 			'exif'      => array(
   769 				'function' => 'exif_read_data',
   866 				'function' => 'exif_read_data',
   770 				'required' => false,
   867 				'required' => false,
   771 			),
   868 			),
       
   869 			'fileinfo'  => array(
       
   870 				'function' => 'finfo_file',
       
   871 				'required' => false,
       
   872 			),
       
   873 			'hash'      => array(
       
   874 				'function' => 'hash',
       
   875 				'required' => false,
       
   876 			),
       
   877 			'json'      => array(
       
   878 				'function' => 'json_last_error',
       
   879 				'required' => true,
       
   880 			),
       
   881 			'mbstring'  => array(
       
   882 				'function' => 'mb_check_encoding',
       
   883 				'required' => false,
       
   884 			),
       
   885 			'mysqli'    => array(
       
   886 				'function' => 'mysqli_connect',
       
   887 				'required' => false,
       
   888 			),
       
   889 			'libsodium' => array(
       
   890 				'constant'            => 'SODIUM_LIBRARY_VERSION',
       
   891 				'required'            => false,
       
   892 				'php_bundled_version' => '7.2.0',
       
   893 			),
       
   894 			'openssl'   => array(
       
   895 				'function' => 'openssl_encrypt',
       
   896 				'required' => false,
       
   897 			),
       
   898 			'pcre'      => array(
       
   899 				'function' => 'preg_match',
       
   900 				'required' => false,
       
   901 			),
       
   902 			'imagick'   => array(
       
   903 				'extension' => 'imagick',
       
   904 				'required'  => false,
       
   905 			),
       
   906 			'mod_xml'   => array(
       
   907 				'extension' => 'libxml',
       
   908 				'required'  => false,
       
   909 			),
       
   910 			'zip'       => array(
       
   911 				'class'    => 'ZipArchive',
       
   912 				'required' => false,
       
   913 			),
   772 			'filter'    => array(
   914 			'filter'    => array(
   773 				'function' => 'filter_list',
   915 				'function' => 'filter_list',
   774 				'required' => false,
   916 				'required' => false,
   775 			),
       
   776 			'fileinfo'  => array(
       
   777 				'function' => 'finfo_file',
       
   778 				'required' => false,
       
   779 			),
       
   780 			'mod_xml'   => array(
       
   781 				'extension' => 'libxml',
       
   782 				'required'  => false,
       
   783 			),
       
   784 			'mysqli'    => array(
       
   785 				'function' => 'mysqli_connect',
       
   786 				'required' => false,
       
   787 			),
       
   788 			'libsodium' => array(
       
   789 				'function'            => 'sodium_compare',
       
   790 				'required'            => false,
       
   791 				'php_bundled_version' => '7.2.0',
       
   792 			),
       
   793 			'openssl'   => array(
       
   794 				'function' => 'openssl_encrypt',
       
   795 				'required' => false,
       
   796 			),
       
   797 			'pcre'      => array(
       
   798 				'function' => 'preg_match',
       
   799 				'required' => false,
       
   800 			),
       
   801 			'imagick'   => array(
       
   802 				'extension' => 'imagick',
       
   803 				'required'  => false,
       
   804 			),
   917 			),
   805 			'gd'        => array(
   918 			'gd'        => array(
   806 				'extension'    => 'gd',
   919 				'extension'    => 'gd',
   807 				'required'     => false,
   920 				'required'     => false,
   808 				'fallback_for' => 'imagick',
   921 				'fallback_for' => 'imagick',
   809 			),
   922 			),
       
   923 			'iconv'     => array(
       
   924 				'function' => 'iconv',
       
   925 				'required' => false,
       
   926 			),
   810 			'mcrypt'    => array(
   927 			'mcrypt'    => array(
   811 				'extension'    => 'mcrypt',
   928 				'extension'    => 'mcrypt',
   812 				'required'     => false,
   929 				'required'     => false,
   813 				'fallback_for' => 'libsodium',
   930 				'fallback_for' => 'libsodium',
   814 			),
   931 			),
       
   932 			'simplexml' => array(
       
   933 				'extension'    => 'simplexml',
       
   934 				'required'     => false,
       
   935 				'fallback_for' => 'mod_xml',
       
   936 			),
   815 			'xmlreader' => array(
   937 			'xmlreader' => array(
   816 				'extension'    => 'xmlreader',
   938 				'extension'    => 'xmlreader',
   817 				'required'     => false,
   939 				'required'     => false,
   818 				'fallback_for' => 'xml',
   940 				'fallback_for' => 'mod_xml',
   819 			),
   941 			),
   820 			'zlib'      => array(
   942 			'zlib'      => array(
   821 				'extension'    => 'zlib',
   943 				'extension'    => 'zlib',
   822 				'required'     => false,
   944 				'required'     => false,
   823 				'fallback_for' => 'zip',
   945 				'fallback_for' => 'zip',
   826 
   948 
   827 		/**
   949 		/**
   828 		 * An array representing all the modules we wish to test for.
   950 		 * An array representing all the modules we wish to test for.
   829 		 *
   951 		 *
   830 		 * @since 5.2.0
   952 		 * @since 5.2.0
       
   953 		 * @since 5.3.0 The `$constant` and `$class` parameters were added.
   831 		 *
   954 		 *
   832 		 * @param array $modules {
   955 		 * @param array $modules {
   833 		 *     An associated array of modules to test for.
   956 		 *     An associative array of modules to test for.
   834 		 *
   957 		 *
   835 		 *     array $module {
   958 		 *     @type array ...$0 {
   836 		 *         An associated array of module properties used during testing.
   959 		 *         An associative array of module properties used during testing.
   837 		 *         One of either `$function` or `$extension` must be provided, or they will fail by default.
   960 		 *         One of either `$function` or `$extension` must be provided, or they will fail by default.
   838 		 *
   961 		 *
   839 		 *         string $function     Optional. A function name to test for the existence of.
   962 		 *         @type string $function     Optional. A function name to test for the existence of.
   840 		 *         string $extension    Optional. An extension to check if is loaded in PHP.
   963 		 *         @type string $extension    Optional. An extension to check if is loaded in PHP.
   841 		 *         bool   $required     Is this a required feature or not.
   964 		 *         @type string $constant     Optional. A constant name to check for to verify an extension exists.
   842 		 *         string $fallback_for Optional. The module this module replaces as a fallback.
   965 		 *         @type string $class        Optional. A class name to check for to verify an extension exists.
       
   966 		 *         @type bool   $required     Is this a required feature or not.
       
   967 		 *         @type string $fallback_for Optional. The module this module replaces as a fallback.
   843 		 *     }
   968 		 *     }
   844 		 * }
   969 		 * }
   845 		 */
   970 		 */
   846 		$modules = apply_filters( 'site_status_test_php_modules', $modules );
   971 		$modules = apply_filters( 'site_status_test_php_modules', $modules );
   847 
   972 
   848 		$failures = array();
   973 		$failures = array();
   849 
   974 
   850 		foreach ( $modules as $library => $module ) {
   975 		foreach ( $modules as $library => $module ) {
   851 			$extension = ( isset( $module['extension'] ) ? $module['extension'] : null );
   976 			$extension  = ( isset( $module['extension'] ) ? $module['extension'] : null );
   852 			$function  = ( isset( $module['function'] ) ? $module['function'] : null );
   977 			$function   = ( isset( $module['function'] ) ? $module['function'] : null );
       
   978 			$constant   = ( isset( $module['constant'] ) ? $module['constant'] : null );
       
   979 			$class_name = ( isset( $module['class'] ) ? $module['class'] : null );
   853 
   980 
   854 			// If this module is a fallback for another function, check if that other function passed.
   981 			// If this module is a fallback for another function, check if that other function passed.
   855 			if ( isset( $module['fallback_for'] ) ) {
   982 			if ( isset( $module['fallback_for'] ) ) {
   856 				/*
   983 				/*
   857 				 * If that other function has a failure, mark this module as required for normal operations.
   984 				 * If that other function has a failure, mark this module as required for usual operations.
   858 				 * If that other function hasn't failed, skip this test as it's only a fallback.
   985 				 * If that other function hasn't failed, skip this test as it's only a fallback.
   859 				 */
   986 				 */
   860 				if ( isset( $failures[ $module['fallback_for'] ] ) ) {
   987 				if ( isset( $failures[ $module['fallback_for'] ] ) ) {
   861 					$module['required'] = true;
   988 					$module['required'] = true;
   862 				} else {
   989 				} else {
   863 					continue;
   990 					continue;
   864 				}
   991 				}
   865 			}
   992 			}
   866 
   993 
   867 			if ( ! $this->test_php_extension_availability( $extension, $function ) && ( ! isset( $module['php_bundled_version'] ) || version_compare( PHP_VERSION, $module['php_bundled_version'], '<' ) ) ) {
   994 			if ( ! $this->test_php_extension_availability( $extension, $function, $constant, $class_name ) && ( ! isset( $module['php_bundled_version'] ) || version_compare( PHP_VERSION, $module['php_bundled_version'], '<' ) ) ) {
   868 				if ( $module['required'] ) {
   995 				if ( $module['required'] ) {
   869 					$result['status'] = 'critical';
   996 					$result['status'] = 'critical';
   870 
   997 
   871 					$class         = 'error';
   998 					$class         = 'error';
   872 					$screen_reader = __( 'Error' );
   999 					$screen_reader = __( 'Error' );
   912 			}
  1039 			}
   913 			if ( 'critical' === $result['status'] ) {
  1040 			if ( 'critical' === $result['status'] ) {
   914 				$result['label'] = __( 'One or more required modules are missing' );
  1041 				$result['label'] = __( 'One or more required modules are missing' );
   915 			}
  1042 			}
   916 
  1043 
   917 			$result['description'] .= sprintf(
  1044 			$result['description'] .= $output;
   918 				'<p>%s</p>',
       
   919 				$output
       
   920 			);
       
   921 		}
  1045 		}
   922 
  1046 
   923 		return $result;
  1047 		return $result;
   924 	}
  1048 	}
   925 
  1049 
   926 	/**
  1050 	/**
       
  1051 	 * Test if the PHP default timezone is set to UTC.
       
  1052 	 *
       
  1053 	 * @since 5.3.1
       
  1054 	 *
       
  1055 	 * @return array The test results.
       
  1056 	 */
       
  1057 	public function get_test_php_default_timezone() {
       
  1058 		$result = array(
       
  1059 			'label'       => __( 'PHP default timezone is valid' ),
       
  1060 			'status'      => 'good',
       
  1061 			'badge'       => array(
       
  1062 				'label' => __( 'Performance' ),
       
  1063 				'color' => 'blue',
       
  1064 			),
       
  1065 			'description' => sprintf(
       
  1066 				'<p>%s</p>',
       
  1067 				__( 'PHP default timezone was configured by WordPress on loading. This is necessary for correct calculations of dates and times.' )
       
  1068 			),
       
  1069 			'actions'     => '',
       
  1070 			'test'        => 'php_default_timezone',
       
  1071 		);
       
  1072 
       
  1073 		if ( 'UTC' !== date_default_timezone_get() ) {
       
  1074 			$result['status'] = 'critical';
       
  1075 
       
  1076 			$result['label'] = __( 'PHP default timezone is invalid' );
       
  1077 
       
  1078 			$result['description'] = sprintf(
       
  1079 				'<p>%s</p>',
       
  1080 				sprintf(
       
  1081 					/* translators: %s: date_default_timezone_set() */
       
  1082 					__( 'PHP default timezone was changed after WordPress loading by a %s function call. This interferes with correct calculations of dates and times.' ),
       
  1083 					'<code>date_default_timezone_set()</code>'
       
  1084 				)
       
  1085 			);
       
  1086 		}
       
  1087 
       
  1088 		return $result;
       
  1089 	}
       
  1090 
       
  1091 	/**
       
  1092 	 * Test if there's an active PHP session that can affect loopback requests.
       
  1093 	 *
       
  1094 	 * @since 5.5.0
       
  1095 	 *
       
  1096 	 * @return array The test results.
       
  1097 	 */
       
  1098 	public function get_test_php_sessions() {
       
  1099 		$result = array(
       
  1100 			'label'       => __( 'No PHP sessions detected' ),
       
  1101 			'status'      => 'good',
       
  1102 			'badge'       => array(
       
  1103 				'label' => __( 'Performance' ),
       
  1104 				'color' => 'blue',
       
  1105 			),
       
  1106 			'description' => sprintf(
       
  1107 				'<p>%s</p>',
       
  1108 				sprintf(
       
  1109 					/* translators: 1: session_start(), 2: session_write_close() */
       
  1110 					__( 'PHP sessions created by a %1$s function call may interfere with REST API and loopback requests. An active session should be closed by %2$s before making any HTTP requests.' ),
       
  1111 					'<code>session_start()</code>',
       
  1112 					'<code>session_write_close()</code>'
       
  1113 				)
       
  1114 			),
       
  1115 			'test'        => 'php_sessions',
       
  1116 		);
       
  1117 
       
  1118 		if ( function_exists( 'session_status' ) && PHP_SESSION_ACTIVE === session_status() ) {
       
  1119 			$result['status'] = 'critical';
       
  1120 
       
  1121 			$result['label'] = __( 'An active PHP session was detected' );
       
  1122 
       
  1123 			$result['description'] = sprintf(
       
  1124 				'<p>%s</p>',
       
  1125 				sprintf(
       
  1126 					/* translators: 1: session_start(), 2: session_write_close() */
       
  1127 					__( 'A PHP session was created by a %1$s function call. This interferes with REST API and loopback requests. The session should be closed by %2$s before making any HTTP requests.' ),
       
  1128 					'<code>session_start()</code>',
       
  1129 					'<code>session_write_close()</code>'
       
  1130 				)
       
  1131 			);
       
  1132 		}
       
  1133 
       
  1134 		return $result;
       
  1135 	}
       
  1136 
       
  1137 	/**
   927 	 * Test if the SQL server is up to date.
  1138 	 * Test if the SQL server is up to date.
   928 	 *
  1139 	 *
   929 	 * @since 5.2.0
  1140 	 * @since 5.2.0
   930 	 *
  1141 	 *
   931 	 * @return array The test results.
  1142 	 * @return array The test results.
   932 	 */
  1143 	 */
   933 	public function get_test_sql_server() {
  1144 	public function get_test_sql_server() {
       
  1145 		if ( ! $this->mysql_server_version ) {
       
  1146 			$this->prepare_sql_data();
       
  1147 		}
       
  1148 
   934 		$result = array(
  1149 		$result = array(
   935 			'label'       => __( 'SQL server is up to date' ),
  1150 			'label'       => __( 'SQL server is up to date' ),
   936 			'status'      => 'good',
  1151 			'status'      => 'good',
   937 			'badge'       => array(
  1152 			'badge'       => array(
   938 				'label' => __( 'Performance' ),
  1153 				'label' => __( 'Performance' ),
   944 			),
  1159 			),
   945 			'actions'     => sprintf(
  1160 			'actions'     => sprintf(
   946 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1161 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
   947 				/* translators: Localized version of WordPress requirements if one exists. */
  1162 				/* translators: Localized version of WordPress requirements if one exists. */
   948 				esc_url( __( 'https://wordpress.org/about/requirements/' ) ),
  1163 				esc_url( __( 'https://wordpress.org/about/requirements/' ) ),
   949 				__( 'Read more about what WordPress requires to run.' ),
  1164 				__( 'Learn more about what WordPress requires to run.' ),
   950 				/* translators: accessibility text */
  1165 				/* translators: Accessibility text. */
   951 				__( '(opens in a new tab)' )
  1166 				__( '(opens in a new tab)' )
   952 			),
  1167 			),
   953 			'test'        => 'sql_server',
  1168 			'test'        => 'sql_server',
   954 		);
  1169 		);
   955 
  1170 
  1016 	 * @return array The test results.
  1231 	 * @return array The test results.
  1017 	 */
  1232 	 */
  1018 	public function get_test_utf8mb4_support() {
  1233 	public function get_test_utf8mb4_support() {
  1019 		global $wpdb;
  1234 		global $wpdb;
  1020 
  1235 
       
  1236 		if ( ! $this->mysql_server_version ) {
       
  1237 			$this->prepare_sql_data();
       
  1238 		}
       
  1239 
  1021 		$result = array(
  1240 		$result = array(
  1022 			'label'       => __( 'UTF8MB4 is supported' ),
  1241 			'label'       => __( 'UTF8MB4 is supported' ),
  1023 			'status'      => 'good',
  1242 			'status'      => 'good',
  1024 			'badge'       => array(
  1243 			'badge'       => array(
  1025 				'label' => __( 'Performance' ),
  1244 				'label' => __( 'Performance' ),
  1026 				'color' => 'blue',
  1245 				'color' => 'blue',
  1027 			),
  1246 			),
  1028 			'description' => sprintf(
  1247 			'description' => sprintf(
  1029 				'<p>%s</p>',
  1248 				'<p>%s</p>',
  1030 				__( 'UTF8MB4 is a database storage attribute that makes sure your site can store non-English text and other strings (for instance emoticons) without unexpected problems.' )
  1249 				__( '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.' )
  1031 			),
  1250 			),
  1032 			'actions'     => '',
  1251 			'actions'     => '',
  1033 			'test'        => 'utf8mb4_support',
  1252 			'test'        => 'utf8mb4_support',
  1034 		);
  1253 		);
  1035 
  1254 
  1051 				$result['description'] .= sprintf(
  1270 				$result['description'] .= sprintf(
  1052 					'<p>%s</p>',
  1271 					'<p>%s</p>',
  1053 					__( 'Your MySQL version supports utf8mb4.' )
  1272 					__( 'Your MySQL version supports utf8mb4.' )
  1054 				);
  1273 				);
  1055 			}
  1274 			}
  1056 		} else { // MariaDB introduced utf8mb4 support in 5.5.0
  1275 		} else { // MariaDB introduced utf8mb4 support in 5.5.0.
  1057 			if ( version_compare( $this->mysql_server_version, '5.5.0', '<' ) ) {
  1276 			if ( version_compare( $this->mysql_server_version, '5.5.0', '<' ) ) {
  1058 				$result['status'] = 'recommended';
  1277 				$result['status'] = 'recommended';
  1059 
  1278 
  1060 				$result['label'] = __( 'utf8mb4 requires a MariaDB update' );
  1279 				$result['label'] = __( 'utf8mb4 requires a MariaDB update' );
  1061 
  1280 
  1077 
  1296 
  1078 		if ( $wpdb->use_mysqli ) {
  1297 		if ( $wpdb->use_mysqli ) {
  1079 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_client_info
  1298 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_client_info
  1080 			$mysql_client_version = mysqli_get_client_info();
  1299 			$mysql_client_version = mysqli_get_client_info();
  1081 		} else {
  1300 		} else {
  1082 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info
  1301 			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
  1083 			$mysql_client_version = mysql_get_client_info();
  1302 			$mysql_client_version = mysql_get_client_info();
  1084 		}
  1303 		}
  1085 
  1304 
  1086 		/*
  1305 		/*
  1087 		 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
  1306 		 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
  1178 			$result['actions'] = sprintf(
  1397 			$result['actions'] = sprintf(
  1179 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1398 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1180 				/* translators: Localized Support reference. */
  1399 				/* translators: Localized Support reference. */
  1181 				esc_url( __( 'https://wordpress.org/support' ) ),
  1400 				esc_url( __( 'https://wordpress.org/support' ) ),
  1182 				__( 'Get help resolving this issue.' ),
  1401 				__( 'Get help resolving this issue.' ),
  1183 				/* translators: accessibility text */
  1402 				/* translators: Accessibility text. */
  1184 				__( '(opens in a new tab)' )
  1403 				__( '(opens in a new tab)' )
  1185 			);
  1404 			);
  1186 		}
  1405 		}
  1187 
  1406 
  1188 		return $result;
  1407 		return $result;
  1189 	}
  1408 	}
  1190 
  1409 
  1191 	/**
  1410 	/**
  1192 	 * Test if debug information is enabled.
  1411 	 * Test if debug information is enabled.
  1193 	 *
  1412 	 *
  1194 	 * When WP_DEBUG is enabled, errors and information may be disclosed to site visitors, or it may be
  1413 	 * When WP_DEBUG is enabled, errors and information may be disclosed to site visitors,
  1195 	 * logged to a publicly accessible file.
  1414 	 * or logged to a publicly accessible file.
  1196 	 *
  1415 	 *
  1197 	 * Debugging is also frequently left enabled after looking for errors on a site, as site owners do
  1416 	 * Debugging is also frequently left enabled after looking for errors on a site,
  1198 	 * not understand the implications of this.
  1417 	 * as site owners do not understand the implications of this.
  1199 	 *
  1418 	 *
  1200 	 * @since 5.2.0
  1419 	 * @since 5.2.0
  1201 	 *
  1420 	 *
  1202 	 * @return array The test results.
  1421 	 * @return array The test results.
  1203 	 */
  1422 	 */
  1215 			),
  1434 			),
  1216 			'actions'     => sprintf(
  1435 			'actions'     => sprintf(
  1217 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1436 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1218 				/* translators: Documentation explaining debugging in WordPress. */
  1437 				/* translators: Documentation explaining debugging in WordPress. */
  1219 				esc_url( __( 'https://wordpress.org/support/article/debugging-in-wordpress/' ) ),
  1438 				esc_url( __( 'https://wordpress.org/support/article/debugging-in-wordpress/' ) ),
  1220 				__( 'Read about debugging in WordPress.' ),
  1439 				__( 'Learn more about debugging in WordPress.' ),
  1221 				/* translators: accessibility text */
  1440 				/* translators: Accessibility text. */
  1222 				__( '(opens in a new tab)' )
  1441 				__( '(opens in a new tab)' )
  1223 			),
  1442 			),
  1224 			'test'        => 'is_in_debug_mode',
  1443 			'test'        => 'is_in_debug_mode',
  1225 		);
  1444 		);
  1226 
  1445 
  1227 		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
  1446 		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
  1228 			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
  1447 			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
  1229 				$result['label'] = __( 'Your site is set to log errors to a potentially public file.' );
  1448 				$result['label'] = __( 'Your site is set to log errors to a potentially public file.' );
  1230 
  1449 
  1231 				$result['status'] = 'critical';
  1450 				$result['status'] = ( 0 === strpos( ini_get( 'error_log' ), ABSPATH ) ) ? 'critical' : 'recommended';
  1232 
  1451 
  1233 				$result['description'] .= sprintf(
  1452 				$result['description'] .= sprintf(
  1234 					'<p>%s</p>',
  1453 					'<p>%s</p>',
  1235 					sprintf(
  1454 					sprintf(
  1236 						/* translators: %s: WP_DEBUG_LOG */
  1455 						/* translators: %s: WP_DEBUG_LOG */
  1237 						__( 'The value, %s, has been added to this website&#8217;s configuration file. This means any errors on the site will be written to a file which is potentially available to normal users.' ),
  1456 						__( 'The value, %s, has been added to this website&#8217;s configuration file. This means any errors on the site will be written to a file which is potentially available to all users.' ),
  1238 						'<code>WP_DEBUG_LOG</code>'
  1457 						'<code>WP_DEBUG_LOG</code>'
  1239 					)
  1458 					)
  1240 				);
  1459 				);
  1241 			}
  1460 			}
  1242 
  1461 
  1278 				'label' => __( 'Security' ),
  1497 				'label' => __( 'Security' ),
  1279 				'color' => 'blue',
  1498 				'color' => 'blue',
  1280 			),
  1499 			),
  1281 			'description' => sprintf(
  1500 			'description' => sprintf(
  1282 				'<p>%s</p>',
  1501 				'<p>%s</p>',
  1283 				__( 'An HTTPS connection is needed for many features on the web today, it also gains the trust of your visitors by helping to protecting their online privacy.' )
  1502 				__( 'An HTTPS connection is a more secure way of browsing the web. Many services now have HTTPS as a requirement. HTTPS allows you to take advantage of new features that can increase site speed, improve search rankings, and gain the trust of your visitors by helping to protect their online privacy.' )
  1284 			),
  1503 			),
  1285 			'actions'     => sprintf(
  1504 			'actions'     => sprintf(
  1286 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1505 				'<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
  1287 				/* translators: Documentation explaining HTTPS and why it should be used. */
  1506 				/* translators: Documentation explaining HTTPS and why it should be used. */
  1288 				esc_url( __( 'https://wordpress.org/support/article/why-should-i-use-https/' ) ),
  1507 				esc_url( __( 'https://wordpress.org/support/article/why-should-i-use-https/' ) ),
  1289 				__( 'Read more about why you should use HTTPS' ),
  1508 				__( 'Learn more about why you should use HTTPS' ),
  1290 				/* translators: accessibility text */
  1509 				/* translators: Accessibility text. */
  1291 				__( '(opens in a new tab)' )
  1510 				__( '(opens in a new tab)' )
  1292 			),
  1511 			),
  1293 			'test'        => 'https_status',
  1512 			'test'        => 'https_status',
  1294 		);
  1513 		);
  1295 
  1514 
  1303 				$result['label'] = __( 'Only parts of your site are using HTTPS' );
  1522 				$result['label'] = __( 'Only parts of your site are using HTTPS' );
  1304 
  1523 
  1305 				$result['description'] = sprintf(
  1524 				$result['description'] = sprintf(
  1306 					'<p>%s</p>',
  1525 					'<p>%s</p>',
  1307 					sprintf(
  1526 					sprintf(
  1308 						/* translators: %s: URL to Settings > General to change options. */
  1527 						/* translators: %s: URL to General Settings screen. */
  1309 						__( 'You are accessing this website using HTTPS, but your <a href="%s">WordPress Address</a> is not set up to use HTTPS by default.' ),
  1528 						__( 'You are accessing this website using HTTPS, but your <a href="%s">WordPress Address</a> is not set up to use HTTPS by default.' ),
  1310 						esc_url( admin_url( 'options-general.php' ) )
  1529 						esc_url( admin_url( 'options-general.php' ) )
  1311 					)
  1530 					)
  1312 				);
  1531 				);
  1313 
  1532 
  1370 	}
  1589 	}
  1371 
  1590 
  1372 	/**
  1591 	/**
  1373 	 * Test if scheduled events run as intended.
  1592 	 * Test if scheduled events run as intended.
  1374 	 *
  1593 	 *
  1375 	 * If scheduled events are not running, this may indicate something with WP_Cron is not working as intended,
  1594 	 * If scheduled events are not running, this may indicate something with WP_Cron is not working
  1376 	 * or that there are orphaned events hanging around from older code.
  1595 	 * as intended, or that there are orphaned events hanging around from older code.
  1377 	 *
  1596 	 *
  1378 	 * @since 5.2.0
  1597 	 * @since 5.2.0
  1379 	 *
  1598 	 *
  1380 	 * @return array The test results.
  1599 	 * @return array The test results.
  1381 	 */
  1600 	 */
  1408 					/* translators: %s: The error message returned while from the cron scheduler. */
  1627 					/* translators: %s: The error message returned while from the cron scheduler. */
  1409 					__( 'While trying to test your site&#8217;s scheduled events, the following error was returned: %s' ),
  1628 					__( 'While trying to test your site&#8217;s scheduled events, the following error was returned: %s' ),
  1410 					$this->has_missed_cron()->get_error_message()
  1629 					$this->has_missed_cron()->get_error_message()
  1411 				)
  1630 				)
  1412 			);
  1631 			);
  1413 		} else {
  1632 		} elseif ( $this->has_missed_cron() ) {
  1414 			if ( $this->has_missed_cron() ) {
  1633 			$result['status'] = 'recommended';
  1415 				$result['status'] = 'recommended';
  1634 
  1416 
  1635 			$result['label'] = __( 'A scheduled event has failed' );
  1417 				$result['label'] = __( 'A scheduled event has failed' );
  1636 
  1418 
  1637 			$result['description'] = sprintf(
  1419 				$result['description'] = sprintf(
  1638 				'<p>%s</p>',
  1420 					'<p>%s</p>',
  1639 				sprintf(
  1421 					sprintf(
  1640 					/* translators: %s: The name of the failed cron event. */
  1422 						/* translators: %s: The name of the failed cron event. */
  1641 					__( 'The scheduled event, %s, failed to run. Your site still works, but this may indicate that scheduling posts or automated updates may not work as intended.' ),
  1423 						__( 'The scheduled event, %s, failed to run. Your site still works, but this may indicate that scheduling posts or automated updates may not work as intended.' ),
  1642 					$this->last_missed_cron
  1424 						$this->last_missed_cron
  1643 				)
  1425 					)
  1644 			);
  1426 				);
  1645 		} elseif ( $this->has_late_cron() ) {
  1427 			}
  1646 			$result['status'] = 'recommended';
       
  1647 
       
  1648 			$result['label'] = __( 'A scheduled event is late' );
       
  1649 
       
  1650 			$result['description'] = sprintf(
       
  1651 				'<p>%s</p>',
       
  1652 				sprintf(
       
  1653 					/* translators: %s: The name of the late cron event. */
       
  1654 					__( 'The scheduled event, %s, is late to run. Your site still works, but this may indicate that scheduling posts or automated updates may not work as intended.' ),
       
  1655 					$this->last_late_cron
       
  1656 				)
       
  1657 			);
  1428 		}
  1658 		}
  1429 
  1659 
  1430 		return $result;
  1660 		return $result;
  1431 	}
  1661 	}
  1432 
  1662 
  1433 	/**
  1663 	/**
  1434 	 * Test if WordPress can run automated background updates.
  1664 	 * Test if WordPress can run automated background updates.
  1435 	 *
  1665 	 *
  1436 	 * Background updates in WordPress are primarily used for minor releases and security updates. It's important
  1666 	 * Background updates in WordPress are primarily used for minor releases and security updates.
  1437 	 * to either have these working, or be aware that they are intentionally disabled for whatever reason.
  1667 	 * It's important to either have these working, or be aware that they are intentionally disabled
       
  1668 	 * for whatever reason.
  1438 	 *
  1669 	 *
  1439 	 * @since 5.2.0
  1670 	 * @since 5.2.0
  1440 	 *
  1671 	 *
  1441 	 * @return array The test results.
  1672 	 * @return array The test results.
  1442 	 */
  1673 	 */
  1455 			'actions'     => '',
  1686 			'actions'     => '',
  1456 			'test'        => 'background_updates',
  1687 			'test'        => 'background_updates',
  1457 		);
  1688 		);
  1458 
  1689 
  1459 		if ( ! class_exists( 'WP_Site_Health_Auto_Updates' ) ) {
  1690 		if ( ! class_exists( 'WP_Site_Health_Auto_Updates' ) ) {
  1460 			require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health-auto-updates.php' );
  1691 			require_once ABSPATH . 'wp-admin/includes/class-wp-site-health-auto-updates.php';
  1461 		}
  1692 		}
  1462 
  1693 
  1463 		// Run the auto-update tests in a separate class,
  1694 		// Run the auto-update tests in a separate class,
  1464 		// as there are many considerations to be made.
  1695 		// as there are many considerations to be made.
  1465 		$automatic_updates = new WP_Site_Health_Auto_Updates();
  1696 		$automatic_updates = new WP_Site_Health_Auto_Updates();
  1495 		}
  1726 		}
  1496 
  1727 
  1497 		$output .= '</ul>';
  1728 		$output .= '</ul>';
  1498 
  1729 
  1499 		if ( 'good' !== $result['status'] ) {
  1730 		if ( 'good' !== $result['status'] ) {
       
  1731 			$result['description'] .= $output;
       
  1732 		}
       
  1733 
       
  1734 		return $result;
       
  1735 	}
       
  1736 
       
  1737 	/**
       
  1738 	 * Test if plugin and theme auto-updates appear to be configured correctly.
       
  1739 	 *
       
  1740 	 * @since 5.5.0
       
  1741 	 *
       
  1742 	 * @return array The test results.
       
  1743 	 */
       
  1744 	public function get_test_plugin_theme_auto_updates() {
       
  1745 		$result = array(
       
  1746 			'label'       => __( 'Plugin and theme auto-updates appear to be configured correctly' ),
       
  1747 			'status'      => 'good',
       
  1748 			'badge'       => array(
       
  1749 				'label' => __( 'Security' ),
       
  1750 				'color' => 'blue',
       
  1751 			),
       
  1752 			'description' => sprintf(
       
  1753 				'<p>%s</p>',
       
  1754 				__( 'Plugin and theme auto-updates ensure that the latest versions are always installed.' )
       
  1755 			),
       
  1756 			'actions'     => '',
       
  1757 			'test'        => 'plugin_theme_auto_updates',
       
  1758 		);
       
  1759 
       
  1760 		$check_plugin_theme_updates = $this->detect_plugin_theme_auto_update_issues();
       
  1761 
       
  1762 		$result['status'] = $check_plugin_theme_updates->status;
       
  1763 
       
  1764 		if ( 'good' !== $result['status'] ) {
       
  1765 			$result['label'] = __( 'Your site may have problems auto-updating plugins and themes' );
       
  1766 
  1500 			$result['description'] .= sprintf(
  1767 			$result['description'] .= sprintf(
  1501 				'<p>%s</p>',
  1768 				'<p>%s</p>',
  1502 				$output
  1769 				$check_plugin_theme_updates->message
  1503 			);
  1770 			);
  1504 		}
  1771 		}
  1505 
  1772 
  1506 		return $result;
  1773 		return $result;
  1507 	}
  1774 	}
  1508 
  1775 
  1509 	/**
  1776 	/**
  1510 	 * Test if loopbacks work as expected.
  1777 	 * Test if loopbacks work as expected.
  1511 	 *
  1778 	 *
  1512 	 * A loopback is when WordPress queries itself, for example to start a new WP_Cron instance, or when editing a
  1779 	 * A loopback is when WordPress queries itself, for example to start a new WP_Cron instance,
  1513 	 * plugin or theme. This has shown itself to be a recurring issue as code can very easily break this interaction.
  1780 	 * or when editing a plugin or theme. This has shown itself to be a recurring issue,
       
  1781 	 * as code can very easily break this interaction.
  1514 	 *
  1782 	 *
  1515 	 * @since 5.2.0
  1783 	 * @since 5.2.0
  1516 	 *
  1784 	 *
  1517 	 * @return array The test results.
  1785 	 * @return array The test results.
  1518 	 */
  1786 	 */
  1534 
  1802 
  1535 		$check_loopback = $this->can_perform_loopback();
  1803 		$check_loopback = $this->can_perform_loopback();
  1536 
  1804 
  1537 		$result['status'] = $check_loopback->status;
  1805 		$result['status'] = $check_loopback->status;
  1538 
  1806 
  1539 		if ( 'good' !== $check_loopback->status ) {
  1807 		if ( 'good' !== $result['status'] ) {
  1540 			$result['label'] = __( 'Your site could not complete a loopback request' );
  1808 			$result['label'] = __( 'Your site could not complete a loopback request' );
  1541 
  1809 
  1542 			$result['description'] .= sprintf(
  1810 			$result['description'] .= sprintf(
  1543 				'<p>%s</p>',
  1811 				'<p>%s</p>',
  1544 				$check_loopback->message
  1812 				$check_loopback->message
  1549 	}
  1817 	}
  1550 
  1818 
  1551 	/**
  1819 	/**
  1552 	 * Test if HTTP requests are blocked.
  1820 	 * Test if HTTP requests are blocked.
  1553 	 *
  1821 	 *
  1554 	 * It's possible to block all outgoing communication (with the possibility of whitelisting hosts) via the
  1822 	 * It's possible to block all outgoing communication (with the possibility of allowing certain
  1555 	 * HTTP API. This may create problems for users as many features are running as services these days.
  1823 	 * hosts) via the HTTP API. This may create problems for users as many features are running as
       
  1824 	 * services these days.
  1556 	 *
  1825 	 *
  1557 	 * @since 5.2.0
  1826 	 * @since 5.2.0
  1558 	 *
  1827 	 *
  1559 	 * @return array The test results.
  1828 	 * @return array The test results.
  1560 	 */
  1829 	 */
  1606 			$result['label'] = __( 'HTTP requests are partially blocked' );
  1875 			$result['label'] = __( 'HTTP requests are partially blocked' );
  1607 
  1876 
  1608 			$result['description'] .= sprintf(
  1877 			$result['description'] .= sprintf(
  1609 				'<p>%s</p>',
  1878 				'<p>%s</p>',
  1610 				sprintf(
  1879 				sprintf(
  1611 					/* translators: 1: Name of the constant used. 2: List of hostnames whitelisted. */
  1880 					/* translators: 1: Name of the constant used. 2: List of allowed hostnames. */
  1612 					__( 'HTTP requests have been blocked by the %1$s constant, with some hosts whitelisted: %2$s.' ),
  1881 					__( 'HTTP requests have been blocked by the %1$s constant, with some allowed hosts: %2$s.' ),
  1613 					'<code>WP_HTTP_BLOCK_EXTERNAL</code>',
  1882 					'<code>WP_HTTP_BLOCK_EXTERNAL</code>',
  1614 					implode( ',', $hosts )
  1883 					implode( ',', $hosts )
  1615 				)
  1884 				)
  1616 			);
  1885 			);
  1617 		}
  1886 		}
  1649 		$timeout = 10;
  1918 		$timeout = 10;
  1650 		$headers = array(
  1919 		$headers = array(
  1651 			'Cache-Control' => 'no-cache',
  1920 			'Cache-Control' => 'no-cache',
  1652 			'X-WP-Nonce'    => wp_create_nonce( 'wp_rest' ),
  1921 			'X-WP-Nonce'    => wp_create_nonce( 'wp_rest' ),
  1653 		);
  1922 		);
       
  1923 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
       
  1924 		$sslverify = apply_filters( 'https_local_ssl_verify', false );
  1654 
  1925 
  1655 		// Include Basic auth in loopback requests.
  1926 		// Include Basic auth in loopback requests.
  1656 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
  1927 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
  1657 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
  1928 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
  1658 		}
  1929 		}
  1665 				'context' => 'edit',
  1936 				'context' => 'edit',
  1666 			),
  1937 			),
  1667 			$url
  1938 			$url
  1668 		);
  1939 		);
  1669 
  1940 
  1670 		$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
  1941 		$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
  1671 
  1942 
  1672 		if ( is_wp_error( $r ) ) {
  1943 		if ( is_wp_error( $r ) ) {
  1673 			$result['status'] = 'critical';
  1944 			$result['status'] = 'critical';
  1674 
  1945 
  1675 			$result['label'] = __( 'The REST API encountered an error' );
  1946 			$result['label'] = __( 'The REST API encountered an error' );
  1678 				'<p>%s</p>',
  1949 				'<p>%s</p>',
  1679 				sprintf(
  1950 				sprintf(
  1680 					'%s<br>%s',
  1951 					'%s<br>%s',
  1681 					__( 'The REST API request failed due to an error.' ),
  1952 					__( 'The REST API request failed due to an error.' ),
  1682 					sprintf(
  1953 					sprintf(
  1683 						/* translators: 1: The HTTP response code. 2: The error message returned. */
  1954 						/* translators: 1: The WordPress error message. 2: The WordPress error code. */
  1684 						__( 'Error: [%1$s] %2$s' ),
  1955 						__( 'Error: %1$s (%2$s)' ),
  1685 						wp_remote_retrieve_response_code( $r ),
  1956 						$r->get_error_message(),
  1686 						$r->get_error_message()
  1957 						$r->get_error_code()
  1687 					)
  1958 					)
  1688 				)
  1959 				)
  1689 			);
  1960 			);
  1690 		} elseif ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
  1961 		} elseif ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
  1691 			$result['status'] = 'recommended';
  1962 			$result['status'] = 'recommended';
  1693 			$result['label'] = __( 'The REST API encountered an unexpected result' );
  1964 			$result['label'] = __( 'The REST API encountered an unexpected result' );
  1694 
  1965 
  1695 			$result['description'] .= sprintf(
  1966 			$result['description'] .= sprintf(
  1696 				'<p>%s</p>',
  1967 				'<p>%s</p>',
  1697 				sprintf(
  1968 				sprintf(
  1698 					/* translators: 1: The HTTP response code returned. 2: The error message returned. */
  1969 					/* translators: 1: The HTTP error code. 2: The HTTP error message. */
  1699 					__( 'The REST API call gave the following unexpected result: (%1$d) %2$s.' ),
  1970 					__( 'The REST API call gave the following unexpected result: (%1$d) %2$s.' ),
  1700 					wp_remote_retrieve_response_code( $r ),
  1971 					wp_remote_retrieve_response_code( $r ),
  1701 					wp_remote_retrieve_body( $r )
  1972 					esc_html( wp_remote_retrieve_body( $r ) )
  1702 				)
  1973 				)
  1703 			);
  1974 			);
  1704 		} else {
  1975 		} else {
  1705 			$json = json_decode( wp_remote_retrieve_body( $r ), true );
  1976 			$json = json_decode( wp_remote_retrieve_body( $r ), true );
  1706 
  1977 
  1710 				$result['label'] = __( 'The REST API did not behave correctly' );
  1981 				$result['label'] = __( 'The REST API did not behave correctly' );
  1711 
  1982 
  1712 				$result['description'] .= sprintf(
  1983 				$result['description'] .= sprintf(
  1713 					'<p>%s</p>',
  1984 					'<p>%s</p>',
  1714 					sprintf(
  1985 					sprintf(
  1715 						/* translators: %s: the name of the query parameter being tested. */
  1986 						/* translators: %s: The name of the query parameter being tested. */
  1716 						__( 'The REST API did not process the %s query parameter correctly.' ),
  1987 						__( 'The REST API did not process the %s query parameter correctly.' ),
  1717 						'<code>context</code>'
  1988 						'<code>context</code>'
  1718 					)
  1989 					)
  1719 				);
  1990 				);
  1720 			}
  1991 			}
  1722 
  1993 
  1723 		return $result;
  1994 		return $result;
  1724 	}
  1995 	}
  1725 
  1996 
  1726 	/**
  1997 	/**
       
  1998 	 * Test if 'file_uploads' directive in PHP.ini is turned off.
       
  1999 	 *
       
  2000 	 * @since 5.5.0
       
  2001 	 *
       
  2002 	 * @return array The test results.
       
  2003 	 */
       
  2004 	public function get_test_file_uploads() {
       
  2005 		$result = array(
       
  2006 			'label'       => __( 'Files can be uploaded.' ),
       
  2007 			'status'      => 'good',
       
  2008 			'badge'       => array(
       
  2009 				'label' => __( 'Performance' ),
       
  2010 				'color' => 'blue',
       
  2011 			),
       
  2012 			'description' => sprintf(
       
  2013 				'<p>%s</p>',
       
  2014 				sprintf(
       
  2015 					/* translators: 1: file_uploads, 2: php.ini */
       
  2016 					__( 'The %1$s directive in %2$s determines if uploading files is allowed on your site.' ),
       
  2017 					'<code>file_uploads</code>',
       
  2018 					'<code>php.ini</code>'
       
  2019 				)
       
  2020 			),
       
  2021 			'actions'     => '',
       
  2022 			'test'        => 'file_uploads',
       
  2023 		);
       
  2024 
       
  2025 		if ( ! function_exists( 'ini_get' ) ) {
       
  2026 			$result['status']       = 'critical';
       
  2027 			$result['description'] .= sprintf(
       
  2028 				/* translators: %s: ini_get() */
       
  2029 				__( 'The %s function has been disabled, some media settings are unavailable because of this.' ),
       
  2030 				'<code>ini_get()</code>'
       
  2031 			);
       
  2032 			return $result;
       
  2033 		}
       
  2034 
       
  2035 		if ( empty( ini_get( 'file_uploads' ) ) ) {
       
  2036 			$result['status']       = 'critical';
       
  2037 			$result['description'] .= sprintf(
       
  2038 				'<p>%s</p>',
       
  2039 				sprintf(
       
  2040 					/* translators: 1: file_uploads, 2: 0 */
       
  2041 					__( '%1$s is set to %2$s. You won\'t be able to upload files on your site.' ),
       
  2042 					'<code>file_uploads</code>',
       
  2043 					'<code>0</code>'
       
  2044 				)
       
  2045 			);
       
  2046 			return $result;
       
  2047 		}
       
  2048 
       
  2049 		$post_max_size       = ini_get( 'post_max_size' );
       
  2050 		$upload_max_filesize = ini_get( 'upload_max_filesize' );
       
  2051 
       
  2052 		if ( wp_convert_hr_to_bytes( $post_max_size ) < wp_convert_hr_to_bytes( $upload_max_filesize ) ) {
       
  2053 			$result['label'] = sprintf(
       
  2054 				/* translators: 1: post_max_size, 2: upload_max_filesize */
       
  2055 				__( 'The "%1$s" value is smaller than "%2$s".' ),
       
  2056 				'post_max_size',
       
  2057 				'upload_max_filesize'
       
  2058 			);
       
  2059 			$result['status']      = 'recommended';
       
  2060 			$result['description'] = sprintf(
       
  2061 				'<p>%s</p>',
       
  2062 				sprintf(
       
  2063 					/* translators: 1: post_max_size, 2: upload_max_filesize */
       
  2064 					__( 'The setting for %1$s is smaller than %2$s, this could cause some problems when trying to upload files.' ),
       
  2065 					'<code>post_max_size</code>',
       
  2066 					'<code>upload_max_filesize</code>'
       
  2067 				)
       
  2068 			);
       
  2069 			return $result;
       
  2070 		}
       
  2071 
       
  2072 		return $result;
       
  2073 	}
       
  2074 
       
  2075 	/**
  1727 	 * Return a set of tests that belong to the site status page.
  2076 	 * Return a set of tests that belong to the site status page.
  1728 	 *
  2077 	 *
  1729 	 * Each site status test is defined here, they may be `direct` tests, that run on page load, or `async` tests
  2078 	 * Each site status test is defined here, they may be `direct` tests, that run on page load, or `async` tests
  1730 	 * which will run later down the line via JavaScript calls to improve page performance and hopefully also user
  2079 	 * which will run later down the line via JavaScript calls to improve page performance and hopefully also user
  1731 	 * experiences.
  2080 	 * experiences.
  1735 	 * @return array The list of tests to run.
  2084 	 * @return array The list of tests to run.
  1736 	 */
  2085 	 */
  1737 	public static function get_tests() {
  2086 	public static function get_tests() {
  1738 		$tests = array(
  2087 		$tests = array(
  1739 			'direct' => array(
  2088 			'direct' => array(
  1740 				'wordpress_version' => array(
  2089 				'wordpress_version'         => array(
  1741 					'label' => __( 'WordPress Version' ),
  2090 					'label' => __( 'WordPress Version' ),
  1742 					'test'  => 'wordpress_version',
  2091 					'test'  => 'wordpress_version',
  1743 				),
  2092 				),
  1744 				'plugin_version'    => array(
  2093 				'plugin_version'            => array(
  1745 					'label' => __( 'Plugin Versions' ),
  2094 					'label' => __( 'Plugin Versions' ),
  1746 					'test'  => 'plugin_version',
  2095 					'test'  => 'plugin_version',
  1747 				),
  2096 				),
  1748 				'theme_version'     => array(
  2097 				'theme_version'             => array(
  1749 					'label' => __( 'Theme Versions' ),
  2098 					'label' => __( 'Theme Versions' ),
  1750 					'test'  => 'theme_version',
  2099 					'test'  => 'theme_version',
  1751 				),
  2100 				),
  1752 				'php_version'       => array(
  2101 				'php_version'               => array(
  1753 					'label' => __( 'PHP Version' ),
  2102 					'label' => __( 'PHP Version' ),
  1754 					'test'  => 'php_version',
  2103 					'test'  => 'php_version',
  1755 				),
  2104 				),
  1756 				'sql_server'        => array(
  2105 				'php_extensions'            => array(
       
  2106 					'label' => __( 'PHP Extensions' ),
       
  2107 					'test'  => 'php_extensions',
       
  2108 				),
       
  2109 				'php_default_timezone'      => array(
       
  2110 					'label' => __( 'PHP Default Timezone' ),
       
  2111 					'test'  => 'php_default_timezone',
       
  2112 				),
       
  2113 				'php_sessions'              => array(
       
  2114 					'label' => __( 'PHP Sessions' ),
       
  2115 					'test'  => 'php_sessions',
       
  2116 				),
       
  2117 				'sql_server'                => array(
  1757 					'label' => __( 'Database Server version' ),
  2118 					'label' => __( 'Database Server version' ),
  1758 					'test'  => 'sql_server',
  2119 					'test'  => 'sql_server',
  1759 				),
  2120 				),
  1760 				'php_extensions'    => array(
  2121 				'utf8mb4_support'           => array(
  1761 					'label' => __( 'PHP Extensions' ),
       
  1762 					'test'  => 'php_extensions',
       
  1763 				),
       
  1764 				'utf8mb4_support'   => array(
       
  1765 					'label' => __( 'MySQL utf8mb4 support' ),
  2122 					'label' => __( 'MySQL utf8mb4 support' ),
  1766 					'test'  => 'utf8mb4_support',
  2123 					'test'  => 'utf8mb4_support',
  1767 				),
  2124 				),
  1768 				'https_status'      => array(
  2125 				'https_status'              => array(
  1769 					'label' => __( 'HTTPS status' ),
  2126 					'label' => __( 'HTTPS status' ),
  1770 					'test'  => 'https_status',
  2127 					'test'  => 'https_status',
  1771 				),
  2128 				),
  1772 				'ssl_support'       => array(
  2129 				'ssl_support'               => array(
  1773 					'label' => __( 'Secure communication' ),
  2130 					'label' => __( 'Secure communication' ),
  1774 					'test'  => 'ssl_support',
  2131 					'test'  => 'ssl_support',
  1775 				),
  2132 				),
  1776 				'scheduled_events'  => array(
  2133 				'scheduled_events'          => array(
  1777 					'label' => __( 'Scheduled events' ),
  2134 					'label' => __( 'Scheduled events' ),
  1778 					'test'  => 'scheduled_events',
  2135 					'test'  => 'scheduled_events',
  1779 				),
  2136 				),
  1780 				'http_requests'     => array(
  2137 				'http_requests'             => array(
  1781 					'label' => __( 'HTTP Requests' ),
  2138 					'label' => __( 'HTTP Requests' ),
  1782 					'test'  => 'http_requests',
  2139 					'test'  => 'http_requests',
  1783 				),
  2140 				),
  1784 				'debug_enabled'     => array(
  2141 				'debug_enabled'             => array(
  1785 					'label' => __( 'Debugging enabled' ),
  2142 					'label' => __( 'Debugging enabled' ),
  1786 					'test'  => 'is_in_debug_mode',
  2143 					'test'  => 'is_in_debug_mode',
       
  2144 				),
       
  2145 				'file_uploads'              => array(
       
  2146 					'label' => __( 'File uploads' ),
       
  2147 					'test'  => 'file_uploads',
       
  2148 				),
       
  2149 				'plugin_theme_auto_updates' => array(
       
  2150 					'label' => __( 'Plugin and theme auto-updates' ),
       
  2151 					'test'  => 'plugin_theme_auto_updates',
  1787 				),
  2152 				),
  1788 			),
  2153 			),
  1789 			'async'  => array(
  2154 			'async'  => array(
  1790 				'dotorg_communication' => array(
  2155 				'dotorg_communication' => array(
  1791 					'label' => __( 'Communication with WordPress.org' ),
  2156 					'label' => __( 'Communication with WordPress.org' ),
  1825 		 *
  2190 		 *
  1826 		 * @since 5.2.0
  2191 		 * @since 5.2.0
  1827 		 *
  2192 		 *
  1828 		 * @param array $test_type {
  2193 		 * @param array $test_type {
  1829 		 *     An associative array, where the `$test_type` is either `direct` or
  2194 		 *     An associative array, where the `$test_type` is either `direct` or
  1830 		 *     `async`, to declare if the test should run via AJAX calls after page load.
  2195 		 *     `async`, to declare if the test should run via Ajax calls after page load.
  1831 		 *
  2196 		 *
  1832 		 *     @type array $identifier {
  2197 		 *     @type array $identifier {
  1833 		 *         `$identifier` should be a unique identifier for the test that should run.
  2198 		 *         `$identifier` should be a unique identifier for the test that should run.
  1834 		 *         Plugins and themes are encouraged to prefix test identifiers with their slug
  2199 		 *         Plugins and themes are encouraged to prefix test identifiers with their slug
  1835 		 *         to avoid any collisions between tests.
  2200 		 *         to avoid any collisions between tests.
  1836 		 *
  2201 		 *
  1837 		 *         @type string $label A friendly label for your test to identify it by.
  2202 		 *         @type string $label A friendly label for your test to identify it by.
  1838 		 *         @type mixed  $test  A callable to perform a direct test, or a string AJAX action to be called
  2203 		 *         @type mixed  $test  A callable to perform a direct test, or a string Ajax action to be called
  1839 		 *                             to perform an async test.
  2204 		 *                             to perform an async test.
  1840 		 *     }
  2205 		 *     }
  1841 		 * }
  2206 		 * }
  1842 		 */
  2207 		 */
  1843 		$tests = apply_filters( 'site_status_tests', $tests );
  2208 		$tests = apply_filters( 'site_status_tests', $tests );
  1844 
  2209 
       
  2210 		// Ensure that the filtered tests contain the required array keys.
       
  2211 		$tests = array_merge(
       
  2212 			array(
       
  2213 				'direct' => array(),
       
  2214 				'async'  => array(),
       
  2215 			),
       
  2216 			$tests
       
  2217 		);
       
  2218 
  1845 		return $tests;
  2219 		return $tests;
  1846 	}
  2220 	}
  1847 
  2221 
  1848 	/**
  2222 	/**
  1849 	 * Add a class to the body HTML tag.
  2223 	 * Add a class to the body HTML tag.
  1854 	 *
  2228 	 *
  1855 	 * @param string $body_class The body class string.
  2229 	 * @param string $body_class The body class string.
  1856 	 * @return string The modified body class string.
  2230 	 * @return string The modified body class string.
  1857 	 */
  2231 	 */
  1858 	public function admin_body_class( $body_class ) {
  2232 	public function admin_body_class( $body_class ) {
       
  2233 		$screen = get_current_screen();
       
  2234 		if ( 'site-health' !== $screen->id ) {
       
  2235 			return $body_class;
       
  2236 		}
       
  2237 
  1859 		$body_class .= ' site-health';
  2238 		$body_class .= ' site-health';
  1860 
  2239 
  1861 		return $body_class;
  2240 		return $body_class;
  1862 	}
  2241 	}
  1863 
  2242 
  1905 	}
  2284 	}
  1906 
  2285 
  1907 	/**
  2286 	/**
  1908 	 * Check if any scheduled tasks have been missed.
  2287 	 * Check if any scheduled tasks have been missed.
  1909 	 *
  2288 	 *
  1910 	 * Returns a boolean value of `true` if a scheduled task has been missed and ends processing. If the list of
  2289 	 * Returns a boolean value of `true` if a scheduled task has been missed and ends processing.
  1911 	 * crons is an instance of WP_Error, return the instance instead of a boolean value.
  2290 	 *
  1912 	 *
  2291 	 * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value.
  1913 	 * @since 5.2.0
  2292 	 *
  1914 	 *
  2293 	 * @since 5.2.0
  1915 	 * @return bool|WP_Error true if a cron was missed, false if it wasn't. WP_Error if the cron is set to that.
  2294 	 *
       
  2295 	 * @return bool|WP_Error True if a cron was missed, false if not. WP_Error if the cron is set to that.
  1916 	 */
  2296 	 */
  1917 	public function has_missed_cron() {
  2297 	public function has_missed_cron() {
  1918 		if ( is_wp_error( $this->crons ) ) {
  2298 		if ( is_wp_error( $this->crons ) ) {
  1919 			return $this->crons;
  2299 			return $this->crons;
  1920 		}
  2300 		}
  1921 
  2301 
  1922 		foreach ( $this->crons as $id => $cron ) {
  2302 		foreach ( $this->crons as $id => $cron ) {
  1923 			if ( ( $cron->time - time() ) < 0 ) {
  2303 			if ( ( $cron->time - time() ) < $this->timeout_missed_cron ) {
  1924 				$this->last_missed_cron = $cron->hook;
  2304 				$this->last_missed_cron = $cron->hook;
  1925 				return true;
  2305 				return true;
  1926 			}
  2306 			}
  1927 		}
  2307 		}
  1928 
  2308 
  1929 		return false;
  2309 		return false;
       
  2310 	}
       
  2311 
       
  2312 	/**
       
  2313 	 * Check if any scheduled tasks are late.
       
  2314 	 *
       
  2315 	 * Returns a boolean value of `true` if a scheduled task is late and ends processing.
       
  2316 	 *
       
  2317 	 * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value.
       
  2318 	 *
       
  2319 	 * @since 5.3.0
       
  2320 	 *
       
  2321 	 * @return bool|WP_Error True if a cron is late, false if not. WP_Error if the cron is set to that.
       
  2322 	 */
       
  2323 	public function has_late_cron() {
       
  2324 		if ( is_wp_error( $this->crons ) ) {
       
  2325 			return $this->crons;
       
  2326 		}
       
  2327 
       
  2328 		foreach ( $this->crons as $id => $cron ) {
       
  2329 			$cron_offset = $cron->time - time();
       
  2330 			if (
       
  2331 				$cron_offset >= $this->timeout_missed_cron &&
       
  2332 				$cron_offset < $this->timeout_late_cron
       
  2333 			) {
       
  2334 				$this->last_late_cron = $cron->hook;
       
  2335 				return true;
       
  2336 			}
       
  2337 		}
       
  2338 
       
  2339 		return false;
       
  2340 	}
       
  2341 
       
  2342 	/**
       
  2343 	 * Check for potential issues with plugin and theme auto-updates.
       
  2344 	 *
       
  2345 	 * Though there is no way to 100% determine if plugin and theme auto-updates are configured
       
  2346 	 * correctly, a few educated guesses could be made to flag any conditions that would
       
  2347 	 * potentially cause unexpected behaviors.
       
  2348 	 *
       
  2349 	 * @since 5.5.0
       
  2350 	 *
       
  2351 	 * @return object The test results.
       
  2352 	 */
       
  2353 	function detect_plugin_theme_auto_update_issues() {
       
  2354 		$mock_plugin = (object) array(
       
  2355 			'id'            => 'w.org/plugins/a-fake-plugin',
       
  2356 			'slug'          => 'a-fake-plugin',
       
  2357 			'plugin'        => 'a-fake-plugin/a-fake-plugin.php',
       
  2358 			'new_version'   => '9.9',
       
  2359 			'url'           => 'https://wordpress.org/plugins/a-fake-plugin/',
       
  2360 			'package'       => 'https://downloads.wordpress.org/plugin/a-fake-plugin.9.9.zip',
       
  2361 			'icons'         => array(
       
  2362 				'2x' => 'https://ps.w.org/a-fake-plugin/assets/icon-256x256.png',
       
  2363 				'1x' => 'https://ps.w.org/a-fake-plugin/assets/icon-128x128.png',
       
  2364 			),
       
  2365 			'banners'       => array(
       
  2366 				'2x' => 'https://ps.w.org/a-fake-plugin/assets/banner-1544x500.png',
       
  2367 				'1x' => 'https://ps.w.org/a-fake-plugin/assets/banner-772x250.png',
       
  2368 			),
       
  2369 			'banners_rtl'   => array(),
       
  2370 			'tested'        => '5.5.0',
       
  2371 			'requires_php'  => '5.6.20',
       
  2372 			'compatibility' => new stdClass(),
       
  2373 		);
       
  2374 
       
  2375 		$mock_theme = (object) array(
       
  2376 			'theme'        => 'a-fake-theme',
       
  2377 			'new_version'  => '9.9',
       
  2378 			'url'          => 'https://wordpress.org/themes/a-fake-theme/',
       
  2379 			'package'      => 'https://downloads.wordpress.org/theme/a-fake-theme.9.9.zip',
       
  2380 			'requires'     => '5.0.0',
       
  2381 			'requires_php' => '5.6.20',
       
  2382 		);
       
  2383 
       
  2384 		$type = 'plugin';
       
  2385 		/** This filter is documented in wp-admin/includes/class-wp-automatic-updater.php */
       
  2386 		$test_plugins_enabled = apply_filters( "auto_update_{$type}", true, $mock_plugin );
       
  2387 
       
  2388 		$type = 'theme';
       
  2389 		/** This filter is documented in wp-admin/includes/class-wp-automatic-updater.php */
       
  2390 		$test_themes_enabled = apply_filters( "auto_update_{$type}", true, $mock_theme );
       
  2391 
       
  2392 		$ui_enabled_for_plugins = wp_is_auto_update_enabled_for_type( 'plugin' );
       
  2393 		$ui_enabled_for_themes  = wp_is_auto_update_enabled_for_type( 'theme' );
       
  2394 		$plugin_filter_present  = has_filter( 'auto_update_plugin' );
       
  2395 		$theme_filter_present   = has_filter( 'auto_update_theme' );
       
  2396 
       
  2397 		if ( ( ! $test_plugins_enabled && $ui_enabled_for_plugins )
       
  2398 			|| ( ! $test_themes_enabled && $ui_enabled_for_themes )
       
  2399 		) {
       
  2400 			return (object) array(
       
  2401 				'status'  => 'critical',
       
  2402 				'message' => __( 'Auto-updates for plugins and/or themes appear to be disabled, but settings are still set to be displayed. This could cause auto-updates to not work as expected.' ),
       
  2403 			);
       
  2404 		}
       
  2405 
       
  2406 		if ( ( ! $test_plugins_enabled && $plugin_filter_present )
       
  2407 			&& ( ! $test_themes_enabled && $theme_filter_present )
       
  2408 		) {
       
  2409 			return (object) array(
       
  2410 				'status'  => 'recommended',
       
  2411 				'message' => __( 'Auto-updates for plugins and themes appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ),
       
  2412 			);
       
  2413 		} elseif ( ! $test_plugins_enabled && $plugin_filter_present ) {
       
  2414 			return (object) array(
       
  2415 				'status'  => 'recommended',
       
  2416 				'message' => __( 'Auto-updates for plugins appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ),
       
  2417 			);
       
  2418 		} elseif ( ! $test_themes_enabled && $theme_filter_present ) {
       
  2419 			return (object) array(
       
  2420 				'status'  => 'recommended',
       
  2421 				'message' => __( 'Auto-updates for themes appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ),
       
  2422 			);
       
  2423 		}
       
  2424 
       
  2425 		return (object) array(
       
  2426 			'status'  => 'good',
       
  2427 			'message' => __( 'There appear to be no issues with plugin and theme auto-updates.' ),
       
  2428 		);
  1930 	}
  2429 	}
  1931 
  2430 
  1932 	/**
  2431 	/**
  1933 	 * Run a loopback test on our site.
  2432 	 * Run a loopback test on our site.
  1934 	 *
  2433 	 *
  1943 		$cookies = wp_unslash( $_COOKIE );
  2442 		$cookies = wp_unslash( $_COOKIE );
  1944 		$timeout = 10;
  2443 		$timeout = 10;
  1945 		$headers = array(
  2444 		$headers = array(
  1946 			'Cache-Control' => 'no-cache',
  2445 			'Cache-Control' => 'no-cache',
  1947 		);
  2446 		);
       
  2447 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
       
  2448 		$sslverify = apply_filters( 'https_local_ssl_verify', false );
  1948 
  2449 
  1949 		// Include Basic auth in loopback requests.
  2450 		// Include Basic auth in loopback requests.
  1950 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
  2451 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
  1951 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
  2452 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
  1952 		}
  2453 		}
  1953 
  2454 
  1954 		$url = admin_url();
  2455 		$url = admin_url();
  1955 
  2456 
  1956 		$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
  2457 		$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
  1957 
  2458 
  1958 		if ( is_wp_error( $r ) ) {
  2459 		if ( is_wp_error( $r ) ) {
  1959 			return (object) array(
  2460 			return (object) array(
  1960 				'status'  => 'critical',
  2461 				'status'  => 'critical',
  1961 				'message' => sprintf(
  2462 				'message' => sprintf(
  1962 					'%s<br>%s',
  2463 					'%s<br>%s',
  1963 					__( 'The loopback request to your site failed, this means features relying on them are not currently working as expected.' ),
  2464 					__( 'The loopback request to your site failed, this means features relying on them are not currently working as expected.' ),
  1964 					sprintf(
  2465 					sprintf(
  1965 						// translators: 1: The HTTP response code. 2: The error message returned.
  2466 						/* translators: 1: The WordPress error message. 2: The WordPress error code. */
  1966 						__( 'Error: [%1$s] %2$s' ),
  2467 						__( 'Error: %1$s (%2$s)' ),
  1967 						wp_remote_retrieve_response_code( $r ),
  2468 						$r->get_error_message(),
  1968 						$r->get_error_message()
  2469 						$r->get_error_code()
  1969 					)
  2470 					)
  1970 				),
  2471 				),
  1971 			);
  2472 			);
  1972 		}
  2473 		}
  1973 
  2474 
  1974 		if ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
  2475 		if ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
  1975 			return (object) array(
  2476 			return (object) array(
  1976 				'status'  => 'recommended',
  2477 				'status'  => 'recommended',
  1977 				'message' => sprintf(
  2478 				'message' => sprintf(
  1978 					// translators: %d: The HTTP response code returned.
  2479 					/* translators: %d: The HTTP response code returned. */
  1979 					__( 'The loopback request returned an unexpected http status code, %d, it was not possible to determine if this will prevent features from working as expected.' ),
  2480 					__( 'The loopback request returned an unexpected http status code, %d, it was not possible to determine if this will prevent features from working as expected.' ),
  1980 					wp_remote_retrieve_response_code( $r )
  2481 					wp_remote_retrieve_response_code( $r )
  1981 				),
  2482 				),
  1982 			);
  2483 			);
  1983 		}
  2484 		}
  1985 		return (object) array(
  2486 		return (object) array(
  1986 			'status'  => 'good',
  2487 			'status'  => 'good',
  1987 			'message' => __( 'The loopback request to your site completed successfully.' ),
  2488 			'message' => __( 'The loopback request to your site completed successfully.' ),
  1988 		);
  2489 		);
  1989 	}
  2490 	}
       
  2491 
       
  2492 	/**
       
  2493 	 * Create a weekly cron event, if one does not already exist.
       
  2494 	 *
       
  2495 	 * @since 5.4.0
       
  2496 	 */
       
  2497 	public function maybe_create_scheduled_event() {
       
  2498 		if ( ! wp_next_scheduled( 'wp_site_health_scheduled_check' ) && ! wp_installing() ) {
       
  2499 			wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', 'wp_site_health_scheduled_check' );
       
  2500 		}
       
  2501 	}
       
  2502 
       
  2503 	/**
       
  2504 	 * Run our scheduled event to check and update the latest site health status for the website.
       
  2505 	 *
       
  2506 	 * @since 5.4.0
       
  2507 	 */
       
  2508 	public function wp_cron_scheduled_check() {
       
  2509 		// Bootstrap wp-admin, as WP_Cron doesn't do this for us.
       
  2510 		require_once trailingslashit( ABSPATH ) . 'wp-admin/includes/admin.php';
       
  2511 
       
  2512 		$tests = WP_Site_Health::get_tests();
       
  2513 
       
  2514 		$results = array();
       
  2515 
       
  2516 		$site_status = array(
       
  2517 			'good'        => 0,
       
  2518 			'recommended' => 0,
       
  2519 			'critical'    => 0,
       
  2520 		);
       
  2521 
       
  2522 		// Don't run https test on localhost.
       
  2523 		if ( 'localhost' === preg_replace( '|https?://|', '', get_site_url() ) ) {
       
  2524 			unset( $tests['direct']['https_status'] );
       
  2525 		}
       
  2526 
       
  2527 		foreach ( $tests['direct'] as $test ) {
       
  2528 
       
  2529 			if ( is_string( $test['test'] ) ) {
       
  2530 				$test_function = sprintf(
       
  2531 					'get_test_%s',
       
  2532 					$test['test']
       
  2533 				);
       
  2534 
       
  2535 				if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) {
       
  2536 					$results[] = $this->perform_test( array( $this, $test_function ) );
       
  2537 					continue;
       
  2538 				}
       
  2539 			}
       
  2540 
       
  2541 			if ( is_callable( $test['test'] ) ) {
       
  2542 				$results[] = $this->perform_test( $test['test'] );
       
  2543 			}
       
  2544 		}
       
  2545 
       
  2546 		foreach ( $tests['async'] as $test ) {
       
  2547 			if ( is_string( $test['test'] ) ) {
       
  2548 				if ( isset( $test['has_rest'] ) && $test['has_rest'] ) {
       
  2549 					$result_fetch = wp_remote_post(
       
  2550 						rest_url( $test['test'] ),
       
  2551 						array(
       
  2552 							'body' => array(
       
  2553 								'_wpnonce' => wp_create_nonce( 'wp_rest' ),
       
  2554 							),
       
  2555 						)
       
  2556 					);
       
  2557 				} else {
       
  2558 					$result_fetch = wp_remote_post(
       
  2559 						admin_url( 'admin-ajax.php' ),
       
  2560 						array(
       
  2561 							'body' => array(
       
  2562 								'action'   => $test['test'],
       
  2563 								'_wpnonce' => wp_create_nonce( 'health-check-site-status' ),
       
  2564 							),
       
  2565 						)
       
  2566 					);
       
  2567 				}
       
  2568 
       
  2569 				if ( ! is_wp_error( $result_fetch ) ) {
       
  2570 					$result = json_decode( wp_remote_retrieve_body( $result_fetch ), true );
       
  2571 				} else {
       
  2572 					$result = false;
       
  2573 				}
       
  2574 
       
  2575 				if ( is_array( $result ) ) {
       
  2576 					$results[] = $result;
       
  2577 				} else {
       
  2578 					$results[] = array(
       
  2579 						'status' => 'recommended',
       
  2580 						'label'  => __( 'A test is unavailable' ),
       
  2581 					);
       
  2582 				}
       
  2583 			}
       
  2584 		}
       
  2585 
       
  2586 		foreach ( $results as $result ) {
       
  2587 			if ( 'critical' === $result['status'] ) {
       
  2588 				$site_status['critical']++;
       
  2589 			} elseif ( 'recommended' === $result['status'] ) {
       
  2590 				$site_status['recommended']++;
       
  2591 			} else {
       
  2592 				$site_status['good']++;
       
  2593 			}
       
  2594 		}
       
  2595 
       
  2596 		set_transient( 'health-check-site-status-result', wp_json_encode( $site_status ) );
       
  2597 	}
  1990 }
  2598 }