[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> class-wp-site-health.php (source)

   1  <?php
   2  /**
   3   * Class for looking up a site's health based on a user's WordPress environment.
   4   *
   5   * @package WordPress
   6   * @subpackage Site_Health
   7   * @since 5.2.0
   8   */
   9  
  10  #[AllowDynamicProperties]
  11  class WP_Site_Health {
  12      private static $instance = null;
  13  
  14      private $is_acceptable_mysql_version;
  15      private $is_recommended_mysql_version;
  16  
  17      public $is_mariadb                   = false;
  18      private $mysql_server_version        = '';
  19      private $mysql_required_version      = '5.5';
  20      private $mysql_recommended_version   = '8.0';
  21      private $mariadb_recommended_version = '10.6';
  22  
  23      public $php_memory_limit;
  24  
  25      public $schedules;
  26      public $crons;
  27      public $last_missed_cron     = null;
  28      public $last_late_cron       = null;
  29      private $timeout_missed_cron = null;
  30      private $timeout_late_cron   = null;
  31  
  32      /**
  33       * WP_Site_Health constructor.
  34       *
  35       * @since 5.2.0
  36       */
  37  	public function __construct() {
  38          $this->maybe_create_scheduled_event();
  39  
  40          // Save memory limit before it's affected by wp_raise_memory_limit( 'admin' ).
  41          $this->php_memory_limit = ini_get( 'memory_limit' );
  42  
  43          $this->timeout_late_cron   = 0;
  44          $this->timeout_missed_cron = - 5 * MINUTE_IN_SECONDS;
  45  
  46          if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
  47              $this->timeout_late_cron   = - 15 * MINUTE_IN_SECONDS;
  48              $this->timeout_missed_cron = - 1 * HOUR_IN_SECONDS;
  49          }
  50  
  51          add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
  52  
  53          add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
  54          add_action( 'wp_site_health_scheduled_check', array( $this, 'wp_cron_scheduled_check' ) );
  55  
  56          add_action( 'site_health_tab_content', array( $this, 'show_site_health_tab' ) );
  57      }
  58  
  59      /**
  60       * Outputs the content of a tab in the Site Health screen.
  61       *
  62       * @since 5.8.0
  63       *
  64       * @param string $tab Slug of the current tab being displayed.
  65       */
  66  	public function show_site_health_tab( $tab ) {
  67          if ( 'debug' === $tab ) {
  68              require_once  ABSPATH . 'wp-admin/site-health-info.php';
  69          }
  70      }
  71  
  72      /**
  73       * Returns an instance of the WP_Site_Health class, or create one if none exist yet.
  74       *
  75       * @since 5.4.0
  76       *
  77       * @return WP_Site_Health|null
  78       */
  79  	public static function get_instance() {
  80          if ( null === self::$instance ) {
  81              self::$instance = new WP_Site_Health();
  82          }
  83  
  84          return self::$instance;
  85      }
  86  
  87      /**
  88       * Enqueues the site health scripts.
  89       *
  90       * @since 5.2.0
  91       */
  92  	public function enqueue_scripts() {
  93          $screen = get_current_screen();
  94          if ( 'site-health' !== $screen->id && 'dashboard' !== $screen->id ) {
  95              return;
  96          }
  97  
  98          $health_check_js_variables = array(
  99              'screen'      => $screen->id,
 100              'nonce'       => array(
 101                  'site_status'        => wp_create_nonce( 'health-check-site-status' ),
 102                  'site_status_result' => wp_create_nonce( 'health-check-site-status-result' ),
 103              ),
 104              'site_status' => array(
 105                  'direct' => array(),
 106                  'async'  => array(),
 107                  'issues' => array(
 108                      'good'        => 0,
 109                      'recommended' => 0,
 110                      'critical'    => 0,
 111                  ),
 112              ),
 113          );
 114  
 115          $issue_counts = get_transient( 'health-check-site-status-result' );
 116  
 117          if ( false !== $issue_counts ) {
 118              $issue_counts = json_decode( $issue_counts );
 119  
 120              $health_check_js_variables['site_status']['issues'] = $issue_counts;
 121          }
 122  
 123          if ( 'site-health' === $screen->id && ( ! isset( $_GET['tab'] ) || empty( $_GET['tab'] ) ) ) {
 124              $tests = WP_Site_Health::get_tests();
 125  
 126              // Don't run https test on development environments.
 127              if ( $this->is_development_environment() ) {
 128                  unset( $tests['async']['https_status'] );
 129              }
 130  
 131              foreach ( $tests['direct'] as $test ) {
 132                  if ( is_string( $test['test'] ) ) {
 133                      $test_function = sprintf(
 134                          'get_test_%s',
 135                          $test['test']
 136                      );
 137  
 138                      if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) {
 139                          $health_check_js_variables['site_status']['direct'][] = $this->perform_test( array( $this, $test_function ) );
 140                          continue;
 141                      }
 142                  }
 143  
 144                  if ( is_callable( $test['test'] ) ) {
 145                      $health_check_js_variables['site_status']['direct'][] = $this->perform_test( $test['test'] );
 146                  }
 147              }
 148  
 149              foreach ( $tests['async'] as $test ) {
 150                  if ( is_string( $test['test'] ) ) {
 151                      $health_check_js_variables['site_status']['async'][] = array(
 152                          'test'      => $test['test'],
 153                          'has_rest'  => $test['has_rest'] ?? false,
 154                          'completed' => false,
 155                          'headers'   => $test['headers'] ?? array(),
 156                      );
 157                  }
 158              }
 159          }
 160  
 161          wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables );
 162      }
 163  
 164      /**
 165       * Runs a Site Health test directly.
 166       *
 167       * @since 5.4.0
 168       *
 169       * @param callable $callback
 170       * @return array{
 171       *     label: string,
 172       *     status: 'good'|'recommended'|'critical',
 173       *     badge: array{
 174       *         label: string,
 175       *         color: string,
 176       *     },
 177       *     description: string,
 178       *     actions: string,
 179       *     test: string,
 180       * }
 181       */
 182  	private function perform_test( $callback ) {
 183          /**
 184           * Filters the output of a finished Site Health test.
 185           *
 186           * @since 5.3.0
 187           *
 188           * @param array $test_result {
 189           *     An associative array of test result data.
 190           *
 191           *     @type string $label       A label describing the test, and is used as a header in the output.
 192           *     @type string $status      The status of the test, which can be a value of `good`, `recommended` or `critical`.
 193           *     @type array  $badge {
 194           *         Tests are put into categories which have an associated badge shown, these can be modified and assigned here.
 195           *
 196           *         @type string $label The test label, for example `Performance`.
 197           *         @type string $color Default `blue`. A string representing a color to use for the label.
 198           *     }
 199           *     @type string $description A more descriptive explanation of what the test looks for, and why it is important for the end user.
 200           *     @type string $actions     An action to direct the user to where they can resolve the issue, if one exists.
 201           *     @type string $test        The name of the test being ran, used as a reference point.
 202           * }
 203           */
 204          return apply_filters( 'site_status_test_result', call_user_func( $callback ) );
 205      }
 206  
 207      /**
 208       * Runs the SQL version checks.
 209       *
 210       * These values are used in later tests, but the part of preparing them is more easily managed
 211       * early in the class for ease of access and discovery.
 212       *
 213       * @since 5.2.0
 214       *
 215       * @global wpdb $wpdb WordPress database abstraction object.
 216       */
 217  	private function prepare_sql_data() {
 218          global $wpdb;
 219  
 220          $mysql_server_type = $wpdb->db_server_info();
 221  
 222          $this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' );
 223  
 224          if ( stristr( $mysql_server_type, 'mariadb' ) ) {
 225              $this->is_mariadb                = true;
 226              $this->mysql_recommended_version = $this->mariadb_recommended_version;
 227          }
 228  
 229          $this->is_acceptable_mysql_version  = version_compare( $this->mysql_required_version, $this->mysql_server_version, '<=' );
 230          $this->is_recommended_mysql_version = version_compare( $this->mysql_recommended_version, $this->mysql_server_version, '<=' );
 231      }
 232  
 233      /**
 234       * Tests whether `wp_version_check` is blocked.
 235       *
 236       * It's possible to block updates with the `wp_version_check` filter, but this can't be checked
 237       * during an Ajax call, as the filter is never introduced then.
 238       *
 239       * This filter overrides a standard page request if it's made by an admin through the Ajax call
 240       * with the right query argument to check for this.
 241       *
 242       * @since 5.2.0
 243       */
 244  	public function check_wp_version_check_exists() {
 245          if ( ! is_admin() || ! is_user_logged_in() || ! current_user_can( 'update_core' ) || ! isset( $_GET['health-check-test-wp_version_check'] ) ) {
 246              return;
 247          }
 248  
 249          echo ( has_filter( 'wp_version_check', 'wp_version_check' ) ? 'yes' : 'no' );
 250  
 251          die();
 252      }
 253  
 254      /**
 255       * Tests for WordPress version and outputs it.
 256       *
 257       * Gives various results depending on what kind of updates are available, if any, to encourage
 258       * the user to install security updates as a priority.
 259       *
 260       * @since 5.2.0
 261       *
 262       * @return array The test result.
 263       */
 264  	public function get_test_wordpress_version() {
 265          $result = array(
 266              'label'       => '',
 267              'status'      => '',
 268              'badge'       => array(
 269                  'label' => __( 'Performance' ),
 270                  'color' => 'blue',
 271              ),
 272              'description' => '',
 273              'actions'     => '',
 274              'test'        => 'wordpress_version',
 275          );
 276  
 277          $core_current_version = wp_get_wp_version();
 278          $core_updates         = get_core_updates();
 279  
 280          if ( ! is_array( $core_updates ) ) {
 281              $result['status'] = 'recommended';
 282  
 283              $result['label'] = sprintf(
 284                  /* translators: %s: Your current version of WordPress. */
 285                  __( 'WordPress version %s' ),
 286                  $core_current_version
 287              );
 288  
 289              $result['description'] = sprintf(
 290                  '<p>%s</p>',
 291                  __( 'Unable to check if any new versions of WordPress are available.' )
 292              );
 293  
 294              $result['actions'] = sprintf(
 295                  '<a href="%s">%s</a>',
 296                  esc_url( admin_url( 'update-core.php?force-check=1' ) ),
 297                  __( 'Check for updates manually' )
 298              );
 299          } else {
 300              foreach ( $core_updates as $core => $update ) {
 301                  if ( 'upgrade' === $update->response ) {
 302                      $current_version = explode( '.', $core_current_version );
 303                      $new_version     = explode( '.', $update->version );
 304  
 305                      $current_major = $current_version[0] . '.' . $current_version[1];
 306                      $new_major     = $new_version[0] . '.' . $new_version[1];
 307  
 308                      $result['label'] = sprintf(
 309                          /* translators: %s: The latest version of WordPress available. */
 310                          __( 'WordPress update available (%s)' ),
 311                          $update->version
 312                      );
 313  
 314                      $result['actions'] = sprintf(
 315                          '<a href="%s">%s</a>',
 316                          esc_url( admin_url( 'update-core.php' ) ),
 317                          __( 'Install the latest version of WordPress' )
 318                      );
 319  
 320                      if ( $current_major !== $new_major ) {
 321                          // This is a major version mismatch.
 322                          $result['status']      = 'recommended';
 323                          $result['description'] = sprintf(
 324                              '<p>%s</p>',
 325                              __( 'A new version of WordPress is available.' )
 326                          );
 327                      } else {
 328                          // This is a minor version, sometimes considered more critical.
 329                          $result['status']         = 'critical';
 330                          $result['badge']['label'] = __( 'Security' );
 331                          $result['description']    = sprintf(
 332                              '<p>%s</p>',
 333                              __( 'A new minor update is available for your site. Because minor updates often address security, it&#8217;s important to install them.' )
 334                          );
 335                      }
 336                  } else {
 337                      $result['status'] = 'good';
 338                      $result['label']  = sprintf(
 339                          /* translators: %s: The current version of WordPress installed on this site. */
 340                          __( 'Your version of WordPress (%s) is up to date' ),
 341                          $core_current_version
 342                      );
 343  
 344                      $result['description'] = sprintf(
 345                          '<p>%s</p>',
 346                          __( 'You are currently running the latest version of WordPress available, keep it up!' )
 347                      );
 348                  }
 349              }
 350          }
 351  
 352          return $result;
 353      }
 354  
 355      /**
 356       * Tests if plugins are outdated, or unnecessary.
 357       *
 358       * The test checks if your plugins are up to date, and encourages you to remove any
 359       * that are not in use.
 360       *
 361       * @since 5.2.0
 362       *
 363       * @return array The test result.
 364       */
 365  	public function get_test_plugin_version() {
 366          $result = array(
 367              'label'       => __( 'Your plugins are all up to date' ),
 368              'status'      => 'good',
 369              'badge'       => array(
 370                  'label' => __( 'Security' ),
 371                  'color' => 'blue',
 372              ),
 373              'description' => sprintf(
 374                  '<p>%s</p>',
 375                  __( 'Plugins extend your site&#8217;s functionality with things like contact forms, ecommerce and much more. That means they have deep access to your site, so it&#8217;s vital to keep them up to date.' )
 376              ),
 377              'actions'     => sprintf(
 378                  '<p><a href="%s">%s</a></p>',
 379                  esc_url( admin_url( 'plugins.php' ) ),
 380                  __( 'Manage your plugins' )
 381              ),
 382              'test'        => 'plugin_version',
 383          );
 384  
 385          $plugins        = get_plugins();
 386          $plugin_updates = get_plugin_updates();
 387  
 388          $plugins_active      = 0;
 389          $plugins_total       = 0;
 390          $plugins_need_update = 0;
 391  
 392          // Loop over the available plugins and check their versions and active state.
 393          foreach ( $plugins as $plugin_path => $plugin ) {
 394              ++$plugins_total;
 395  
 396              if ( is_plugin_active( $plugin_path ) ) {
 397                  ++$plugins_active;
 398              }
 399  
 400              if ( array_key_exists( $plugin_path, $plugin_updates ) ) {
 401                  ++$plugins_need_update;
 402              }
 403          }
 404  
 405          // Add a notice if there are outdated plugins.
 406          if ( $plugins_need_update > 0 ) {
 407              $result['status'] = 'critical';
 408  
 409              $result['label'] = __( 'You have plugins waiting to be updated' );
 410  
 411              $result['description'] .= sprintf(
 412                  '<p>%s</p>',
 413                  sprintf(
 414                      /* translators: %d: The number of outdated plugins. */
 415                      _n(
 416                          'Your site has %d plugin waiting to be updated.',
 417                          'Your site has %d plugins waiting to be updated.',
 418                          $plugins_need_update
 419                      ),
 420                      $plugins_need_update
 421                  )
 422              );
 423  
 424              $result['actions'] .= sprintf(
 425                  '<p><a href="%s">%s</a></p>',
 426                  esc_url( network_admin_url( 'plugins.php?plugin_status=upgrade' ) ),
 427                  __( 'Update your plugins' )
 428              );
 429          } else {
 430              if ( 1 === $plugins_active ) {
 431                  $result['description'] .= sprintf(
 432                      '<p>%s</p>',
 433                      __( 'Your site has 1 active plugin, and it is up to date.' )
 434                  );
 435              } elseif ( $plugins_active > 0 ) {
 436                  $result['description'] .= sprintf(
 437                      '<p>%s</p>',
 438                      sprintf(
 439                          /* translators: %d: The number of active plugins. */
 440                          _n(
 441                              'Your site has %d active plugin, and it is up to date.',
 442                              'Your site has %d active plugins, and they are all up to date.',
 443                              $plugins_active
 444                          ),
 445                          $plugins_active
 446                      )
 447                  );
 448              } else {
 449                  $result['description'] .= sprintf(
 450                      '<p>%s</p>',
 451                      __( 'Your site does not have any active plugins.' )
 452                  );
 453              }
 454          }
 455  
 456          // Check if there are inactive plugins.
 457          if ( $plugins_total > $plugins_active && ! is_multisite() ) {
 458              $unused_plugins = $plugins_total - $plugins_active;
 459  
 460              $result['status'] = 'recommended';
 461  
 462              $result['label'] = __( 'You should remove inactive plugins' );
 463  
 464              $result['description'] .= sprintf(
 465                  '<p>%s %s</p>',
 466                  sprintf(
 467                      /* translators: %d: The number of inactive plugins. */
 468                      _n(
 469                          'Your site has %d inactive plugin.',
 470                          'Your site has %d inactive plugins.',
 471                          $unused_plugins
 472                      ),
 473                      $unused_plugins
 474                  ),
 475                  __( 'Inactive plugins are tempting targets for attackers. If you are not going to use a plugin, you should consider removing it.' )
 476              );
 477  
 478              $result['actions'] .= sprintf(
 479                  '<p><a href="%s">%s</a></p>',
 480                  esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ),
 481                  __( 'Manage inactive plugins' )
 482              );
 483          }
 484  
 485          return $result;
 486      }
 487  
 488      /**
 489       * Tests if themes are outdated, or unnecessary.
 490       *
 491       * Checks if your site has a default theme (to fall back on if there is a need),
 492       * if your themes are up to date and, finally, encourages you to remove any themes
 493       * that are not needed.
 494       *
 495       * @since 5.2.0
 496       *
 497       * @return array The test results.
 498       */
 499  	public function get_test_theme_version() {
 500          $result = array(
 501              'label'       => __( 'Your themes are all up to date' ),
 502              'status'      => 'good',
 503              'badge'       => array(
 504                  'label' => __( 'Security' ),
 505                  'color' => 'blue',
 506              ),
 507              'description' => sprintf(
 508                  '<p>%s</p>',
 509                  __( 'Themes add your site&#8217;s look and feel. It&#8217;s important to keep them up to date, to stay consistent with your brand and keep your site secure.' )
 510              ),
 511              'actions'     => sprintf(
 512                  '<p><a href="%s">%s</a></p>',
 513                  esc_url( admin_url( 'themes.php' ) ),
 514                  __( 'Manage your themes' )
 515              ),
 516              'test'        => 'theme_version',
 517          );
 518  
 519          $theme_updates = get_theme_updates();
 520  
 521          $themes_total        = 0;
 522          $themes_need_updates = 0;
 523          $themes_inactive     = 0;
 524  
 525          // This value is changed during processing to determine how many themes are considered a reasonable amount.
 526          $allowed_theme_count = 1;
 527  
 528          $has_default_theme   = false;
 529          $has_unused_themes   = false;
 530          $show_unused_themes  = true;
 531          $using_default_theme = false;
 532  
 533          // Populate a list of all themes available in the install.
 534          $all_themes   = wp_get_themes();
 535          $active_theme = wp_get_theme();
 536  
 537          // If WP_DEFAULT_THEME doesn't exist, fall back to the latest core default theme.
 538          $default_theme = wp_get_theme( WP_DEFAULT_THEME );
 539          if ( ! $default_theme->exists() ) {
 540              $default_theme = WP_Theme::get_core_default_theme();
 541          }
 542  
 543          if ( $default_theme ) {
 544              $has_default_theme = true;
 545  
 546              if (
 547                  $active_theme->get_stylesheet() === $default_theme->get_stylesheet()
 548              ||
 549                  is_child_theme() && $active_theme->get_template() === $default_theme->get_template()
 550              ) {
 551                  $using_default_theme = true;
 552              }
 553          }
 554  
 555          foreach ( $all_themes as $theme_slug => $theme ) {
 556              ++$themes_total;
 557  
 558              if ( array_key_exists( $theme_slug, $theme_updates ) ) {
 559                  ++$themes_need_updates;
 560              }
 561          }
 562  
 563          // If this is a child theme, increase the allowed theme count by one, to account for the parent.
 564          if ( is_child_theme() ) {
 565              ++$allowed_theme_count;
 566          }
 567  
 568          // If there's a default theme installed and not in use, we count that as allowed as well.
 569          if ( $has_default_theme && ! $using_default_theme ) {
 570              ++$allowed_theme_count;
 571          }
 572  
 573          if ( $themes_total > $allowed_theme_count ) {
 574              $has_unused_themes = true;
 575              $themes_inactive   = ( $themes_total - $allowed_theme_count );
 576          }
 577  
 578          // Check if any themes need to be updated.
 579          if ( $themes_need_updates > 0 ) {
 580              $result['status'] = 'critical';
 581  
 582              $result['label'] = __( 'You have themes waiting to be updated' );
 583  
 584              $result['description'] .= sprintf(
 585                  '<p>%s</p>',
 586                  sprintf(
 587                      /* translators: %d: The number of outdated themes. */
 588                      _n(
 589                          'Your site has %d theme waiting to be updated.',
 590                          'Your site has %d themes waiting to be updated.',
 591                          $themes_need_updates
 592                      ),
 593                      $themes_need_updates
 594                  )
 595              );
 596          } else {
 597              // Give positive feedback about the site being good about keeping things up to date.
 598              if ( 1 === $themes_total ) {
 599                  $result['description'] .= sprintf(
 600                      '<p>%s</p>',
 601                      __( 'Your site has 1 installed theme, and it is up to date.' )
 602                  );
 603              } elseif ( $themes_total > 0 ) {
 604                  $result['description'] .= sprintf(
 605                      '<p>%s</p>',
 606                      sprintf(
 607                          /* translators: %d: The number of themes. */
 608                          _n(
 609                              'Your site has %d installed theme, and it is up to date.',
 610                              'Your site has %d installed themes, and they are all up to date.',
 611                              $themes_total
 612                          ),
 613                          $themes_total
 614                      )
 615                  );
 616              } else {
 617                  $result['description'] .= sprintf(
 618                      '<p>%s</p>',
 619                      __( 'Your site does not have any installed themes.' )
 620                  );
 621              }
 622          }
 623  
 624          if ( $has_unused_themes && $show_unused_themes && ! is_multisite() ) {
 625  
 626              // This is a child theme, so we want to be a bit more explicit in our messages.
 627              if ( $active_theme->parent() ) {
 628                  // Recommend removing inactive themes, except a default theme, your current one, and the parent theme.
 629                  $result['status'] = 'recommended';
 630  
 631                  $result['label'] = __( 'You should remove inactive themes' );
 632  
 633                  if ( $using_default_theme ) {
 634                      $result['description'] .= sprintf(
 635                          '<p>%s %s</p>',
 636                          sprintf(
 637                              /* translators: %d: The number of inactive themes. */
 638                              _n(
 639                                  'Your site has %d inactive theme.',
 640                                  'Your site has %d inactive themes.',
 641                                  $themes_inactive
 642                              ),
 643                              $themes_inactive
 644                          ),
 645                          sprintf(
 646                              /* translators: 1: The currently active theme. 2: The active theme's parent theme. */
 647                              __( 'To enhance your site&#8217;s security, you should consider removing any themes you are not using. You should keep your active theme, %1$s, and %2$s, its parent theme.' ),
 648                              $active_theme->name,
 649                              $active_theme->parent()->name
 650                          )
 651                      );
 652                  } else {
 653                      $result['description'] .= sprintf(
 654                          '<p>%s %s</p>',
 655                          sprintf(
 656                              /* translators: %d: The number of inactive themes. */
 657                              _n(
 658                                  'Your site has %d inactive theme.',
 659                                  'Your site has %d inactive themes.',
 660                                  $themes_inactive
 661                              ),
 662                              $themes_inactive
 663                          ),
 664                          sprintf(
 665                              /* translators: 1: The default theme for WordPress. 2: The currently active theme. 3: The active theme's parent theme. */
 666                              __( 'To enhance your site&#8217;s security, you should consider removing any themes you are not using. You should keep %1$s, the default WordPress theme, %2$s, your active theme, and %3$s, its parent theme.' ),
 667                              $default_theme ? $default_theme->name : WP_DEFAULT_THEME,
 668                              $active_theme->name,
 669                              $active_theme->parent()->name
 670                          )
 671                      );
 672                  }
 673              } else {
 674                  // Recommend removing all inactive themes.
 675                  $result['status'] = 'recommended';
 676  
 677                  $result['label'] = __( 'You should remove inactive themes' );
 678  
 679                  if ( $using_default_theme ) {
 680                      $result['description'] .= sprintf(
 681                          '<p>%s %s</p>',
 682                          sprintf(
 683                              /* translators: 1: The amount of inactive themes. 2: The currently active theme. */
 684                              _n(
 685                                  'Your site has %1$d inactive theme, other than %2$s, your active theme.',
 686                                  'Your site has %1$d inactive themes, other than %2$s, your active theme.',
 687                                  $themes_inactive
 688                              ),
 689                              $themes_inactive,
 690                              $active_theme->name
 691                          ),
 692                          __( 'You should consider removing any unused themes to enhance your site&#8217;s security.' )
 693                      );
 694                  } else {
 695                      $result['description'] .= sprintf(
 696                          '<p>%s %s</p>',
 697                          sprintf(
 698                              /* translators: 1: The amount of inactive themes. 2: The default theme for WordPress. 3: The currently active theme. */
 699                              _n(
 700                                  'Your site has %1$d inactive theme, other than %2$s, the default WordPress theme, and %3$s, your active theme.',
 701                                  'Your site has %1$d inactive themes, other than %2$s, the default WordPress theme, and %3$s, your active theme.',
 702                                  $themes_inactive
 703                              ),
 704                              $themes_inactive,
 705                              $default_theme ? $default_theme->name : WP_DEFAULT_THEME,
 706                              $active_theme->name
 707                          ),
 708                          __( 'You should consider removing any unused themes to enhance your site&#8217;s security.' )
 709                      );
 710                  }
 711              }
 712          }
 713  
 714          // If no default Twenty* theme exists.
 715          if ( ! $has_default_theme ) {
 716              $result['status'] = 'recommended';
 717  
 718              $result['label'] = __( 'Have a default theme available' );
 719  
 720              $result['description'] .= sprintf(
 721                  '<p>%s</p>',
 722                  __( 'Your site does not have any default theme. Default themes are used by WordPress automatically if anything is wrong with your chosen theme.' )
 723              );
 724          }
 725  
 726          return $result;
 727      }
 728  
 729      /**
 730       * Tests if the supplied PHP version is supported.
 731       *
 732       * @since 5.2.0
 733       *
 734       * @return array The test results.
 735       */
 736  	public function get_test_php_version() {
 737          $response = wp_check_php_version();
 738  
 739          $result = array(
 740              'label'       => sprintf(
 741                  /* translators: %s: The server PHP version. */
 742                  __( 'Your site is running PHP %s' ),
 743                  PHP_VERSION
 744              ),
 745              'status'      => 'good',
 746              'badge'       => array(
 747                  'label' => __( 'Performance' ),
 748                  'color' => 'blue',
 749              ),
 750              'description' => sprintf(
 751                  '<p>%s</p>',
 752                  __( 'PHP is one of the programming languages used to build WordPress. Newer versions of PHP receive regular security updates and may increase your site&#8217;s performance.' )
 753              ),
 754              'actions'     => sprintf(
 755                  '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
 756                  esc_url( wp_get_update_php_url() ),
 757                  __( 'Learn more about updating PHP' ),
 758                  /* translators: Hidden accessibility text. */
 759                  __( '(opens in a new tab)' )
 760              ),
 761              'test'        => 'php_version',
 762          );
 763  
 764          if ( ! $response ) {
 765              $result['label'] = sprintf(
 766                  /* translators: %s: The server PHP version. */
 767                  __( 'Unable to determine the status of the current PHP version (%s)' ),
 768                  PHP_VERSION
 769              );
 770              $result['status']      = 'recommended';
 771              $result['description'] = '<p><em>' . sprintf(
 772                  /* translators: %s is the URL to the Serve Happy docs page. */
 773                  __( 'Unable to access the WordPress.org API for <a href="%s">Serve Happy</a>.' ),
 774                  'https://codex.wordpress.org/WordPress.org_API#Serve_Happy'
 775              ) . '</em></p>' . $result['description'];
 776              return $result;
 777          }
 778  
 779          $result['description'] .= '<p>' . sprintf(
 780              /* translators: %s: The minimum recommended PHP version. */
 781              __( 'The minimum recommended version of PHP is %s.' ),
 782              $response['recommended_version']
 783          ) . '</p>';
 784  
 785          // PHP is up to date.
 786          if ( version_compare( PHP_VERSION, $response['recommended_version'], '>=' ) ) {
 787              $result['label'] = sprintf(
 788                  /* translators: %s: The server PHP version. */
 789                  __( 'Your site is running a recommended version of PHP (%s)' ),
 790                  PHP_VERSION
 791              );
 792              $result['status'] = 'good';
 793  
 794              return $result;
 795          }
 796  
 797          // The PHP version is older than the recommended version, but still receiving active support.
 798          if ( $response['is_supported'] ) {
 799              $result['label'] = sprintf(
 800                  /* translators: %s: The server PHP version. */
 801                  __( 'Your site is running on an older version of PHP (%s)' ),
 802                  PHP_VERSION
 803              );
 804              $result['status'] = 'recommended';
 805  
 806              return $result;
 807          }
 808  
 809          /*
 810           * The PHP version is still receiving security fixes, but is lower than
 811           * the expected minimum version that will be required by WordPress in the near future.
 812           */
 813          if ( $response['is_secure'] && $response['is_lower_than_future_minimum'] ) {
 814              // The `is_secure` array key name doesn't actually imply this is a secure version of PHP. It only means it receives security updates.
 815  
 816              $result['label'] = sprintf(
 817                  /* translators: %s: The server PHP version. */
 818                  __( 'Your site is running on an outdated version of PHP (%s), which soon will not be supported by WordPress.' ),
 819                  PHP_VERSION
 820              );
 821  
 822              $result['status']         = 'critical';
 823              $result['badge']['label'] = __( 'Requirements' );
 824  
 825              return $result;
 826          }
 827  
 828          // The PHP version is only receiving security fixes.
 829          if ( $response['is_secure'] ) {
 830              $result['label'] = sprintf(
 831                  /* translators: %s: The server PHP version. */
 832                  __( 'Your site is running on an older version of PHP (%s), which should be updated' ),
 833                  PHP_VERSION
 834              );
 835              $result['status'] = 'recommended';
 836  
 837              return $result;
 838          }
 839  
 840          // No more security updates for the PHP version, and lower than the expected minimum version required by WordPress.
 841          if ( $response['is_lower_than_future_minimum'] ) {
 842              $message = sprintf(
 843                  /* translators: %s: The server PHP version. */
 844                  __( 'Your site is running on an outdated version of PHP (%s), which does not receive security updates and soon will not be supported by WordPress.' ),
 845                  PHP_VERSION
 846              );
 847          } else {
 848              // No more security updates for the PHP version, must be updated.
 849              $message = sprintf(
 850                  /* translators: %s: The server PHP version. */
 851                  __( 'Your site is running on an outdated version of PHP (%s), which does not receive security updates. It should be updated.' ),
 852                  PHP_VERSION
 853              );
 854          }
 855  
 856          $result['label']  = $message;
 857          $result['status'] = 'critical';
 858  
 859          $result['badge']['label'] = __( 'Security' );
 860  
 861          return $result;
 862      }
 863  
 864      /**
 865       * Checks if the passed extension or function are available.
 866       *
 867       * Make the check for available PHP modules into a simple boolean operator for a cleaner test runner.
 868       *
 869       * @since 5.2.0
 870       * @since 5.3.0 The `$constant_name` and `$class_name` parameters were added.
 871       *
 872       * @param string $extension_name Optional. The extension name to test. Default null.
 873       * @param string $function_name  Optional. The function name to test. Default null.
 874       * @param string $constant_name  Optional. The constant name to test for. Default null.
 875       * @param string $class_name     Optional. The class name to test for. Default null.
 876       * @return bool Whether or not the extension and function are available.
 877       */
 878  	private function test_php_extension_availability( $extension_name = null, $function_name = null, $constant_name = null, $class_name = null ) {
 879          // If no extension or function is passed, claim to fail testing, as we have nothing to test against.
 880          if ( ! $extension_name && ! $function_name && ! $constant_name && ! $class_name ) {
 881              return false;
 882          }
 883  
 884          if ( $extension_name && ! extension_loaded( $extension_name ) ) {
 885              return false;
 886          }
 887  
 888          if ( $function_name && ! function_exists( $function_name ) ) {
 889              return false;
 890          }
 891  
 892          if ( $constant_name && ! defined( $constant_name ) ) {
 893              return false;
 894          }
 895  
 896          if ( $class_name && ! class_exists( $class_name ) ) {
 897              return false;
 898          }
 899  
 900          return true;
 901      }
 902  
 903      /**
 904       * Tests if required PHP modules are installed on the host.
 905       *
 906       * This test builds on the recommendations made by the WordPress Hosting Team
 907       * as seen at https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions
 908       *
 909       * @since 5.2.0
 910       *
 911       * @return array
 912       */
 913  	public function get_test_php_extensions() {
 914          $result = array(
 915              'label'       => __( 'Required and recommended modules are installed' ),
 916              'status'      => 'good',
 917              'badge'       => array(
 918                  'label' => __( 'Performance' ),
 919                  'color' => 'blue',
 920              ),
 921              'description' => sprintf(
 922                  '<p>%s</p><p>%s</p>',
 923                  __( 'PHP modules perform most of the tasks on the server that make your site run. Any changes to these must be made by your server administrator.' ),
 924                  sprintf(
 925                      /* translators: 1: Link to the hosting group page about recommended PHP modules. 2: Additional link attributes. 3: Accessibility text. */
 926                      __( 'The WordPress Hosting Team maintains a list of those modules, both recommended and required, in <a href="%1$s" %2$s>the team handbook%3$s</a>.' ),
 927                      /* translators: Localized team handbook, if one exists. */
 928                      esc_url( __( 'https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions' ) ),
 929                      'target="_blank"',
 930                      sprintf(
 931                          '<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span>',
 932                          /* translators: Hidden accessibility text. */
 933                          __( '(opens in a new tab)' )
 934                      )
 935                  )
 936              ),
 937              'actions'     => '',
 938              'test'        => 'php_extensions',
 939          );
 940  
 941          $modules = array(
 942              'curl'      => array(
 943                  'function' => 'curl_version',
 944                  'required' => false,
 945              ),
 946              'dom'       => array(
 947                  'class'    => 'DOMNode',
 948                  'required' => false,
 949              ),
 950              'exif'      => array(
 951                  'function' => 'exif_read_data',
 952                  'required' => false,
 953              ),
 954              'fileinfo'  => array(
 955                  'function' => 'finfo_file',
 956                  'required' => false,
 957              ),
 958              'hash'      => array(
 959                  'function' => 'hash',
 960                  'required' => true,
 961              ),
 962              'imagick'   => array(
 963                  'extension' => 'imagick',
 964                  'required'  => false,
 965              ),
 966              'json'      => array(
 967                  'function' => 'json_last_error',
 968                  'required' => true,
 969              ),
 970              'mbstring'  => array(
 971                  'function' => 'mb_check_encoding',
 972                  'required' => false,
 973              ),
 974              'mysqli'    => array(
 975                  'function' => 'mysqli_connect',
 976                  'required' => false,
 977              ),
 978              // Sodium was introduced in PHP 7.2, but the extension may not be enabled.
 979              'libsodium' => array(
 980                  'constant'            => 'SODIUM_LIBRARY_VERSION',
 981                  'required'            => false,
 982                  'php_bundled_version' => '7.2.0',
 983              ),
 984              'openssl'   => array(
 985                  'function' => 'openssl_encrypt',
 986                  'required' => false,
 987              ),
 988              'pcre'      => array(
 989                  'function' => 'preg_match',
 990                  'required' => false,
 991              ),
 992              'mod_xml'   => array(
 993                  'extension' => 'libxml',
 994                  'required'  => false,
 995              ),
 996              'zip'       => array(
 997                  'class'    => 'ZipArchive',
 998                  'required' => false,
 999              ),
1000              'filter'    => array(
1001                  'function' => 'filter_list',
1002                  'required' => false,
1003              ),
1004              'gd'        => array(
1005                  'extension'    => 'gd',
1006                  'required'     => false,
1007                  'fallback_for' => 'imagick',
1008              ),
1009              'iconv'     => array(
1010                  'function' => 'iconv',
1011                  'required' => false,
1012              ),
1013              'intl'      => array(
1014                  'extension' => 'intl',
1015                  'required'  => false,
1016              ),
1017              'mcrypt'    => array(
1018                  'extension'    => 'mcrypt',
1019                  'required'     => false,
1020                  'fallback_for' => 'libsodium',
1021              ),
1022              'simplexml' => array(
1023                  'extension'    => 'simplexml',
1024                  'required'     => false,
1025                  'fallback_for' => 'mod_xml',
1026              ),
1027              'xmlreader' => array(
1028                  'extension'    => 'xmlreader',
1029                  'required'     => false,
1030                  'fallback_for' => 'mod_xml',
1031              ),
1032              'zlib'      => array(
1033                  'extension'    => 'zlib',
1034                  'required'     => false,
1035                  'fallback_for' => 'zip',
1036              ),
1037          );
1038  
1039          /**
1040           * Filters the array representing all the modules we wish to test for.
1041           *
1042           * @since 5.2.0
1043           * @since 5.3.0 The `$constant` and `$class` parameters were added.
1044           *
1045           * @param array $modules {
1046           *     An associative array of modules to test for.
1047           *
1048           *     @type array ...$0 {
1049           *         An associative array of module properties used during testing.
1050           *         One of either `$function` or `$extension` must be provided, or they will fail by default.
1051           *
1052           *         @type string $function     Optional. A function name to test for the existence of.
1053           *         @type string $extension    Optional. An extension to check if is loaded in PHP.
1054           *         @type string $constant     Optional. A constant name to check for to verify an extension exists.
1055           *         @type string $class        Optional. A class name to check for to verify an extension exists.
1056           *         @type bool   $required     Is this a required feature or not.
1057           *         @type string $fallback_for Optional. The module this module replaces as a fallback.
1058           *     }
1059           * }
1060           */
1061          $modules = apply_filters( 'site_status_test_php_modules', $modules );
1062  
1063          $failures = array();
1064  
1065          foreach ( $modules as $library => $module ) {
1066              $extension_name = $module['extension'] ?? null;
1067              $function_name  = $module['function'] ?? null;
1068              $constant_name  = $module['constant'] ?? null;
1069              $class_name     = $module['class'] ?? null;
1070  
1071              // If this module is a fallback for another function, check if that other function passed.
1072              if ( isset( $module['fallback_for'] ) ) {
1073                  /*
1074                   * If that other function has a failure, mark this module as required for usual operations.
1075                   * If that other function hasn't failed, skip this test as it's only a fallback.
1076                   */
1077                  if ( isset( $failures[ $module['fallback_for'] ] ) ) {
1078                      $module['required'] = true;
1079                  } else {
1080                      continue;
1081                  }
1082              }
1083  
1084              if ( ! $this->test_php_extension_availability( $extension_name, $function_name, $constant_name, $class_name )
1085                  && ( ! isset( $module['php_bundled_version'] )
1086                      || version_compare( PHP_VERSION, $module['php_bundled_version'], '<' ) )
1087              ) {
1088                  if ( $module['required'] ) {
1089                      $result['status'] = 'critical';
1090  
1091                      $class = 'error';
1092                      /* translators: Hidden accessibility text. */
1093                      $screen_reader = __( 'Error' );
1094                      $message       = sprintf(
1095                          /* translators: %s: The module name. */
1096                          __( 'The required module, %s, is not installed, or has been disabled.' ),
1097                          $library
1098                      );
1099                  } else {
1100                      $class = 'warning';
1101                      /* translators: Hidden accessibility text. */
1102                      $screen_reader = __( 'Warning' );
1103                      $message       = sprintf(
1104                          /* translators: %s: The module name. */
1105                          __( 'The optional module, %s, is not installed, or has been disabled.' ),
1106                          $library
1107                      );
1108                  }
1109  
1110                  if ( ! $module['required'] && 'good' === $result['status'] ) {
1111                      $result['status'] = 'recommended';
1112                  }
1113  
1114                  $failures[ $library ] = "<span class='dashicons $class' aria-hidden='true'></span><span class='screen-reader-text'>$screen_reader</span> $message";
1115              }
1116          }
1117  
1118          if ( ! empty( $failures ) ) {
1119              $output = '<ul>';
1120  
1121              foreach ( $failures as $failure ) {
1122                  $output .= sprintf(
1123                      '<li>%s</li>',
1124                      $failure
1125                  );
1126              }
1127  
1128              $output .= '</ul>';
1129          }
1130  
1131          if ( 'good' !== $result['status'] ) {
1132              if ( 'recommended' === $result['status'] ) {
1133                  $result['label'] = __( 'One or more recommended modules are missing' );
1134              }
1135              if ( 'critical' === $result['status'] ) {
1136                  $result['label'] = __( 'One or more required modules are missing' );
1137              }
1138  
1139              $result['description'] .= $output;
1140          }
1141  
1142          return $result;
1143      }
1144  
1145      /**
1146       * Tests if the PHP default timezone is set to UTC.
1147       *
1148       * @since 5.3.1
1149       *
1150       * @return array The test results.
1151       */
1152  	public function get_test_php_default_timezone() {
1153          $result = array(
1154              'label'       => __( 'PHP default timezone is valid' ),
1155              'status'      => 'good',
1156              'badge'       => array(
1157                  'label' => __( 'Performance' ),
1158                  'color' => 'blue',
1159              ),
1160              'description' => sprintf(
1161                  '<p>%s</p>',
1162                  __( 'PHP default timezone was configured by WordPress on loading. This is necessary for correct calculations of dates and times.' )
1163              ),
1164              'actions'     => '',
1165              'test'        => 'php_default_timezone',
1166          );
1167  
1168          if ( 'UTC' !== date_default_timezone_get() ) {
1169              $result['status'] = 'critical';
1170  
1171              $result['label'] = __( 'PHP default timezone is invalid' );
1172  
1173              $result['description'] = sprintf(
1174                  '<p>%s</p>',
1175                  sprintf(
1176                      /* translators: %s: date_default_timezone_set() */
1177                      __( 'PHP default timezone was changed after WordPress loading by a %s function call. This interferes with correct calculations of dates and times.' ),
1178                      '<code>date_default_timezone_set()</code>'
1179                  )
1180              );
1181          }
1182  
1183          return $result;
1184      }
1185  
1186      /**
1187       * Tests if there's an active PHP session that can affect loopback requests.
1188       *
1189       * @since 5.5.0
1190       *
1191       * @return array The test results.
1192       */
1193  	public function get_test_php_sessions() {
1194          $result = array(
1195              'label'       => __( 'No PHP sessions detected' ),
1196              'status'      => 'good',
1197              'badge'       => array(
1198                  'label' => __( 'Performance' ),
1199                  'color' => 'blue',
1200              ),
1201              'description' => sprintf(
1202                  '<p>%s</p>',
1203                  sprintf(
1204                      /* translators: 1: session_start(), 2: session_write_close() */
1205                      __( '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.' ),
1206                      '<code>session_start()</code>',
1207                      '<code>session_write_close()</code>'
1208                  )
1209              ),
1210              'test'        => 'php_sessions',
1211          );
1212  
1213          if ( function_exists( 'session_status' ) && PHP_SESSION_ACTIVE === session_status() ) {
1214              $result['status'] = 'critical';
1215  
1216              $result['label'] = __( 'An active PHP session was detected' );
1217  
1218              $result['description'] = sprintf(
1219                  '<p>%s</p>',
1220                  sprintf(
1221                      /* translators: 1: session_start(), 2: session_write_close() */
1222                      __( '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.' ),
1223                      '<code>session_start()</code>',
1224                      '<code>session_write_close()</code>'
1225                  )
1226              );
1227          }
1228  
1229          return $result;
1230      }
1231  
1232      /**
1233       * Tests if the SQL server is up to date.
1234       *
1235       * @since 5.2.0
1236       *
1237       * @return array The test results.
1238       */
1239  	public function get_test_sql_server() {
1240          if ( ! $this->mysql_server_version ) {
1241              $this->prepare_sql_data();
1242          }
1243  
1244          $result = array(
1245              'label'       => __( 'SQL server is up to date' ),
1246              'status'      => 'good',
1247              'badge'       => array(
1248                  'label' => __( 'Performance' ),
1249                  'color' => 'blue',
1250              ),
1251              'description' => sprintf(
1252                  '<p>%s</p>',
1253                  __( 'The SQL server is a required piece of software for the database WordPress uses to store all your site&#8217;s content and settings.' )
1254              ),
1255              'actions'     => sprintf(
1256                  '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
1257                  /* translators: Localized version of WordPress requirements if one exists. */
1258                  esc_url( __( 'https://wordpress.org/about/requirements/' ) ),
1259                  __( 'Learn more about what WordPress requires to run.' ),
1260                  /* translators: Hidden accessibility text. */
1261                  __( '(opens in a new tab)' )
1262              ),
1263              'test'        => 'sql_server',
1264          );
1265  
1266          $db_dropin = file_exists( WP_CONTENT_DIR . '/db.php' );
1267  
1268          if ( ! $this->is_recommended_mysql_version ) {
1269              $result['status'] = 'recommended';
1270  
1271              $result['label'] = __( 'Outdated SQL server' );
1272  
1273              $result['description'] .= sprintf(
1274                  '<p>%s</p>',
1275                  sprintf(
1276                      /* translators: 1: The database engine in use (MySQL or MariaDB). 2: Database server recommended version number. */
1277                      __( 'For optimal performance and security reasons, you should consider running %1$s version %2$s or higher. Contact your web hosting company to correct this.' ),
1278                      ( $this->is_mariadb ? 'MariaDB' : 'MySQL' ),
1279                      $this->mysql_recommended_version
1280                  )
1281              );
1282          }
1283  
1284          if ( ! $this->is_acceptable_mysql_version ) {
1285              $result['status'] = 'critical';
1286  
1287              $result['label']          = __( 'Severely outdated SQL server' );
1288              $result['badge']['label'] = __( 'Security' );
1289  
1290              $result['description'] .= sprintf(
1291                  '<p>%s</p>',
1292                  sprintf(
1293                      /* translators: 1: The database engine in use (MySQL or MariaDB). 2: Database server minimum version number. */
1294                      __( 'WordPress requires %1$s version %2$s or higher. Contact your web hosting company to correct this.' ),
1295                      ( $this->is_mariadb ? 'MariaDB' : 'MySQL' ),
1296                      $this->mysql_required_version
1297                  )
1298              );
1299          }
1300  
1301          if ( $db_dropin ) {
1302              $result['description'] .= sprintf(
1303                  '<p>%s</p>',
1304                  wp_kses(
1305                      sprintf(
1306                          /* translators: 1: The name of the drop-in. 2: The name of the database engine. */
1307                          __( 'You are using a %1$s drop-in which might mean that a %2$s database is not being used.' ),
1308                          '<code>wp-content/db.php</code>',
1309                          ( $this->is_mariadb ? 'MariaDB' : 'MySQL' )
1310                      ),
1311                      array(
1312                          'code' => true,
1313                      )
1314                  )
1315              );
1316          }
1317  
1318          return $result;
1319      }
1320  
1321      /**
1322       * Tests if the site can communicate with WordPress.org.
1323       *
1324       * @since 5.2.0
1325       *
1326       * @return array The test results.
1327       */
1328  	public function get_test_dotorg_communication() {
1329          $result = array(
1330              'label'       => __( 'Can communicate with WordPress.org' ),
1331              'status'      => '',
1332              'badge'       => array(
1333                  'label' => __( 'Security' ),
1334                  'color' => 'blue',
1335              ),
1336              'description' => sprintf(
1337                  '<p>%s</p>',
1338                  __( 'Communicating with the WordPress servers is used to check for new versions, and to both install and update WordPress core, themes or plugins.' )
1339              ),
1340              'actions'     => '',
1341              'test'        => 'dotorg_communication',
1342          );
1343  
1344          $wp_dotorg = wp_remote_get(
1345              'https://api.wordpress.org',
1346              array(
1347                  'timeout' => 10,
1348              )
1349          );
1350          if ( ! is_wp_error( $wp_dotorg ) ) {
1351              $result['status'] = 'good';
1352          } else {
1353              $result['status'] = 'critical';
1354  
1355              $result['label'] = __( 'Could not reach WordPress.org' );
1356  
1357              $result['description'] .= sprintf(
1358                  '<p>%s</p>',
1359                  sprintf(
1360                      '<span class="error"><span class="screen-reader-text">%s</span></span> %s',
1361                      /* translators: Hidden accessibility text. */
1362                      __( 'Error' ),
1363                      sprintf(
1364                          /* translators: 1: The IP address WordPress.org resolves to. 2: The error returned by the lookup. */
1365                          __( 'Your site is unable to reach WordPress.org at %1$s, and returned the error: %2$s' ),
1366                          gethostbyname( 'api.wordpress.org' ),
1367                          $wp_dotorg->get_error_message()
1368                      )
1369                  )
1370              );
1371  
1372              $result['actions'] = sprintf(
1373                  '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
1374                  /* translators: Localized Support reference. */
1375                  esc_url( __( 'https://wordpress.org/support/forums/' ) ),
1376                  __( 'Get help resolving this issue.' ),
1377                  /* translators: Hidden accessibility text. */
1378                  __( '(opens in a new tab)' )
1379              );
1380          }
1381  
1382          return $result;
1383      }
1384  
1385      /**
1386       * Tests if debug information is enabled.
1387       *
1388       * When WP_DEBUG is enabled, errors and information may be disclosed to site visitors,
1389       * or logged to a publicly accessible file.
1390       *
1391       * Debugging is also frequently left enabled after looking for errors on a site,
1392       * as site owners do not understand the implications of this.
1393       *
1394       * @since 5.2.0
1395       *
1396       * @return array The test results.
1397       */
1398  	public function get_test_is_in_debug_mode() {
1399          $result = array(
1400              'label'       => __( 'Your site is not set to output debug information' ),
1401              'status'      => 'good',
1402              'badge'       => array(
1403                  'label' => __( 'Security' ),
1404                  'color' => 'blue',
1405              ),
1406              'description' => sprintf(
1407                  '<p>%s</p>',
1408                  __( 'Debug mode is often enabled to gather more details about an error or site failure, but may contain sensitive information which should not be available on a publicly available website.' )
1409              ),
1410              'actions'     => sprintf(
1411                  '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
1412                  /* translators: Documentation explaining debugging in WordPress. */
1413                  esc_url( __( 'https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/' ) ),
1414                  __( 'Learn more about debugging in WordPress.' ),
1415                  /* translators: Hidden accessibility text. */
1416                  __( '(opens in a new tab)' )
1417              ),
1418              'test'        => 'is_in_debug_mode',
1419          );
1420  
1421          if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
1422              if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
1423                  $result['label'] = __( 'Your site is set to log errors to a potentially public file' );
1424  
1425                  $result['status'] = str_starts_with( ini_get( 'error_log' ), ABSPATH ) ? 'critical' : 'recommended';
1426  
1427                  $result['description'] .= sprintf(
1428                      '<p>%s</p>',
1429                      sprintf(
1430                          /* translators: %s: WP_DEBUG_LOG */
1431                          __( '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.' ),
1432                          '<code>WP_DEBUG_LOG</code>'
1433                      )
1434                  );
1435              }
1436  
1437              if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
1438                  $result['label'] = __( 'Your site is set to display errors to site visitors' );
1439  
1440                  $result['status'] = 'critical';
1441  
1442                  // On development environments, set the status to recommended.
1443                  if ( $this->is_development_environment() ) {
1444                      $result['status'] = 'recommended';
1445                  }
1446  
1447                  $result['description'] .= sprintf(
1448                      '<p>%s</p>',
1449                      sprintf(
1450                          /* translators: 1: WP_DEBUG_DISPLAY, 2: WP_DEBUG */
1451                          __( 'The value, %1$s, has either been enabled by %2$s or added to your configuration file. This will make errors display on the front end of your site.' ),
1452                          '<code>WP_DEBUG_DISPLAY</code>',
1453                          '<code>WP_DEBUG</code>'
1454                      )
1455                  );
1456              }
1457          }
1458  
1459          return $result;
1460      }
1461  
1462      /**
1463       * Tests if the site is serving content over HTTPS.
1464       *
1465       * Many sites have varying degrees of HTTPS support, the most common of which is sites that have it
1466       * enabled, but only if you visit the right site address.
1467       *
1468       * @since 5.2.0
1469       * @since 5.7.0 Updated to rely on {@see wp_is_using_https()} and {@see wp_is_https_supported()}.
1470       *
1471       * @return array The test results.
1472       */
1473  	public function get_test_https_status() {
1474          /*
1475           * Check HTTPS detection results.
1476           */
1477          $errors = wp_get_https_detection_errors();
1478  
1479          $default_update_url = wp_get_default_update_https_url();
1480  
1481          $result = array(
1482              'label'       => __( 'Your website is using an active HTTPS connection' ),
1483              'status'      => 'good',
1484              'badge'       => array(
1485                  'label' => __( 'Security' ),
1486                  'color' => 'blue',
1487              ),
1488              'description' => sprintf(
1489                  '<p>%s</p>',
1490                  __( '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.' )
1491              ),
1492              'actions'     => sprintf(
1493                  '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
1494                  esc_url( $default_update_url ),
1495                  __( 'Learn more about why you should use HTTPS' ),
1496                  /* translators: Hidden accessibility text. */
1497                  __( '(opens in a new tab)' )
1498              ),
1499              'test'        => 'https_status',
1500          );
1501  
1502          if ( ! wp_is_using_https() ) {
1503              /*
1504               * If the website is not using HTTPS, provide more information
1505               * about whether it is supported and how it can be enabled.
1506               */
1507              $result['status'] = 'recommended';
1508              $result['label']  = __( 'Your website does not use HTTPS' );
1509  
1510              if ( wp_is_site_url_using_https() ) {
1511                  if ( is_ssl() ) {
1512                      $result['description'] = sprintf(
1513                          '<p>%s</p>',
1514                          sprintf(
1515                              /* translators: %s: URL to Settings > General > Site Address. */
1516                              __( 'You are accessing this website using HTTPS, but your <a href="%s">Site Address</a> is not set up to use HTTPS by default.' ),
1517                              esc_url( admin_url( 'options-general.php' ) . '#home' )
1518                          )
1519                      );
1520                  } else {
1521                      $result['description'] = sprintf(
1522                          '<p>%s</p>',
1523                          sprintf(
1524                              /* translators: %s: URL to Settings > General > Site Address. */
1525                              __( 'Your <a href="%s">Site Address</a> is not set up to use HTTPS.' ),
1526                              esc_url( admin_url( 'options-general.php' ) . '#home' )
1527                          )
1528                      );
1529                  }
1530              } else {
1531                  if ( is_ssl() ) {
1532                      $result['description'] = sprintf(
1533                          '<p>%s</p>',
1534                          sprintf(
1535                              /* translators: 1: URL to Settings > General > WordPress Address, 2: URL to Settings > General > Site Address. */
1536                              __( 'You are accessing this website using HTTPS, but your <a href="%1$s">WordPress Address</a> and <a href="%2$s">Site Address</a> are not set up to use HTTPS by default.' ),
1537                              esc_url( admin_url( 'options-general.php' ) . '#siteurl' ),
1538                              esc_url( admin_url( 'options-general.php' ) . '#home' )
1539                          )
1540                      );
1541                  } else {
1542                      $result['description'] = sprintf(
1543                          '<p>%s</p>',
1544                          sprintf(
1545                              /* translators: 1: URL to Settings > General > WordPress Address, 2: URL to Settings > General > Site Address. */
1546                              __( 'Your <a href="%1$s">WordPress Address</a> and <a href="%2$s">Site Address</a> are not set up to use HTTPS.' ),
1547                              esc_url( admin_url( 'options-general.php' ) . '#siteurl' ),
1548                              esc_url( admin_url( 'options-general.php' ) . '#home' )
1549                          )
1550                      );
1551                  }
1552              }
1553  
1554              if ( wp_is_https_supported() ) {
1555                  $result['description'] .= sprintf(
1556                      '<p>%s</p>',
1557                      __( 'HTTPS is already supported for your website.' )
1558                  );
1559  
1560                  if ( defined( 'WP_HOME' ) || defined( 'WP_SITEURL' ) ) {
1561                      $result['description'] .= sprintf(
1562                          '<p>%s</p>',
1563                          sprintf(
1564                              /* translators: 1: wp-config.php, 2: WP_HOME, 3: WP_SITEURL */
1565                              __( 'However, your WordPress Address is currently controlled by a PHP constant and therefore cannot be updated. You need to edit your %1$s and remove or update the definitions of %2$s and %3$s.' ),
1566                              '<code>wp-config.php</code>',
1567                              '<code>WP_HOME</code>',
1568                              '<code>WP_SITEURL</code>'
1569                          )
1570                      );
1571                  } elseif ( current_user_can( 'update_https' ) ) {
1572                      $default_direct_update_url = add_query_arg( 'action', 'update_https', wp_nonce_url( admin_url( 'site-health.php' ), 'wp_update_https' ) );
1573                      $direct_update_url         = wp_get_direct_update_https_url();
1574  
1575                      if ( ! empty( $direct_update_url ) ) {
1576                          $result['actions'] = sprintf(
1577                              '<p class="button-container"><a class="button button-primary" href="%1$s" target="_blank">%2$s<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
1578                              esc_url( $direct_update_url ),
1579                              __( 'Update your site to use HTTPS' ),
1580                              /* translators: Hidden accessibility text. */
1581                              __( '(opens in a new tab)' )
1582                          );
1583                      } else {
1584                          $result['actions'] = sprintf(
1585                              '<p class="button-container"><a class="button button-primary" href="%1$s">%2$s</a></p>',
1586                              esc_url( $default_direct_update_url ),
1587                              __( 'Update your site to use HTTPS' )
1588                          );
1589                      }
1590                  }
1591              } else {
1592                  // If host-specific "Update HTTPS" URL is provided, include a link.
1593                  $update_url = wp_get_update_https_url();
1594                  if ( $update_url !== $default_update_url ) {
1595                      $result['description'] .= sprintf(
1596                          '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
1597                          esc_url( $update_url ),
1598                          __( 'Talk to your web host about supporting HTTPS for your website.' ),
1599                          /* translators: Hidden accessibility text. */
1600                          __( '(opens in a new tab)' )
1601                      );
1602                  } else {
1603                      $result['description'] .= sprintf(
1604                          '<p>%s</p>',
1605                          __( 'Talk to your web host about supporting HTTPS for your website.' )
1606                      );
1607                  }
1608              }
1609          }
1610  
1611          return $result;
1612      }
1613  
1614      /**
1615       * Checks if the HTTP API can handle SSL/TLS requests.
1616       *
1617       * @since 5.2.0
1618       *
1619       * @return array The test result.
1620       */
1621  	public function get_test_ssl_support() {
1622          $result = array(
1623              'label'       => '',
1624              'status'      => '',
1625              'badge'       => array(
1626                  'label' => __( 'Security' ),
1627                  'color' => 'blue',
1628              ),
1629              'description' => sprintf(
1630                  '<p>%s</p>',
1631                  __( 'Securely communicating between servers are needed for transactions such as fetching files, conducting sales on store sites, and much more.' )
1632              ),
1633              'actions'     => '',
1634              'test'        => 'ssl_support',
1635          );
1636  
1637          $supports_https = wp_http_supports( array( 'ssl' ) );
1638  
1639          if ( $supports_https ) {
1640              $result['status'] = 'good';
1641  
1642              $result['label'] = __( 'Your site can communicate securely with other services' );
1643          } else {
1644              $result['status'] = 'critical';
1645  
1646              $result['label'] = __( 'Your site is unable to communicate securely with other services' );
1647  
1648              $result['description'] .= sprintf(
1649                  '<p>%s</p>',
1650                  __( 'Talk to your web host about OpenSSL support for PHP.' )
1651              );
1652          }
1653  
1654          return $result;
1655      }
1656  
1657      /**
1658       * Tests if scheduled events run as intended.
1659       *
1660       * If scheduled events are not running, this may indicate something with WP_Cron is not working
1661       * as intended, or that there are orphaned events hanging around from older code.
1662       *
1663       * @since 5.2.0
1664       *
1665       * @return array The test results.
1666       */
1667  	public function get_test_scheduled_events() {
1668          $result = array(
1669              'label'       => __( 'Scheduled events are running' ),
1670              'status'      => 'good',
1671              'badge'       => array(
1672                  'label' => __( 'Performance' ),
1673                  'color' => 'blue',
1674              ),
1675              'description' => sprintf(
1676                  '<p>%s</p>',
1677                  __( 'Scheduled events are what periodically looks for updates to plugins, themes and WordPress itself. It is also what makes sure scheduled posts are published on time. It may also be used by various plugins to make sure that planned actions are executed.' )
1678              ),
1679              'actions'     => '',
1680              'test'        => 'scheduled_events',
1681          );
1682  
1683          $this->wp_schedule_test_init();
1684  
1685          if ( is_wp_error( $this->has_missed_cron() ) ) {
1686              $result['status'] = 'critical';
1687  
1688              $result['label'] = __( 'It was not possible to check your scheduled events' );
1689  
1690              $result['description'] = sprintf(
1691                  '<p>%s</p>',
1692                  sprintf(
1693                      /* translators: %s: The error message returned while from the cron scheduler. */
1694                      __( 'While trying to test your site&#8217;s scheduled events, the following error was returned: %s' ),
1695                      $this->has_missed_cron()->get_error_message()
1696                  )
1697              );
1698          } elseif ( $this->has_missed_cron() ) {
1699              $result['status'] = 'recommended';
1700  
1701              $result['label'] = __( 'A scheduled event has failed' );
1702  
1703              $result['description'] = sprintf(
1704                  '<p>%s</p>',
1705                  sprintf(
1706                      /* translators: %s: The name of the failed cron event. */
1707                      __( '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.' ),
1708                      $this->last_missed_cron
1709                  )
1710              );
1711          } elseif ( $this->has_late_cron() ) {
1712              $result['status'] = 'recommended';
1713  
1714              $result['label'] = __( 'A scheduled event is late' );
1715  
1716              $result['description'] = sprintf(
1717                  '<p>%s</p>',
1718                  sprintf(
1719                      /* translators: %s: The name of the late cron event. */
1720                      __( '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.' ),
1721                      $this->last_late_cron
1722                  )
1723              );
1724          }
1725  
1726          return $result;
1727      }
1728  
1729      /**
1730       * Tests if WordPress can run automated background updates.
1731       *
1732       * Background updates in WordPress are primarily used for minor releases and security updates.
1733       * It's important to either have these working, or be aware that they are intentionally disabled
1734       * for whatever reason.
1735       *
1736       * @since 5.2.0
1737       *
1738       * @return array The test results.
1739       */
1740  	public function get_test_background_updates() {
1741          $result = array(
1742              'label'       => __( 'Background updates are working' ),
1743              'status'      => 'good',
1744              'badge'       => array(
1745                  'label' => __( 'Security' ),
1746                  'color' => 'blue',
1747              ),
1748              'description' => sprintf(
1749                  '<p>%s</p>',
1750                  __( 'Background updates ensure that WordPress can auto-update if a security update is released for the version you are currently using.' )
1751              ),
1752              'actions'     => '',
1753              'test'        => 'background_updates',
1754          );
1755  
1756          if ( ! class_exists( 'WP_Site_Health_Auto_Updates' ) ) {
1757              require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health-auto-updates.php';
1758          }
1759  
1760          /*
1761           * Run the auto-update tests in a separate class,
1762           * as there are many considerations to be made.
1763           */
1764          $automatic_updates = new WP_Site_Health_Auto_Updates();
1765          $tests             = $automatic_updates->run_tests();
1766  
1767          $output = '<ul>';
1768  
1769          foreach ( $tests as $test ) {
1770              /* translators: Hidden accessibility text. */
1771              $severity_string = __( 'Passed' );
1772  
1773              if ( 'fail' === $test->severity ) {
1774                  $result['label'] = __( 'Background updates are not working as expected' );
1775  
1776                  $result['status'] = 'critical';
1777  
1778                  /* translators: Hidden accessibility text. */
1779                  $severity_string = __( 'Error' );
1780              }
1781  
1782              if ( 'warning' === $test->severity && 'good' === $result['status'] ) {
1783                  $result['label'] = __( 'Background updates may not be working properly' );
1784  
1785                  $result['status'] = 'recommended';
1786  
1787                  /* translators: Hidden accessibility text. */
1788                  $severity_string = __( 'Warning' );
1789              }
1790  
1791              $output .= sprintf(
1792                  '<li><span class="dashicons %s"><span class="screen-reader-text">%s</span></span> %s</li>',
1793                  esc_attr( $test->severity ),
1794                  $severity_string,
1795                  $test->description
1796              );
1797          }
1798  
1799          $output .= '</ul>';
1800  
1801          if ( 'good' !== $result['status'] ) {
1802              $result['description'] .= $output;
1803          }
1804  
1805          return $result;
1806      }
1807  
1808      /**
1809       * Tests if plugin and theme auto-updates appear to be configured correctly.
1810       *
1811       * @since 5.5.0
1812       *
1813       * @return array The test results.
1814       */
1815  	public function get_test_plugin_theme_auto_updates() {
1816          $result = array(
1817              'label'       => __( 'Plugin and theme auto-updates appear to be configured correctly' ),
1818              'status'      => 'good',
1819              'badge'       => array(
1820                  'label' => __( 'Security' ),
1821                  'color' => 'blue',
1822              ),
1823              'description' => sprintf(
1824                  '<p>%s</p>',
1825                  __( 'Plugin and theme auto-updates ensure that the latest versions are always installed.' )
1826              ),
1827              'actions'     => '',
1828              'test'        => 'plugin_theme_auto_updates',
1829          );
1830  
1831          $check_plugin_theme_updates = $this->detect_plugin_theme_auto_update_issues();
1832  
1833          $result['status'] = $check_plugin_theme_updates->status;
1834  
1835          if ( 'good' !== $result['status'] ) {
1836              $result['label'] = __( 'Your site may have problems auto-updating plugins and themes' );
1837  
1838              $result['description'] .= sprintf(
1839                  '<p>%s</p>',
1840                  $check_plugin_theme_updates->message
1841              );
1842          }
1843  
1844          return $result;
1845      }
1846  
1847      /**
1848       * Tests available disk space for updates.
1849       *
1850       * @since 6.3.0
1851       *
1852       * @return array The test results.
1853       */
1854  	public function get_test_available_updates_disk_space() {
1855          $available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
1856  
1857          $result = array(
1858              'label'       => __( 'Disk space available to safely perform updates' ),
1859              'status'      => 'good',
1860              'badge'       => array(
1861                  'label' => __( 'Security' ),
1862                  'color' => 'blue',
1863              ),
1864              'description' => sprintf(
1865                  /* translators: %s: Available disk space in MB or GB. */
1866                  '<p>' . __( '%s available disk space was detected, update routines can be performed safely.' ) . '</p>',
1867                  size_format( $available_space )
1868              ),
1869              'actions'     => '',
1870              'test'        => 'available_updates_disk_space',
1871          );
1872  
1873          if ( false === $available_space ) {
1874              $result['description'] = __( 'Could not determine available disk space for updates.' );
1875              $result['status']      = 'recommended';
1876          } elseif ( $available_space < 20 * MB_IN_BYTES ) {
1877              $result['description'] = sprintf(
1878                  /* translators: %s: Available disk space in MB or GB. */
1879                  __( 'Available disk space is critically low, less than %s available. Proceed with caution, updates may fail.' ),
1880                  size_format( 20 * MB_IN_BYTES )
1881              );
1882              $result['status'] = 'critical';
1883          } elseif ( $available_space < 100 * MB_IN_BYTES ) {
1884              $result['description'] = sprintf(
1885                  /* translators: %s: Available disk space in MB or GB. */
1886                  __( 'Available disk space is low, less than %s available.' ),
1887                  size_format( 100 * MB_IN_BYTES )
1888              );
1889              $result['status'] = 'recommended';
1890          }
1891  
1892          return $result;
1893      }
1894  
1895      /**
1896       * Tests if registration is open to everyone and the default role is privileged.
1897       *
1898       * @since 7.0.0
1899       *
1900       * @return array The test results.
1901       */
1902  	public function get_test_insecure_registration() {
1903          $users_can_register = get_option( 'users_can_register' );
1904          $default_role       = get_option( 'default_role' );
1905  
1906          $result = array(
1907              'label'       => __( 'Open Registration with privileged default role' ),
1908              'status'      => 'good',
1909              'badge'       => array(
1910                  'label' => __( 'Security' ),
1911                  'color' => 'blue',
1912              ),
1913              'description' => '<p>' . __( 'The combination of open registration setting and the default user role may lead to security issues.' ) . '</p>',
1914              'actions'     => '',
1915              'test'        => 'insecure_registration',
1916          );
1917  
1918          if ( $users_can_register && in_array( $default_role, array( 'editor', 'administrator' ), true ) ) {
1919              $result['description'] = __( 'Registration is open to anyone, and the default role is set to a privileged role.' );
1920              $result['status']      = 'critical';
1921              $result['actions']     = sprintf(
1922                  '<p><a href="%s">%s</a></p>',
1923                  esc_url( admin_url( 'options-general.php' ) ),
1924                  __( 'Change these settings' )
1925              );
1926          }
1927  
1928          return $result;
1929      }
1930  
1931      /**
1932       * Tests if plugin and theme temporary backup directories are writable or can be created.
1933       *
1934       * @since 6.3.0
1935       *
1936       * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
1937       *
1938       * @return array The test results.
1939       */
1940  	public function get_test_update_temp_backup_writable() {
1941          global $wp_filesystem;
1942  
1943          $result = array(
1944              'label'       => __( 'Plugin and theme temporary backup directory is writable' ),
1945              'status'      => 'good',
1946              'badge'       => array(
1947                  'label' => __( 'Security' ),
1948                  'color' => 'blue',
1949              ),
1950              'description' => sprintf(
1951                  /* translators: %s: wp-content/upgrade-temp-backup */
1952                  '<p>' . __( 'The %s directory used to improve the stability of plugin and theme updates is writable.' ) . '</p>',
1953                  '<code>wp-content/upgrade-temp-backup</code>'
1954              ),
1955              'actions'     => '',
1956              'test'        => 'update_temp_backup_writable',
1957          );
1958  
1959          if ( ! function_exists( 'WP_Filesystem' ) ) {
1960              require_once  ABSPATH . 'wp-admin/includes/file.php';
1961          }
1962  
1963          ob_start();
1964          $credentials = request_filesystem_credentials( '' );
1965          ob_end_clean();
1966  
1967          if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
1968              $result['status']      = 'recommended';
1969              $result['label']       = __( 'Could not access filesystem' );
1970              $result['description'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
1971              return $result;
1972          }
1973  
1974          $wp_content = $wp_filesystem->wp_content_dir();
1975  
1976          if ( ! $wp_content ) {
1977              $result['status']      = 'critical';
1978              $result['label']       = __( 'Unable to locate WordPress content directory' );
1979              $result['description'] = sprintf(
1980                  /* translators: %s: wp-content */
1981                  '<p>' . __( 'The %s directory cannot be located.' ) . '</p>',
1982                  '<code>wp-content</code>'
1983              );
1984              return $result;
1985          }
1986  
1987          $upgrade_dir_exists      = $wp_filesystem->is_dir( "$wp_content/upgrade" );
1988          $upgrade_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade" );
1989          $backup_dir_exists       = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup" );
1990          $backup_dir_is_writable  = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup" );
1991  
1992          $plugins_dir_exists      = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup/plugins" );
1993          $plugins_dir_is_writable = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup/plugins" );
1994          $themes_dir_exists       = $wp_filesystem->is_dir( "$wp_content/upgrade-temp-backup/themes" );
1995          $themes_dir_is_writable  = $wp_filesystem->is_writable( "$wp_content/upgrade-temp-backup/themes" );
1996  
1997          if ( $plugins_dir_exists && ! $plugins_dir_is_writable && $themes_dir_exists && ! $themes_dir_is_writable ) {
1998              $result['status']      = 'critical';
1999              $result['label']       = __( 'Plugin and theme temporary backup directories exist but are not writable' );
2000              $result['description'] = sprintf(
2001                  /* translators: 1: wp-content/upgrade-temp-backup/plugins, 2: wp-content/upgrade-temp-backup/themes. */
2002                  '<p>' . __( 'The %1$s and %2$s directories exist but are not writable. These directories are used to improve the stability of plugin updates. Please make sure the server has write permissions to these directories.' ) . '</p>',
2003                  '<code>wp-content/upgrade-temp-backup/plugins</code>',
2004                  '<code>wp-content/upgrade-temp-backup/themes</code>'
2005              );
2006              return $result;
2007          }
2008  
2009          if ( $plugins_dir_exists && ! $plugins_dir_is_writable ) {
2010              $result['status']      = 'critical';
2011              $result['label']       = __( 'Plugin temporary backup directory exists but is not writable' );
2012              $result['description'] = sprintf(
2013                  /* translators: %s: wp-content/upgrade-temp-backup/plugins */
2014                  '<p>' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of plugin updates. Please make sure the server has write permissions to this directory.' ) . '</p>',
2015                  '<code>wp-content/upgrade-temp-backup/plugins</code>'
2016              );
2017              return $result;
2018          }
2019  
2020          if ( $themes_dir_exists && ! $themes_dir_is_writable ) {
2021              $result['status']      = 'critical';
2022              $result['label']       = __( 'Theme temporary backup directory exists but is not writable' );
2023              $result['description'] = sprintf(
2024                  /* translators: %s: wp-content/upgrade-temp-backup/themes */
2025                  '<p>' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of theme updates. Please make sure the server has write permissions to this directory.' ) . '</p>',
2026                  '<code>wp-content/upgrade-temp-backup/themes</code>'
2027              );
2028              return $result;
2029          }
2030  
2031          if ( ( ! $plugins_dir_exists || ! $themes_dir_exists ) && $backup_dir_exists && ! $backup_dir_is_writable ) {
2032              $result['status']      = 'critical';
2033              $result['label']       = __( 'The temporary backup directory exists but is not writable' );
2034              $result['description'] = sprintf(
2035                  /* translators: %s: wp-content/upgrade-temp-backup */
2036                  '<p>' . __( 'The %s directory exists but is not writable. This directory is used to improve the stability of plugin and theme updates. Please make sure the server has write permissions to this directory.' ) . '</p>',
2037                  '<code>wp-content/upgrade-temp-backup</code>'
2038              );
2039              return $result;
2040          }
2041  
2042          if ( ! $backup_dir_exists && $upgrade_dir_exists && ! $upgrade_dir_is_writable ) {
2043              $result['status']      = 'critical';
2044              $result['label']       = __( 'The upgrade directory exists but is not writable' );
2045              $result['description'] = sprintf(
2046                  /* translators: %s: wp-content/upgrade */
2047                  '<p>' . __( 'The %s directory exists but is not writable. This directory is used for plugin and theme updates. Please make sure the server has write permissions to this directory.' ) . '</p>',
2048                  '<code>wp-content/upgrade</code>'
2049              );
2050              return $result;
2051          }
2052  
2053          if ( ! $upgrade_dir_exists && ! $wp_filesystem->is_writable( $wp_content ) ) {
2054              $result['status']      = 'critical';
2055              $result['label']       = __( 'The upgrade directory cannot be created' );
2056              $result['description'] = sprintf(
2057                  /* translators: 1: wp-content/upgrade, 2: wp-content. */
2058                  '<p>' . __( 'The %1$s directory does not exist, and the server does not have write permissions in %2$s to create it. This directory is used for plugin and theme updates. Please make sure the server has write permissions in %2$s.' ) . '</p>',
2059                  '<code>wp-content/upgrade</code>',
2060                  '<code>wp-content</code>'
2061              );
2062              return $result;
2063          }
2064  
2065          return $result;
2066      }
2067  
2068      /**
2069       * Tests if loopbacks work as expected.
2070       *
2071       * A loopback is when WordPress queries itself, for example to start a new WP_Cron instance,
2072       * or when editing a plugin or theme. This has shown itself to be a recurring issue,
2073       * as code can very easily break this interaction.
2074       *
2075       * @since 5.2.0
2076       *
2077       * @return array The test results.
2078       */
2079  	public function get_test_loopback_requests() {
2080          $result = array(
2081              'label'       => __( 'Your site can perform loopback requests' ),
2082              'status'      => 'good',
2083              'badge'       => array(
2084                  'label' => __( 'Performance' ),
2085                  'color' => 'blue',
2086              ),
2087              'description' => sprintf(
2088                  '<p>%s</p>',
2089                  __( 'Loopback requests are used to run scheduled events, and are also used by the built-in editors for themes and plugins to verify code stability.' )
2090              ),
2091              'actions'     => '',
2092              'test'        => 'loopback_requests',
2093          );
2094  
2095          $check_loopback = $this->can_perform_loopback();
2096  
2097          $result['status'] = $check_loopback->status;
2098  
2099          if ( 'good' !== $result['status'] ) {
2100              $result['label'] = __( 'Your site could not complete a loopback request' );
2101  
2102              $result['description'] .= sprintf(
2103                  '<p>%s</p>',
2104                  $check_loopback->message
2105              );
2106          }
2107  
2108          return $result;
2109      }
2110  
2111      /**
2112       * Tests if HTTP requests are blocked.
2113       *
2114       * It's possible to block all outgoing communication (with the possibility of allowing certain
2115       * hosts) via the HTTP API. This may create problems for users as many features are running as
2116       * services these days.
2117       *
2118       * @since 5.2.0
2119       *
2120       * @return array The test results.
2121       */
2122  	public function get_test_http_requests() {
2123          $result = array(
2124              'label'       => __( 'HTTP requests seem to be working as expected' ),
2125              'status'      => 'good',
2126              'badge'       => array(
2127                  'label' => __( 'Performance' ),
2128                  'color' => 'blue',
2129              ),
2130              'description' => sprintf(
2131                  '<p>%s</p>',
2132                  __( 'It is possible for site maintainers to block all, or some, communication to other sites and services. If set up incorrectly, this may prevent plugins and themes from working as intended.' )
2133              ),
2134              'actions'     => '',
2135              'test'        => 'http_requests',
2136          );
2137  
2138          $blocked = false;
2139          $hosts   = array();
2140  
2141          if ( defined( 'WP_HTTP_BLOCK_EXTERNAL' ) && WP_HTTP_BLOCK_EXTERNAL ) {
2142              $blocked = true;
2143          }
2144  
2145          if ( defined( 'WP_ACCESSIBLE_HOSTS' ) ) {
2146              $hosts = explode( ',', WP_ACCESSIBLE_HOSTS );
2147          }
2148  
2149          if ( $blocked && 0 === count( $hosts ) ) {
2150              $result['status'] = 'critical';
2151  
2152              $result['label'] = __( 'HTTP requests are blocked' );
2153  
2154              $result['description'] .= sprintf(
2155                  '<p>%s</p>',
2156                  sprintf(
2157                      /* translators: %s: Name of the constant used. */
2158                      __( 'HTTP requests have been blocked by the %s constant, with no allowed hosts.' ),
2159                      '<code>WP_HTTP_BLOCK_EXTERNAL</code>'
2160                  )
2161              );
2162          }
2163  
2164          if ( $blocked && 0 < count( $hosts ) ) {
2165              $result['status'] = 'recommended';
2166  
2167              $result['label'] = __( 'HTTP requests are partially blocked' );
2168  
2169              $result['description'] .= sprintf(
2170                  '<p>%s</p>',
2171                  sprintf(
2172                      /* translators: 1: Name of the constant used. 2: List of allowed hostnames. */
2173                      __( 'HTTP requests have been blocked by the %1$s constant, with some allowed hosts: %2$s.' ),
2174                      '<code>WP_HTTP_BLOCK_EXTERNAL</code>',
2175                      implode( ',', $hosts )
2176                  )
2177              );
2178          }
2179  
2180          return $result;
2181      }
2182  
2183      /**
2184       * Tests if the REST API is accessible.
2185       *
2186       * Various security measures may block the REST API from working, or it may have been disabled in general.
2187       * This is required for the new block editor to work, so we explicitly test for this.
2188       *
2189       * @since 5.2.0
2190       *
2191       * @return array The test results.
2192       */
2193  	public function get_test_rest_availability() {
2194          $result = array(
2195              'label'       => __( 'The REST API is available' ),
2196              'status'      => 'good',
2197              'badge'       => array(
2198                  'label' => __( 'Performance' ),
2199                  'color' => 'blue',
2200              ),
2201              'description' => sprintf(
2202                  '<p>%s</p>',
2203                  __( 'The REST API is one way that WordPress and other applications communicate with the server. For example, the block editor screen relies on the REST API to display and save your posts and pages.' )
2204              ),
2205              'actions'     => '',
2206              'test'        => 'rest_availability',
2207          );
2208  
2209          $cookies = wp_unslash( $_COOKIE );
2210          $timeout = 10; // 10 seconds.
2211          $headers = array(
2212              'Cache-Control' => 'no-cache',
2213              'X-WP-Nonce'    => wp_create_nonce( 'wp_rest' ),
2214          );
2215          /** This filter is documented in wp-includes/class-wp-http-streams.php */
2216          $sslverify = apply_filters( 'https_local_ssl_verify', false );
2217  
2218          // Include Basic auth in loopback requests.
2219          if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
2220              $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
2221          }
2222  
2223          $url = rest_url( 'wp/v2/types/post' );
2224  
2225          // The context for this is editing with the new block editor.
2226          $url = add_query_arg(
2227              array(
2228                  'context' => 'edit',
2229              ),
2230              $url
2231          );
2232  
2233          $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
2234  
2235          if ( is_wp_error( $r ) ) {
2236              $result['status'] = 'critical';
2237  
2238              $result['label'] = __( 'The REST API encountered an error' );
2239  
2240              $result['description'] .= sprintf(
2241                  '<p>%s</p><p>%s<br>%s</p>',
2242                  __( 'When testing the REST API, an error was encountered:' ),
2243                  sprintf(
2244                      // translators: %s: The REST API URL.
2245                      __( 'REST API Endpoint: %s' ),
2246                      $url
2247                  ),
2248                  sprintf(
2249                      // translators: 1: The WordPress error code. 2: The WordPress error message.
2250                      __( 'REST API Response: (%1$s) %2$s' ),
2251                      $r->get_error_code(),
2252                      $r->get_error_message()
2253                  )
2254              );
2255          } elseif ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
2256              $result['status'] = 'recommended';
2257  
2258              $result['label'] = __( 'The REST API encountered an unexpected result' );
2259  
2260              $result['description'] .= sprintf(
2261                  '<p>%s</p><p>%s<br>%s</p>',
2262                  __( 'When testing the REST API, an unexpected result was returned:' ),
2263                  sprintf(
2264                      // translators: %s: The REST API URL.
2265                      __( 'REST API Endpoint: %s' ),
2266                      $url
2267                  ),
2268                  sprintf(
2269                      // translators: 1: The WordPress error code. 2: The HTTP status code error message.
2270                      __( 'REST API Response: (%1$s) %2$s' ),
2271                      wp_remote_retrieve_response_code( $r ),
2272                      wp_remote_retrieve_response_message( $r )
2273                  )
2274              );
2275          } else {
2276              $json = json_decode( wp_remote_retrieve_body( $r ), true );
2277  
2278              if ( false !== $json && ! isset( $json['capabilities'] ) ) {
2279                  $result['status'] = 'recommended';
2280  
2281                  $result['label'] = __( 'The REST API did not behave correctly' );
2282  
2283                  $result['description'] .= sprintf(
2284                      '<p>%s</p>',
2285                      sprintf(
2286                          /* translators: %s: The name of the query parameter being tested. */
2287                          __( 'The REST API did not process the %s query parameter correctly.' ),
2288                          '<code>context</code>'
2289                      )
2290                  );
2291              }
2292          }
2293  
2294          return $result;
2295      }
2296  
2297      /**
2298       * Tests if 'file_uploads' directive in PHP.ini is turned off.
2299       *
2300       * @since 5.5.0
2301       *
2302       * @return array The test results.
2303       */
2304  	public function get_test_file_uploads() {
2305          $result = array(
2306              'label'       => __( 'Files can be uploaded' ),
2307              'status'      => 'good',
2308              'badge'       => array(
2309                  'label' => __( 'Performance' ),
2310                  'color' => 'blue',
2311              ),
2312              'description' => sprintf(
2313                  '<p>%s</p>',
2314                  sprintf(
2315                      /* translators: 1: file_uploads, 2: php.ini */
2316                      __( 'The %1$s directive in %2$s determines if uploading files is allowed on your site.' ),
2317                      '<code>file_uploads</code>',
2318                      '<code>php.ini</code>'
2319                  )
2320              ),
2321              'actions'     => '',
2322              'test'        => 'file_uploads',
2323          );
2324  
2325          if ( ! function_exists( 'ini_get' ) ) {
2326              $result['status']       = 'critical';
2327              $result['description'] .= sprintf(
2328                  /* translators: %s: ini_get() */
2329                  __( 'The %s function has been disabled, some media settings are unavailable because of this.' ),
2330                  '<code>ini_get()</code>'
2331              );
2332              return $result;
2333          }
2334  
2335          if ( empty( ini_get( 'file_uploads' ) ) ) {
2336              $result['status']       = 'critical';
2337              $result['description'] .= sprintf(
2338                  '<p>%s</p>',
2339                  sprintf(
2340                      /* translators: 1: file_uploads, 2: 0 */
2341                      __( '%1$s is set to %2$s. You won\'t be able to upload files on your site.' ),
2342                      '<code>file_uploads</code>',
2343                      '<code>0</code>'
2344                  )
2345              );
2346              return $result;
2347          }
2348  
2349          $post_max_size       = ini_get( 'post_max_size' );
2350          $upload_max_filesize = ini_get( 'upload_max_filesize' );
2351  
2352          if ( wp_convert_hr_to_bytes( $post_max_size ) < wp_convert_hr_to_bytes( $upload_max_filesize ) ) {
2353              $result['label'] = sprintf(
2354                  /* translators: 1: post_max_size, 2: upload_max_filesize */
2355                  __( 'The "%1$s" value is smaller than "%2$s"' ),
2356                  'post_max_size',
2357                  'upload_max_filesize'
2358              );
2359              $result['status'] = 'recommended';
2360  
2361              if ( 0 === wp_convert_hr_to_bytes( $post_max_size ) ) {
2362                  $result['description'] = sprintf(
2363                      '<p>%s</p>',
2364                      sprintf(
2365                          /* translators: 1: post_max_size, 2: upload_max_filesize */
2366                          __( 'The setting for %1$s is currently configured as 0, this could cause some problems when trying to upload files through plugin or theme features that rely on various upload methods. It is recommended to configure this setting to a fixed value, ideally matching the value of %2$s, as some upload methods read the value 0 as either unlimited, or disabled.' ),
2367                          '<code>post_max_size</code>',
2368                          '<code>upload_max_filesize</code>'
2369                      )
2370                  );
2371              } else {
2372                  $result['description'] = sprintf(
2373                      '<p>%s</p>',
2374                      sprintf(
2375                          /* translators: 1: post_max_size, 2: upload_max_filesize */
2376                          __( 'The setting for %1$s is smaller than %2$s, this could cause some problems when trying to upload files.' ),
2377                          '<code>post_max_size</code>',
2378                          '<code>upload_max_filesize</code>'
2379                      )
2380                  );
2381              }
2382  
2383              return $result;
2384          }
2385  
2386          return $result;
2387      }
2388  
2389      /**
2390       * Tests if the Authorization header has the expected values.
2391       *
2392       * @since 5.6.0
2393       *
2394       * @return array
2395       */
2396  	public function get_test_authorization_header() {
2397          $result = array(
2398              'label'       => __( 'The Authorization header is working as expected' ),
2399              'status'      => 'good',
2400              'badge'       => array(
2401                  'label' => __( 'Security' ),
2402                  'color' => 'blue',
2403              ),
2404              'description' => sprintf(
2405                  '<p>%s</p>',
2406                  __( 'The Authorization header is used by third-party applications you have approved for this site. Without this header, those apps cannot connect to your site.' )
2407              ),
2408              'actions'     => '',
2409              'test'        => 'authorization_header',
2410          );
2411  
2412          if ( ! isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) {
2413              $result['label'] = __( 'The authorization header is missing' );
2414          } elseif ( 'user' !== $_SERVER['PHP_AUTH_USER'] || 'pwd' !== $_SERVER['PHP_AUTH_PW'] ) {
2415              $result['label'] = __( 'The authorization header is invalid' );
2416          } else {
2417              return $result;
2418          }
2419  
2420          $result['status']       = 'recommended';
2421          $result['description'] .= sprintf(
2422              '<p>%s</p>',
2423              __( 'If you are still seeing this warning after having tried the actions below, you may need to contact your hosting provider for further assistance.' )
2424          );
2425  
2426          if ( ! function_exists( 'got_mod_rewrite' ) ) {
2427              require_once  ABSPATH . 'wp-admin/includes/misc.php';
2428          }
2429  
2430          if ( got_mod_rewrite() ) {
2431              $result['actions'] .= sprintf(
2432                  '<p><a href="%s">%s</a></p>',
2433                  esc_url( admin_url( 'options-permalink.php' ) ),
2434                  __( 'Flush permalinks' )
2435              );
2436          } else {
2437              $result['actions'] .= sprintf(
2438                  '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
2439                  __( 'https://developer.wordpress.org/rest-api/frequently-asked-questions/#why-is-authentication-not-working' ),
2440                  __( 'Learn how to configure the Authorization header.' ),
2441                  /* translators: Hidden accessibility text. */
2442                  __( '(opens in a new tab)' )
2443              );
2444          }
2445  
2446          return $result;
2447      }
2448  
2449      /**
2450       * Tests if a full page cache is available.
2451       *
2452       * @since 6.1.0
2453       *
2454       * @return array The test result.
2455       */
2456  	public function get_test_page_cache() {
2457          $description  = '<p>' . __( 'Page cache enhances the speed and performance of your site by saving and serving static pages instead of calling for a page every time a user visits.' ) . '</p>';
2458          $description .= '<p>' . __( 'Page cache is detected by looking for an active page cache plugin as well as making three requests to the homepage and looking for one or more of the following HTTP client caching response headers:' ) . '</p>';
2459          $description .= '<code>' . implode( '</code>, <code>', array_keys( $this->get_page_cache_headers() ) ) . '.</code>';
2460  
2461          $result = array(
2462              'badge'       => array(
2463                  'label' => __( 'Performance' ),
2464                  'color' => 'blue',
2465              ),
2466              'description' => wp_kses_post( $description ),
2467              'test'        => 'page_cache',
2468              'status'      => 'good',
2469              'label'       => '',
2470              'actions'     => sprintf(
2471                  '<p><a href="%1$s" target="_blank" rel="noreferrer">%2$s<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
2472                  __( 'https://developer.wordpress.org/advanced-administration/performance/optimization/#caching' ),
2473                  __( 'Learn more about page cache' ),
2474                  /* translators: Hidden accessibility text. */
2475                  __( '(opens in a new tab)' )
2476              ),
2477          );
2478  
2479          $page_cache_detail = $this->get_page_cache_detail();
2480  
2481          if ( is_wp_error( $page_cache_detail ) ) {
2482              $result['label']  = __( 'Unable to detect the presence of page cache' );
2483              $result['status'] = 'recommended';
2484              $error_info       = sprintf(
2485              /* translators: 1: Error message, 2: Error code. */
2486                  __( 'Unable to detect page cache due to possible loopback request problem. Please verify that the loopback request test is passing. Error: %1$s (Code: %2$s)' ),
2487                  $page_cache_detail->get_error_message(),
2488                  $page_cache_detail->get_error_code()
2489              );
2490              $result['description'] = wp_kses_post( "<p>$error_info</p>" ) . $result['description'];
2491              return $result;
2492          }
2493  
2494          $result['status'] = $page_cache_detail['status'];
2495  
2496          switch ( $page_cache_detail['status'] ) {
2497              case 'recommended':
2498                  $result['label'] = __( 'Page cache is not detected but the server response time is OK' );
2499                  break;
2500              case 'good':
2501                  $result['label'] = __( 'Page cache is detected and the server response time is good' );
2502                  break;
2503              default:
2504                  if ( empty( $page_cache_detail['headers'] ) && ! $page_cache_detail['advanced_cache_present'] ) {
2505                      $result['label'] = __( 'Page cache is not detected and the server response time is slow' );
2506                  } else {
2507                      $result['label'] = __( 'Page cache is detected but the server response time is still slow' );
2508                  }
2509          }
2510  
2511          $page_cache_test_summary = array();
2512  
2513          if ( empty( $page_cache_detail['response_time'] ) ) {
2514              $page_cache_test_summary[] = '<span class="dashicons dashicons-dismiss" aria-hidden="true"></span> ' . __( 'Server response time could not be determined. Verify that loopback requests are working.' );
2515          } else {
2516  
2517              $threshold = $this->get_good_response_time_threshold();
2518              if ( $page_cache_detail['response_time'] < $threshold ) {
2519                  $page_cache_test_summary[] = '<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span> ' . sprintf(
2520                      /* translators: 1: The response time in milliseconds, 2: The recommended threshold in milliseconds. */
2521                      __( 'Median server response time was %1$s milliseconds. This is less than the recommended %2$s milliseconds threshold.' ),
2522                      number_format_i18n( $page_cache_detail['response_time'] ),
2523                      number_format_i18n( $threshold )
2524                  );
2525              } else {
2526                  $page_cache_test_summary[] = '<span class="dashicons dashicons-warning" aria-hidden="true"></span> ' . sprintf(
2527                      /* translators: 1: The response time in milliseconds, 2: The recommended threshold in milliseconds. */
2528                      __( 'Median server response time was %1$s milliseconds. It should be less than the recommended %2$s milliseconds threshold.' ),
2529                      number_format_i18n( $page_cache_detail['response_time'] ),
2530                      number_format_i18n( $threshold )
2531                  );
2532              }
2533  
2534              if ( empty( $page_cache_detail['headers'] ) ) {
2535                  $page_cache_test_summary[] = '<span class="dashicons dashicons-warning" aria-hidden="true"></span> ' . __( 'No client caching response headers were detected.' );
2536              } else {
2537                  $headers_summary  = '<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>';
2538                  $headers_summary .= ' ' . sprintf(
2539                      /* translators: %d: Number of caching headers. */
2540                      _n(
2541                          'There was %d client caching response header detected:',
2542                          'There were %d client caching response headers detected:',
2543                          count( $page_cache_detail['headers'] )
2544                      ),
2545                      count( $page_cache_detail['headers'] )
2546                  );
2547                  $headers_summary          .= ' <code>' . implode( '</code>, <code>', $page_cache_detail['headers'] ) . '</code>.';
2548                  $page_cache_test_summary[] = $headers_summary;
2549              }
2550          }
2551  
2552          if ( $page_cache_detail['advanced_cache_present'] ) {
2553              $page_cache_test_summary[] = '<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span> ' . __( 'A page cache plugin was detected.' );
2554          } elseif ( ! ( is_array( $page_cache_detail ) && ! empty( $page_cache_detail['headers'] ) ) ) {
2555              // Note: This message is not shown if client caching response headers were present since an external caching layer may be employed.
2556              $page_cache_test_summary[] = '<span class="dashicons dashicons-warning" aria-hidden="true"></span> ' . __( 'A page cache plugin was not detected.' );
2557          }
2558  
2559          $result['description'] .= '<ul><li>' . implode( '</li><li>', $page_cache_test_summary ) . '</li></ul>';
2560          return $result;
2561      }
2562  
2563      /**
2564       * Tests if the site uses persistent object cache and recommends to use it if not.
2565       *
2566       * @since 6.1.0
2567       *
2568       * @return array The test result.
2569       */
2570  	public function get_test_persistent_object_cache() {
2571          /**
2572           * Filters the action URL for the persistent object cache health check.
2573           *
2574           * @since 6.1.0
2575           *
2576           * @param string $action_url Learn more link for persistent object cache health check.
2577           */
2578          $action_url = apply_filters(
2579              'site_status_persistent_object_cache_url',
2580              /* translators: Localized Support reference. */
2581              __( 'https://developer.wordpress.org/advanced-administration/performance/optimization/#persistent-object-cache' )
2582          );
2583  
2584          $result = array(
2585              'test'        => 'persistent_object_cache',
2586              'status'      => 'good',
2587              'badge'       => array(
2588                  'label' => __( 'Performance' ),
2589                  'color' => 'blue',
2590              ),
2591              'label'       => __( 'A persistent object cache is being used' ),
2592              'description' => sprintf(
2593                  '<p>%s</p>',
2594                  __( 'A persistent object cache makes your site&#8217;s database more efficient, resulting in faster load times because WordPress can retrieve your site&#8217;s content and settings much more quickly.' )
2595              ),
2596              'actions'     => sprintf(
2597                  '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
2598                  esc_url( $action_url ),
2599                  __( 'Learn more about persistent object caching.' ),
2600                  /* translators: Hidden accessibility text. */
2601                  __( '(opens in a new tab)' )
2602              ),
2603          );
2604  
2605          if ( wp_using_ext_object_cache() ) {
2606              return $result;
2607          }
2608  
2609          if ( ! $this->should_suggest_persistent_object_cache() ) {
2610              $result['label'] = __( 'A persistent object cache is not required' );
2611  
2612              return $result;
2613          }
2614  
2615          $available_services = $this->available_object_cache_services();
2616  
2617          $notes = __( 'Your hosting provider can tell you if a persistent object cache can be enabled on your site.' );
2618  
2619          if ( ! empty( $available_services ) ) {
2620              $notes .= ' ' . sprintf(
2621                  /* translators: Available object caching services. */
2622                  __( 'Your host appears to support the following object caching services: %s.' ),
2623                  implode( ', ', $available_services )
2624              );
2625          }
2626  
2627          /**
2628           * Filters the second paragraph of the health check's description
2629           * when suggesting the use of a persistent object cache.
2630           *
2631           * Hosts may want to replace the notes to recommend their preferred object caching solution.
2632           *
2633           * Plugin authors may want to append notes (not replace) on why object caching is recommended for their plugin.
2634           *
2635           * @since 6.1.0
2636           *
2637           * @param string   $notes              The notes appended to the health check description.
2638           * @param string[] $available_services The list of available persistent object cache services.
2639           */
2640          $notes = apply_filters( 'site_status_persistent_object_cache_notes', $notes, $available_services );
2641  
2642          $result['status']       = 'recommended';
2643          $result['label']        = __( 'You should use a persistent object cache' );
2644          $result['description'] .= sprintf(
2645              '<p>%s</p>',
2646              wp_kses(
2647                  $notes,
2648                  array(
2649                      'a'      => array( 'href' => true ),
2650                      'code'   => true,
2651                      'em'     => true,
2652                      'strong' => true,
2653                  )
2654              )
2655          );
2656  
2657          return $result;
2658      }
2659  
2660      /**
2661       * Calculates total amount of autoloaded data.
2662       *
2663       * @since 6.6.0
2664       *
2665       * @return int Autoloaded data in bytes.
2666       */
2667  	public function get_autoloaded_options_size() {
2668          $alloptions = wp_load_alloptions();
2669  
2670          $total_length = 0;
2671  
2672          foreach ( $alloptions as $option_value ) {
2673              if ( is_array( $option_value ) || is_object( $option_value ) ) {
2674                  $option_value = maybe_serialize( $option_value );
2675              }
2676              $total_length += strlen( (string) $option_value );
2677          }
2678  
2679          return $total_length;
2680      }
2681  
2682      /**
2683       * Tests the number of autoloaded options.
2684       *
2685       * @since 6.6.0
2686       *
2687       * @return array The test results.
2688       */
2689  	public function get_test_autoloaded_options() {
2690          $autoloaded_options_size  = $this->get_autoloaded_options_size();
2691          $autoloaded_options_count = count( wp_load_alloptions() );
2692  
2693          $base_description = __( 'Autoloaded options are configuration settings for plugins and themes that are automatically loaded with every page load in WordPress. Having too many autoloaded options can slow down your site.' );
2694  
2695          $result = array(
2696              'label'       => __( 'Autoloaded options are acceptable' ),
2697              'status'      => 'good',
2698              'badge'       => array(
2699                  'label' => __( 'Performance' ),
2700                  'color' => 'blue',
2701              ),
2702              'description' => sprintf(
2703                  /* translators: 1: Number of autoloaded options, 2: Autoloaded options size. */
2704                  '<p>' . esc_html( $base_description ) . ' ' . __( 'Your site has %1$s autoloaded options (size: %2$s) in the options table, which is acceptable.' ) . '</p>',
2705                  $autoloaded_options_count,
2706                  size_format( $autoloaded_options_size )
2707              ),
2708              'actions'     => '',
2709              'test'        => 'autoloaded_options',
2710          );
2711  
2712          /**
2713           * Filters max bytes threshold to trigger warning in Site Health.
2714           *
2715           * @since 6.6.0
2716           *
2717           * @param int $limit Autoloaded options threshold size. Default 800000.
2718           */
2719          $limit = apply_filters( 'site_status_autoloaded_options_size_limit', 800000 );
2720  
2721          if ( $autoloaded_options_size < $limit ) {
2722              return $result;
2723          }
2724  
2725          $result['status']      = 'critical';
2726          $result['label']       = __( 'Autoloaded options could affect performance' );
2727          $result['description'] = sprintf(
2728              /* translators: 1: Number of autoloaded options, 2: Autoloaded options size. */
2729              '<p>' . esc_html( $base_description ) . ' ' . __( 'Your site has %1$s autoloaded options (size: %2$s) in the options table, which could cause your site to be slow. You can review the options being autoloaded in your database and remove any options that are no longer needed by your site.' ) . '</p>',
2730              $autoloaded_options_count,
2731              size_format( $autoloaded_options_size )
2732          );
2733  
2734          /**
2735           * Filters description to be shown on Site Health warning when threshold is met.
2736           *
2737           * @since 6.6.0
2738           *
2739           * @param string $description Description message when autoloaded options bigger than threshold.
2740           */
2741          $result['description'] = apply_filters( 'site_status_autoloaded_options_limit_description', $result['description'] );
2742  
2743          $result['actions'] = sprintf(
2744              /* translators: 1: HelpHub URL, 2: Link description. */
2745              '<p><a target="_blank" href="%1$s">%2$s</a></p>',
2746              esc_url( __( 'https://developer.wordpress.org/advanced-administration/performance/optimization/#autoloaded-options' ) ),
2747              __( 'More info about optimizing autoloaded options' )
2748          );
2749  
2750          /**
2751           * Filters actionable information to tackle the problem. It can be a link to an external guide.
2752           *
2753           * @since 6.6.0
2754           *
2755           * @param string $actions Call to Action to be used to point to the right direction to solve the issue.
2756           */
2757          $result['actions'] = apply_filters( 'site_status_autoloaded_options_action_to_perform', $result['actions'] );
2758          return $result;
2759      }
2760  
2761      /**
2762       * Tests whether search engine indexing is enabled.
2763       *
2764       * Surfaces as “good” if `blog_public === 1`, or “recommended” if `blog_public === 0`.
2765       *
2766       * @since 6.9.0
2767       *
2768       * @return array The test results.
2769       */
2770  	public function get_test_search_engine_visibility() {
2771          $result = array(
2772              'label'       => __( 'Search engine indexing is enabled.', 'default' ),
2773              'status'      => 'good',
2774              'badge'       => array(
2775                  'label' => __( 'Privacy', 'default' ),
2776                  'color' => 'blue',
2777              ),
2778              'description' => sprintf(
2779                  '<p>%s</p>',
2780                  __( 'Search engines can crawl and index your site. No action needed.', 'default' )
2781              ),
2782              'actions'     => sprintf(
2783                  '<p><a href="%1$s">%2$s</a></p>',
2784                  esc_url( admin_url( 'options-reading.php#blog_public' ) ),
2785                  __( 'Review your visibility settings', 'default' )
2786              ),
2787              'test'        => 'search_engine_visibility',
2788          );
2789  
2790          // If indexing is discouraged, flip to “recommended”:
2791          if ( ! get_option( 'blog_public' ) ) {
2792              $result['status']         = 'recommended';
2793              $result['label']          = __( 'Search engines are discouraged from indexing this site.', 'default' );
2794              $result['badge']['color'] = 'blue';
2795              $result['description']    = sprintf(
2796                  '<p>%s</p>',
2797                  __( 'Your site is hidden from search engines. Consider enabling indexing if this is a public site.', 'default' )
2798              );
2799          }
2800  
2801          return $result;
2802      }
2803  
2804      /**
2805       * Tests if opcode cache is enabled and available.
2806       *
2807       * @since 7.0.0
2808       *
2809       * @return array<string, string|array<string, string>> The test result.
2810       */
2811  	public function get_test_opcode_cache(): array {
2812          $opcode_cache_enabled = false;
2813          if ( function_exists( 'opcache_get_status' ) ) {
2814              $status = @opcache_get_status( false ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Warning emitted in failure case.
2815              if ( $status && true === $status['opcache_enabled'] ) {
2816                  $opcode_cache_enabled = true;
2817              }
2818          }
2819  
2820          $result = array(
2821              'label'       => __( 'Opcode cache is enabled' ),
2822              'status'      => 'good',
2823              'badge'       => array(
2824                  'label' => __( 'Performance' ),
2825                  'color' => 'blue',
2826              ),
2827              'description' => sprintf(
2828                  '<p>%s</p>',
2829                  __( 'Opcode cache improves PHP performance by storing precompiled script bytecode in memory, reducing the need for PHP to load and parse scripts on each request.' )
2830              ),
2831              'actions'     => sprintf(
2832                  '<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
2833                  esc_url( 'https://www.php.net/manual/en/book.opcache.php' ),
2834                  __( 'Learn more about OPcache.' ),
2835                  /* translators: Hidden accessibility text. */
2836                  __( '(opens in a new tab)' )
2837              ),
2838              'test'        => 'opcode_cache',
2839          );
2840  
2841          if ( ! $opcode_cache_enabled ) {
2842              $result['status']       = 'recommended';
2843              $result['label']        = __( 'Opcode cache is not enabled' );
2844              $result['description'] .= '<p>' . __( 'Enabling this cache can significantly improve the performance of your site.' ) . '</p>';
2845          }
2846  
2847          return $result;
2848      }
2849  
2850      /**
2851       * Returns a set of tests that belong to the site status page.
2852       *
2853       * Each site status test is defined here, they may be `direct` tests, that run on page load, or `async` tests
2854       * which will run later down the line via JavaScript calls to improve page performance and hopefully also user
2855       * experiences.
2856       *
2857       * @since 5.2.0
2858       * @since 5.6.0 Added support for `has_rest` and `permissions`.
2859       *
2860       * @return array The list of tests to run.
2861       */
2862  	public static function get_tests() {
2863          $tests = array(
2864              'direct' => array(
2865                  'wordpress_version'            => array(
2866                      'label' => __( 'WordPress Version' ),
2867                      'test'  => 'wordpress_version',
2868                  ),
2869                  'plugin_version'               => array(
2870                      'label' => __( 'Plugin Versions' ),
2871                      'test'  => 'plugin_version',
2872                  ),
2873                  'theme_version'                => array(
2874                      'label' => __( 'Theme Versions' ),
2875                      'test'  => 'theme_version',
2876                  ),
2877                  'php_version'                  => array(
2878                      'label' => __( 'PHP Version' ),
2879                      'test'  => 'php_version',
2880                  ),
2881                  'php_extensions'               => array(
2882                      'label' => __( 'PHP Extensions' ),
2883                      'test'  => 'php_extensions',
2884                  ),
2885                  'php_default_timezone'         => array(
2886                      'label' => __( 'PHP Default Timezone' ),
2887                      'test'  => 'php_default_timezone',
2888                  ),
2889                  'php_sessions'                 => array(
2890                      'label' => __( 'PHP Sessions' ),
2891                      'test'  => 'php_sessions',
2892                  ),
2893                  'sql_server'                   => array(
2894                      'label' => __( 'Database Server version' ),
2895                      'test'  => 'sql_server',
2896                  ),
2897                  'ssl_support'                  => array(
2898                      'label' => __( 'Secure communication' ),
2899                      'test'  => 'ssl_support',
2900                  ),
2901                  'scheduled_events'             => array(
2902                      'label' => __( 'Scheduled events' ),
2903                      'test'  => 'scheduled_events',
2904                  ),
2905                  'http_requests'                => array(
2906                      'label' => __( 'HTTP Requests' ),
2907                      'test'  => 'http_requests',
2908                  ),
2909                  'rest_availability'            => array(
2910                      'label'     => __( 'REST API availability' ),
2911                      'test'      => 'rest_availability',
2912                      'skip_cron' => true,
2913                  ),
2914                  'debug_enabled'                => array(
2915                      'label' => __( 'Debugging enabled' ),
2916                      'test'  => 'is_in_debug_mode',
2917                  ),
2918                  'file_uploads'                 => array(
2919                      'label' => __( 'File uploads' ),
2920                      'test'  => 'file_uploads',
2921                  ),
2922                  'plugin_theme_auto_updates'    => array(
2923                      'label' => __( 'Plugin and theme auto-updates' ),
2924                      'test'  => 'plugin_theme_auto_updates',
2925                  ),
2926                  'update_temp_backup_writable'  => array(
2927                      'label' => __( 'Plugin and theme temporary backup directory access' ),
2928                      'test'  => 'update_temp_backup_writable',
2929                  ),
2930                  'available_updates_disk_space' => array(
2931                      'label' => __( 'Available disk space' ),
2932                      'test'  => 'available_updates_disk_space',
2933                  ),
2934                  'autoloaded_options'           => array(
2935                      'label' => __( 'Autoloaded options' ),
2936                      'test'  => 'autoloaded_options',
2937                  ),
2938                  'insecure_registration'        => array(
2939                      'label' => __( 'Open Registration with privileged default role' ),
2940                      'test'  => 'insecure_registration',
2941                  ),
2942                  'search_engine_visibility'     => array(
2943                      'label' => __( 'Search Engine Visibility' ),
2944                      'test'  => 'search_engine_visibility',
2945                  ),
2946                  'opcode_cache'                 => array(
2947                      'label' => __( 'Opcode cache' ),
2948                      'test'  => 'opcode_cache',
2949                  ),
2950              ),
2951              'async'  => array(
2952                  'dotorg_communication' => array(
2953                      'label'             => __( 'Communication with WordPress.org' ),
2954                      'test'              => rest_url( 'wp-site-health/v1/tests/dotorg-communication' ),
2955                      'has_rest'          => true,
2956                      'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_dotorg_communication' ),
2957                  ),
2958                  'background_updates'   => array(
2959                      'label'             => __( 'Background updates' ),
2960                      'test'              => rest_url( 'wp-site-health/v1/tests/background-updates' ),
2961                      'has_rest'          => true,
2962                      'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_background_updates' ),
2963                  ),
2964                  'loopback_requests'    => array(
2965                      'label'             => __( 'Loopback request' ),
2966                      'test'              => rest_url( 'wp-site-health/v1/tests/loopback-requests' ),
2967                      'has_rest'          => true,
2968                      'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_loopback_requests' ),
2969                  ),
2970                  'https_status'         => array(
2971                      'label'             => __( 'HTTPS status' ),
2972                      'test'              => rest_url( 'wp-site-health/v1/tests/https-status' ),
2973                      'has_rest'          => true,
2974                      'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_https_status' ),
2975                  ),
2976              ),
2977          );
2978  
2979          // Conditionally include Authorization header test if the site isn't protected by Basic Auth.
2980          if ( ! wp_is_site_protected_by_basic_auth() ) {
2981              $tests['async']['authorization_header'] = array(
2982                  'label'     => __( 'Authorization header' ),
2983                  'test'      => rest_url( 'wp-site-health/v1/tests/authorization-header' ),
2984                  'has_rest'  => true,
2985                  'headers'   => array( 'Authorization' => 'Basic ' . base64_encode( 'user:pwd' ) ),
2986                  'skip_cron' => true,
2987              );
2988          }
2989  
2990          // Only check for caches in production environments.
2991          if ( 'production' === wp_get_environment_type() ) {
2992              $tests['async']['page_cache'] = array(
2993                  'label'             => __( 'Page cache' ),
2994                  'test'              => rest_url( 'wp-site-health/v1/tests/page-cache' ),
2995                  'has_rest'          => true,
2996                  'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_page_cache' ),
2997              );
2998  
2999              $tests['direct']['persistent_object_cache'] = array(
3000                  'label' => __( 'Persistent object cache' ),
3001                  'test'  => 'persistent_object_cache',
3002              );
3003          }
3004  
3005          /**
3006           * Filters which site status tests are run on a site.
3007           *
3008           * The site health is determined by a set of tests based on best practices from
3009           * both the WordPress Hosting Team and web standards in general.
3010           *
3011           * Some sites may not have the same requirements, for example the automatic update
3012           * checks may be handled by a host, and are therefore disabled in core.
3013           * Or maybe you want to introduce a new test, is caching enabled/disabled/stale for example.
3014           *
3015           * Tests may be added either as direct, or asynchronous ones. Any test that may require some time
3016           * to complete should run asynchronously, to avoid extended loading periods within wp-admin.
3017           *
3018           * @since 5.2.0
3019           * @since 5.6.0 Added the `async_direct_test` array key for asynchronous tests.
3020           *              Added the `skip_cron` array key for all tests.
3021           *
3022           * @param array[] $tests {
3023           *     An associative array of direct and asynchronous tests.
3024           *
3025           *     @type array[] $direct {
3026           *         An array of direct tests.
3027           *
3028           *         @type array ...$identifier {
3029           *             `$identifier` should be a unique identifier for the test. Plugins and themes are encouraged to
3030           *             prefix test identifiers with their slug to avoid collisions between tests.
3031           *
3032           *             @type string   $label     The friendly label to identify the test.
3033           *             @type callable $test      The callback function that runs the test and returns its result.
3034           *             @type bool     $skip_cron Whether to skip this test when running as cron.
3035           *         }
3036           *     }
3037           *     @type array[] $async {
3038           *         An array of asynchronous tests.
3039           *
3040           *         @type array ...$identifier {
3041           *             `$identifier` should be a unique identifier for the test. Plugins and themes are encouraged to
3042           *             prefix test identifiers with their slug to avoid collisions between tests.
3043           *
3044           *             @type string   $label             The friendly label to identify the test.
3045           *             @type string   $test              An admin-ajax.php action to be called to perform the test, or
3046           *                                               if `$has_rest` is true, a URL to a REST API endpoint to perform
3047           *                                               the test.
3048           *             @type bool     $has_rest          Whether the `$test` property points to a REST API endpoint.
3049           *             @type bool     $skip_cron         Whether to skip this test when running as cron.
3050           *             @type callable $async_direct_test A manner of directly calling the test marked as asynchronous,
3051           *                                               as the scheduled event can not authenticate, and endpoints
3052           *                                               may require authentication.
3053           *         }
3054           *     }
3055           * }
3056           */
3057          $tests = apply_filters( 'site_status_tests', $tests );
3058  
3059          // Ensure that the filtered tests contain the required array keys.
3060          $tests = array_merge(
3061              array(
3062                  'direct' => array(),
3063                  'async'  => array(),
3064              ),
3065              $tests
3066          );
3067  
3068          return $tests;
3069      }
3070  
3071      /**
3072       * Adds a class to the body HTML tag.
3073       *
3074       * Filters the body class string for admin pages and adds our own class for easier styling.
3075       *
3076       * @since 5.2.0
3077       *
3078       * @param string $body_class The body class string.
3079       * @return string The modified body class string.
3080       */
3081  	public function admin_body_class( $body_class ) {
3082          $screen = get_current_screen();
3083          if ( 'site-health' !== $screen->id ) {
3084              return $body_class;
3085          }
3086  
3087          $body_class .= ' site-health';
3088  
3089          return $body_class;
3090      }
3091  
3092      /**
3093       * Initiates the WP_Cron schedule test cases.
3094       *
3095       * @since 5.2.0
3096       */
3097  	private function wp_schedule_test_init() {
3098          $this->schedules = wp_get_schedules();
3099          $this->get_cron_tasks();
3100      }
3101  
3102      /**
3103       * Populates the list of cron events and store them to a class-wide variable.
3104       *
3105       * @since 5.2.0
3106       */
3107  	private function get_cron_tasks() {
3108          $cron_tasks = _get_cron_array();
3109  
3110          if ( empty( $cron_tasks ) ) {
3111              $this->crons = new WP_Error( 'no_tasks', __( 'No scheduled events exist on this site.' ) );
3112              return;
3113          }
3114  
3115          $this->crons = array();
3116  
3117          foreach ( $cron_tasks as $time => $cron ) {
3118              foreach ( $cron as $hook => $dings ) {
3119                  foreach ( $dings as $sig => $data ) {
3120  
3121                      $this->crons[ "$hook-$sig-$time" ] = (object) array(
3122                          'hook'     => $hook,
3123                          'time'     => $time,
3124                          'sig'      => $sig,
3125                          'args'     => $data['args'],
3126                          'schedule' => $data['schedule'],
3127                          'interval' => $data['interval'] ?? null,
3128                      );
3129  
3130                  }
3131              }
3132          }
3133      }
3134  
3135      /**
3136       * Checks if any scheduled tasks have been missed.
3137       *
3138       * Returns a boolean value of `true` if a scheduled task has been missed and ends processing.
3139       *
3140       * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value.
3141       *
3142       * @since 5.2.0
3143       *
3144       * @return bool|WP_Error True if a cron was missed, false if not. WP_Error if the cron is set to that.
3145       */
3146  	public function has_missed_cron() {
3147          if ( is_wp_error( $this->crons ) ) {
3148              return $this->crons;
3149          }
3150  
3151          foreach ( $this->crons as $id => $cron ) {
3152              if ( ( $cron->time - time() ) < $this->timeout_missed_cron ) {
3153                  $this->last_missed_cron = $cron->hook;
3154                  return true;
3155              }
3156          }
3157  
3158          return false;
3159      }
3160  
3161      /**
3162       * Checks if any scheduled tasks are late.
3163       *
3164       * Returns a boolean value of `true` if a scheduled task is late and ends processing.
3165       *
3166       * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value.
3167       *
3168       * @since 5.3.0
3169       *
3170       * @return bool|WP_Error True if a cron is late, false if not. WP_Error if the cron is set to that.
3171       */
3172  	public function has_late_cron() {
3173          if ( is_wp_error( $this->crons ) ) {
3174              return $this->crons;
3175          }
3176  
3177          foreach ( $this->crons as $id => $cron ) {
3178              $cron_offset = $cron->time - time();
3179              if (
3180                  $cron_offset >= $this->timeout_missed_cron &&
3181                  $cron_offset < $this->timeout_late_cron
3182              ) {
3183                  $this->last_late_cron = $cron->hook;
3184                  return true;
3185              }
3186          }
3187  
3188          return false;
3189      }
3190  
3191      /**
3192       * Checks for potential issues with plugin and theme auto-updates.
3193       *
3194       * Though there is no way to 100% determine if plugin and theme auto-updates are configured
3195       * correctly, a few educated guesses could be made to flag any conditions that would
3196       * potentially cause unexpected behaviors.
3197       *
3198       * @since 5.5.0
3199       *
3200       * @return object The test results.
3201       */
3202  	public function detect_plugin_theme_auto_update_issues() {
3203          $mock_plugin = (object) array(
3204              'id'            => 'w.org/plugins/a-fake-plugin',
3205              'slug'          => 'a-fake-plugin',
3206              'plugin'        => 'a-fake-plugin/a-fake-plugin.php',
3207              'new_version'   => '9.9',
3208              'url'           => 'https://wordpress.org/plugins/a-fake-plugin/',
3209              'package'       => 'https://downloads.wordpress.org/plugin/a-fake-plugin.9.9.zip',
3210              'icons'         => array(
3211                  '2x' => 'https://ps.w.org/a-fake-plugin/assets/icon-256x256.png',
3212                  '1x' => 'https://ps.w.org/a-fake-plugin/assets/icon-128x128.png',
3213              ),
3214              'banners'       => array(
3215                  '2x' => 'https://ps.w.org/a-fake-plugin/assets/banner-1544x500.png',
3216                  '1x' => 'https://ps.w.org/a-fake-plugin/assets/banner-772x250.png',
3217              ),
3218              'banners_rtl'   => array(),
3219              'tested'        => '5.5.0',
3220              'requires_php'  => '5.6.20',
3221              'compatibility' => new stdClass(),
3222          );
3223  
3224          $mock_theme = (object) array(
3225              'theme'        => 'a-fake-theme',
3226              'new_version'  => '9.9',
3227              'url'          => 'https://wordpress.org/themes/a-fake-theme/',
3228              'package'      => 'https://downloads.wordpress.org/theme/a-fake-theme.9.9.zip',
3229              'requires'     => '5.0.0',
3230              'requires_php' => '5.6.20',
3231          );
3232  
3233          $test_plugins_enabled = wp_is_auto_update_forced_for_item( 'plugin', true, $mock_plugin );
3234          $test_themes_enabled  = wp_is_auto_update_forced_for_item( 'theme', true, $mock_theme );
3235  
3236          $ui_enabled_for_plugins = wp_is_auto_update_enabled_for_type( 'plugin' );
3237          $ui_enabled_for_themes  = wp_is_auto_update_enabled_for_type( 'theme' );
3238          $plugin_filter_present  = has_filter( 'auto_update_plugin' );
3239          $theme_filter_present   = has_filter( 'auto_update_theme' );
3240  
3241          if ( ( ! $test_plugins_enabled && $ui_enabled_for_plugins )
3242              || ( ! $test_themes_enabled && $ui_enabled_for_themes )
3243          ) {
3244              return (object) array(
3245                  'status'  => 'critical',
3246                  '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.' ),
3247              );
3248          }
3249  
3250          if ( ( ! $test_plugins_enabled && $plugin_filter_present )
3251              && ( ! $test_themes_enabled && $theme_filter_present )
3252          ) {
3253              return (object) array(
3254                  'status'  => 'recommended',
3255                  'message' => __( 'Auto-updates for plugins and themes appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ),
3256              );
3257          } elseif ( ! $test_plugins_enabled && $plugin_filter_present ) {
3258              return (object) array(
3259                  'status'  => 'recommended',
3260                  'message' => __( 'Auto-updates for plugins appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ),
3261              );
3262          } elseif ( ! $test_themes_enabled && $theme_filter_present ) {
3263              return (object) array(
3264                  'status'  => 'recommended',
3265                  'message' => __( 'Auto-updates for themes appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ),
3266              );
3267          }
3268  
3269          return (object) array(
3270              'status'  => 'good',
3271              'message' => __( 'There appear to be no issues with plugin and theme auto-updates.' ),
3272          );
3273      }
3274  
3275      /**
3276       * Runs a loopback test on the site.
3277       *
3278       * Loopbacks are what WordPress uses to communicate with itself to start up WP_Cron, scheduled posts,
3279       * make sure plugin or theme edits don't cause site failures and similar.
3280       *
3281       * @since 5.2.0
3282       *
3283       * @return object The test results.
3284       */
3285  	public function can_perform_loopback() {
3286          $body    = array( 'site-health' => 'loopback-test' );
3287          $cookies = wp_unslash( $_COOKIE );
3288          $timeout = 10; // 10 seconds.
3289          $headers = array(
3290              'Cache-Control' => 'no-cache',
3291          );
3292          /** This filter is documented in wp-includes/class-wp-http-streams.php */
3293          $sslverify = apply_filters( 'https_local_ssl_verify', false );
3294  
3295          // Include Basic auth in loopback requests.
3296          if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
3297              $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
3298          }
3299  
3300          $url = site_url( 'wp-cron.php' );
3301  
3302          /*
3303           * A post request is used for the wp-cron.php loopback test to cause the file
3304           * to finish early without triggering cron jobs. This has two benefits:
3305           * - cron jobs are not triggered a second time on the site health page,
3306           * - the loopback request finishes sooner providing a quicker result.
3307           *
3308           * Using a POST request causes the loopback to differ slightly to the standard
3309           * GET request WordPress uses for wp-cron.php loopback requests but is close
3310           * enough. See https://core.trac.wordpress.org/ticket/52547
3311           */
3312          $r = wp_remote_post( $url, compact( 'body', 'cookies', 'headers', 'timeout', 'sslverify' ) );
3313  
3314          if ( is_wp_error( $r ) ) {
3315              return (object) array(
3316                  'status'  => 'critical',
3317                  'message' => sprintf(
3318                      '%s<br>%s',
3319                      __( 'The loopback request to your site failed, this means features relying on them are not currently working as expected.' ),
3320                      sprintf(
3321                          /* translators: 1: The WordPress error message. 2: The WordPress error code. */
3322                          __( 'Error: %1$s (%2$s)' ),
3323                          $r->get_error_message(),
3324                          $r->get_error_code()
3325                      )
3326                  ),
3327              );
3328          }
3329  
3330          if ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
3331              return (object) array(
3332                  'status'  => 'recommended',
3333                  'message' => sprintf(
3334                      /* translators: %d: The HTTP response code returned. */
3335                      __( '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.' ),
3336                      wp_remote_retrieve_response_code( $r )
3337                  ),
3338              );
3339          }
3340  
3341          return (object) array(
3342              'status'  => 'good',
3343              'message' => __( 'The loopback request to your site completed successfully.' ),
3344          );
3345      }
3346  
3347      /**
3348       * Creates a weekly cron event, if one does not already exist.
3349       *
3350       * @since 5.4.0
3351       */
3352  	public function maybe_create_scheduled_event() {
3353          if ( ! wp_next_scheduled( 'wp_site_health_scheduled_check' ) && ! wp_installing() ) {
3354              wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', 'wp_site_health_scheduled_check' );
3355          }
3356      }
3357  
3358      /**
3359       * Runs the scheduled event to check and update the latest site health status for the website.
3360       *
3361       * @since 5.4.0
3362       */
3363  	public function wp_cron_scheduled_check() {
3364          // Bootstrap wp-admin, as WP_Cron doesn't do this for us.
3365          require_once trailingslashit( ABSPATH ) . 'wp-admin/includes/admin.php';
3366  
3367          $tests = WP_Site_Health::get_tests();
3368  
3369          $results = array();
3370  
3371          $site_status = array(
3372              'good'        => 0,
3373              'recommended' => 0,
3374              'critical'    => 0,
3375          );
3376  
3377          // Don't run https test on development environments.
3378          if ( $this->is_development_environment() ) {
3379              unset( $tests['async']['https_status'] );
3380          }
3381  
3382          foreach ( $tests['direct'] as $test ) {
3383              if ( ! empty( $test['skip_cron'] ) ) {
3384                  continue;
3385              }
3386  
3387              if ( is_string( $test['test'] ) ) {
3388                  $test_function = sprintf(
3389                      'get_test_%s',
3390                      $test['test']
3391                  );
3392  
3393                  if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) {
3394                      $results[] = $this->perform_test( array( $this, $test_function ) );
3395                      continue;
3396                  }
3397              }
3398  
3399              if ( is_callable( $test['test'] ) ) {
3400                  $results[] = $this->perform_test( $test['test'] );
3401              }
3402          }
3403  
3404          foreach ( $tests['async'] as $test ) {
3405              if ( ! empty( $test['skip_cron'] ) ) {
3406                  continue;
3407              }
3408  
3409              // Local endpoints may require authentication, so asynchronous tests can pass a direct test runner as well.
3410              if ( ! empty( $test['async_direct_test'] ) && is_callable( $test['async_direct_test'] ) ) {
3411                  // This test is callable, do so and continue to the next asynchronous check.
3412                  $results[] = $this->perform_test( $test['async_direct_test'] );
3413                  continue;
3414              }
3415  
3416              if ( is_string( $test['test'] ) ) {
3417                  // Check if this test has a REST API endpoint.
3418                  if ( isset( $test['has_rest'] ) && $test['has_rest'] ) {
3419                      $result_fetch = wp_remote_get(
3420                          $test['test'],
3421                          array(
3422                              'body' => array(
3423                                  '_wpnonce' => wp_create_nonce( 'wp_rest' ),
3424                              ),
3425                          )
3426                      );
3427                  } else {
3428                      $result_fetch = wp_remote_post(
3429                          admin_url( 'admin-ajax.php' ),
3430                          array(
3431                              'body' => array(
3432                                  'action'   => $test['test'],
3433                                  '_wpnonce' => wp_create_nonce( 'health-check-site-status' ),
3434                              ),
3435                          )
3436                      );
3437                  }
3438  
3439                  if ( ! is_wp_error( $result_fetch ) && 200 === wp_remote_retrieve_response_code( $result_fetch ) ) {
3440                      $result = json_decode( wp_remote_retrieve_body( $result_fetch ), true );
3441                  } else {
3442                      $result = false;
3443                  }
3444  
3445                  if ( is_array( $result ) ) {
3446                      $results[] = $result;
3447                  } else {
3448                      $results[] = array(
3449                          'status' => 'recommended',
3450                          'label'  => __( 'A test is unavailable' ),
3451                      );
3452                  }
3453              }
3454          }
3455  
3456          foreach ( $results as $result ) {
3457              if ( 'critical' === $result['status'] ) {
3458                  ++$site_status['critical'];
3459              } elseif ( 'recommended' === $result['status'] ) {
3460                  ++$site_status['recommended'];
3461              } else {
3462                  ++$site_status['good'];
3463              }
3464          }
3465  
3466          set_transient( 'health-check-site-status-result', wp_json_encode( $site_status ) );
3467      }
3468  
3469      /**
3470       * Checks if the current environment type is set to 'development' or 'local'.
3471       *
3472       * @since 5.6.0
3473       *
3474       * @return bool True if it is a development environment, false if not.
3475       */
3476  	public function is_development_environment() {
3477          return in_array( wp_get_environment_type(), array( 'development', 'local' ), true );
3478      }
3479  
3480      /**
3481       * Returns a mapping from response headers to an optional callback to verify if page cache is enabled or not.
3482       *
3483       * @since 6.1.0
3484       *
3485       * @return array<string, ?callable> Mapping of page caching headers and their (optional) verification callbacks.
3486       *                                  A null value means a simple existence check is used for the header.
3487       */
3488  	public function get_page_cache_headers(): array {
3489  
3490          $cache_hit_callback = static function ( $header_value ) {
3491              return 1 === preg_match( '/(^| |,)HIT(,| |$)/i', $header_value );
3492          };
3493  
3494          $cache_headers = array(
3495              // Standard HTTP caching headers.
3496              'cache-control'          => static function ( $header_value ) {
3497                  return (bool) preg_match( '/max-age=[1-9]/', $header_value );
3498              },
3499              'expires'                => static function ( $header_value ) {
3500                  return strtotime( $header_value ) > time();
3501              },
3502              'age'                    => static function ( $header_value ) {
3503                  return is_numeric( $header_value ) && $header_value > 0;
3504              },
3505              'last-modified'          => null,
3506              'etag'                   => null,
3507              'via'                    => null,
3508  
3509              /**
3510               * Custom caching headers.
3511               *
3512               * These do not seem to be actually used by any caching layers. There were first introduced in a Site Health
3513               * test in the AMP plugin. They were copied into the Performance Lab plugin's Site Health test before they
3514               * were merged into core.
3515               *
3516               * @link https://github.com/ampproject/amp-wp/pull/6849
3517               * @link https://github.com/WordPress/performance/pull/263
3518               * @link https://core.trac.wordpress.org/changeset/54043
3519               */
3520              'x-cache-enabled'        => static function ( $header_value ) {
3521                  return ( 'true' === strtolower( $header_value ) );
3522              },
3523              'x-cache-disabled'       => static function ( $header_value ) {
3524                  return ( 'on' !== strtolower( $header_value ) );
3525              },
3526  
3527              /**
3528               * CloudFlare.
3529               *
3530               * @link https://developers.cloudflare.com/cache/concepts/cache-responses/
3531               */
3532              'cf-cache-status'        => $cache_hit_callback,
3533  
3534              /**
3535               * Fastly.
3536               *
3537               * @link https://www.fastly.com/documentation/reference/http/http-headers/X-Cache/
3538               */
3539              'x-cache'                => $cache_hit_callback,
3540  
3541              /**
3542               * LightSpeed.
3543               *
3544               * @link https://docs.litespeedtech.com/lscache/devguide/controls/#x-litespeed-cache
3545               */
3546              'x-litespeed-cache'      => $cache_hit_callback,
3547  
3548              /**
3549               * OpenResty srcache-nginx-module.
3550               *
3551               * The `x-srcache-store-status` header indicates if the response was stored in the cache.
3552               * Valid values include `STORE` and `BYPASS`.
3553               *
3554               * The `x-srcache-fetch-status` header indicates if the response was fetched from the cache.
3555               * Valid values include `HIT`, `MISS`, and `BYPASS`.
3556               *
3557               * @link https://github.com/openresty/srcache-nginx-module
3558               */
3559              'x-srcache-store-status' => static function ( $header_value ) {
3560                  return 'store' === strtolower( $header_value );
3561              },
3562              'x-srcache-fetch-status' => $cache_hit_callback,
3563  
3564              /**
3565               * Nginx.
3566               *
3567               * @link https://blog.nginx.org/blog/nginx-caching-guide
3568               * @link https://www.inmotionhosting.com/support/website/nginx-cache-management/
3569               */
3570              'x-cache-status'         => $cache_hit_callback,
3571              'x-proxy-cache'          => $cache_hit_callback,
3572  
3573              /**
3574               * Varnish Cache.
3575               *
3576               * A header with a single number indicates it was not cached. If there are two numbers (or more), then this
3577               * indicates the response was cached.
3578               *
3579               * @link https://vinyl-cache.org/docs/2.1/faq/http.html
3580               * @link https://www.fastly.com/documentation/reference/http/http-headers/X-Varnish/
3581               * @link https://www.linuxjournal.com/content/speed-your-web-site-varnish
3582               */
3583              'x-varnish'              => static function ( $header_value ) {
3584                  return 1 === preg_match( '/^\d+ \d+/', $header_value );
3585              },
3586          );
3587  
3588          /**
3589           * Filters the list of cache headers supported by core.
3590           *
3591           * This list indicates how each of the specified headers will be checked to indicate if a page cache is enabled
3592           * or not. WordPress checks for each of the headers in the returned array. If the callback is provided, it will
3593           * be passed the value for the corresponding header and return a boolean value indicating if the header suggests
3594           * that a cache is active. If the value is `null` for the header, then WordPress will assume that a cache is
3595           * active if the header is present, regardless of its value.
3596           *
3597           * @since 6.1.0
3598           *
3599           * @param array<string, ?callable> $cache_headers Mapping from cache-related HTTP headers to whether they
3600           *                                                indicate if a page cache is enabled for the site. `null`
3601           *                                                indicates caching in the presence of the header; a callback is
3602           *                                                provided the header’s value and should return `true` if it
3603           *                                                implies that a cache is active.
3604           */
3605          return (array) apply_filters( 'site_status_page_cache_supported_cache_headers', $cache_headers );
3606      }
3607  
3608      /**
3609       * Checks if site has page cache enabled or not.
3610       *
3611       * @since 6.1.0
3612       *
3613       * @return WP_Error|array {
3614       *     Page cache detection details or else error information.
3615       *
3616       *     @type bool    $advanced_cache_present        Whether a page cache plugin is present.
3617       *     @type array[] $page_caching_response_headers Sets of client caching headers for the responses.
3618       *     @type float[] $response_timing               Response timings.
3619       * }
3620       */
3621  	private function check_for_page_caching() {
3622  
3623          /** This filter is documented in wp-includes/class-wp-http-streams.php */
3624          $sslverify = apply_filters( 'https_local_ssl_verify', false );
3625  
3626          $headers = array();
3627  
3628          /*
3629           * Include basic auth in loopback requests. Note that this will only pass along basic auth when user is
3630           * initiating the test. If a site requires basic auth, the test will fail when it runs in WP Cron as part of
3631           * wp_site_health_scheduled_check. This logic is copied from WP_Site_Health::can_perform_loopback().
3632           */
3633          if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
3634              $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
3635          }
3636  
3637          $caching_headers               = $this->get_page_cache_headers();
3638          $page_caching_response_headers = array();
3639          $response_timing               = array();
3640  
3641          for ( $i = 1; $i <= 3; $i++ ) {
3642              $start_time    = microtime( true );
3643              $http_response = wp_remote_get( home_url( '/' ), compact( 'sslverify', 'headers' ) );
3644              $end_time      = microtime( true );
3645  
3646              if ( is_wp_error( $http_response ) ) {
3647                  return $http_response;
3648              }
3649              if ( wp_remote_retrieve_response_code( $http_response ) !== 200 ) {
3650                  return new WP_Error(
3651                      'http_' . wp_remote_retrieve_response_code( $http_response ),
3652                      wp_remote_retrieve_response_message( $http_response )
3653                  );
3654              }
3655  
3656              $response_headers = array();
3657  
3658              foreach ( $caching_headers as $header => $callback ) {
3659                  $header_values = wp_remote_retrieve_header( $http_response, $header );
3660                  if ( empty( $header_values ) ) {
3661                      continue;
3662                  }
3663                  $header_values = (array) $header_values;
3664                  if ( empty( $callback ) || ( is_callable( $callback ) && count( array_filter( $header_values, $callback ) ) > 0 ) ) {
3665                      $response_headers[ $header ] = $header_values;
3666                  }
3667              }
3668  
3669              $page_caching_response_headers[] = $response_headers;
3670              $response_timing[]               = ( $end_time - $start_time ) * 1000;
3671          }
3672  
3673          return array(
3674              'advanced_cache_present'        => (
3675                  file_exists( WP_CONTENT_DIR . '/advanced-cache.php' )
3676                  &&
3677                  ( defined( 'WP_CACHE' ) && WP_CACHE )
3678                  &&
3679                  /** This filter is documented in wp-settings.php */
3680                  apply_filters( 'enable_loading_advanced_cache_dropin', true )
3681              ),
3682              'page_caching_response_headers' => $page_caching_response_headers,
3683              'response_timing'               => $response_timing,
3684          );
3685      }
3686  
3687      /**
3688       * Gets page cache details.
3689       *
3690       * @since 6.1.0
3691       *
3692       * @return WP_Error|array {
3693       *     Page cache detail or else a WP_Error if unable to determine.
3694       *
3695       *     @type string   $status                 Page cache status. Good, Recommended or Critical.
3696       *     @type bool     $advanced_cache_present Whether page cache plugin is available or not.
3697       *     @type string[] $headers                Client caching response headers detected.
3698       *     @type float    $response_time          Response time of site.
3699       * }
3700       */
3701  	private function get_page_cache_detail() {
3702          $page_cache_detail = $this->check_for_page_caching();
3703          if ( is_wp_error( $page_cache_detail ) ) {
3704              return $page_cache_detail;
3705          }
3706  
3707          // Use the median server response time.
3708          $response_timings = $page_cache_detail['response_timing'];
3709          rsort( $response_timings );
3710          $page_speed = $response_timings[ floor( count( $response_timings ) / 2 ) ];
3711  
3712          // Obtain unique set of all client caching response headers.
3713          $headers = array();
3714          foreach ( $page_cache_detail['page_caching_response_headers'] as $page_caching_response_headers ) {
3715              $headers = array_merge( $headers, array_keys( $page_caching_response_headers ) );
3716          }
3717          $headers = array_unique( $headers );
3718  
3719          // Page cache is detected if there are response headers or a page cache plugin is present.
3720          $has_page_caching = ( count( $headers ) > 0 || $page_cache_detail['advanced_cache_present'] );
3721  
3722          if ( $page_speed && $page_speed < $this->get_good_response_time_threshold() ) {
3723              $result = $has_page_caching ? 'good' : 'recommended';
3724          } else {
3725              $result = 'critical';
3726          }
3727  
3728          return array(
3729              'status'                 => $result,
3730              'advanced_cache_present' => $page_cache_detail['advanced_cache_present'],
3731              'headers'                => $headers,
3732              'response_time'          => $page_speed,
3733          );
3734      }
3735  
3736      /**
3737       * Gets the threshold below which a response time is considered good.
3738       *
3739       * @since 6.1.0
3740       *
3741       * @return int Threshold in milliseconds.
3742       */
3743  	private function get_good_response_time_threshold() {
3744          /**
3745           * Filters the threshold below which a response time is considered good.
3746           *
3747           * The default is based on https://web.dev/time-to-first-byte/.
3748           *
3749           * @since 6.1.0
3750           *
3751           * @param int $threshold Threshold in milliseconds. Default 600.
3752           */
3753          return (int) apply_filters( 'site_status_good_response_time_threshold', 600 );
3754      }
3755  
3756      /**
3757       * Determines whether to suggest using a persistent object cache.
3758       *
3759       * @since 6.1.0
3760       *
3761       * @global wpdb $wpdb WordPress database abstraction object.
3762       *
3763       * @return bool Whether to suggest using a persistent object cache.
3764       */
3765  	public function should_suggest_persistent_object_cache() {
3766          global $wpdb;
3767  
3768          /**
3769           * Filters whether to suggest use of a persistent object cache and bypass default threshold checks.
3770           *
3771           * Using this filter allows to override the default logic, effectively short-circuiting the method.
3772           *
3773           * @since 6.1.0
3774           *
3775           * @param bool|null $suggest Boolean to short-circuit, for whether to suggest using a persistent object cache.
3776           *                           Default null.
3777           */
3778          $short_circuit = apply_filters( 'site_status_should_suggest_persistent_object_cache', null );
3779          if ( is_bool( $short_circuit ) ) {
3780              return $short_circuit;
3781          }
3782  
3783          if ( is_multisite() ) {
3784              return true;
3785          }
3786  
3787          /**
3788           * Filters the thresholds used to determine whether to suggest the use of a persistent object cache.
3789           *
3790           * @since 6.1.0
3791           *
3792           * @param int[] $thresholds The list of threshold numbers keyed by threshold name.
3793           */
3794          $thresholds = apply_filters(
3795              'site_status_persistent_object_cache_thresholds',
3796              array(
3797                  'alloptions_count' => 500,
3798                  'alloptions_bytes' => 100000,
3799                  'comments_count'   => 1000,
3800                  'options_count'    => 1000,
3801                  'posts_count'      => 1000,
3802                  'terms_count'      => 1000,
3803                  'users_count'      => 1000,
3804              )
3805          );
3806  
3807          $alloptions = wp_load_alloptions();
3808  
3809          if ( $thresholds['alloptions_count'] < count( $alloptions ) ) {
3810              return true;
3811          }
3812  
3813          if ( $thresholds['alloptions_bytes'] < strlen( serialize( $alloptions ) ) ) {
3814              return true;
3815          }
3816  
3817          $table_names = implode( "','", array( $wpdb->comments, $wpdb->options, $wpdb->posts, $wpdb->terms, $wpdb->users ) );
3818  
3819          // With InnoDB the `TABLE_ROWS` are estimates, which are accurate enough and faster to retrieve than individual `COUNT()` queries.
3820          $results = $wpdb->get_results(
3821              $wpdb->prepare(
3822                  // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- This query cannot use interpolation.
3823                  "SELECT TABLE_NAME AS 'table', TABLE_ROWS AS 'rows', SUM(data_length + index_length) as 'bytes' FROM information_schema.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME IN ('$table_names') GROUP BY TABLE_NAME;",
3824                  DB_NAME
3825              ),
3826              OBJECT_K
3827          );
3828  
3829          $threshold_map = array(
3830              'comments_count' => $wpdb->comments,
3831              'options_count'  => $wpdb->options,
3832              'posts_count'    => $wpdb->posts,
3833              'terms_count'    => $wpdb->terms,
3834              'users_count'    => $wpdb->users,
3835          );
3836  
3837          foreach ( $threshold_map as $threshold => $table ) {
3838              if ( $thresholds[ $threshold ] <= $results[ $table ]->rows ) {
3839                  return true;
3840              }
3841          }
3842  
3843          return false;
3844      }
3845  
3846      /**
3847       * Returns a list of available persistent object cache services.
3848       *
3849       * @since 6.1.0
3850       *
3851       * @return string[] The list of available persistent object cache services.
3852       */
3853  	private function available_object_cache_services() {
3854          $extensions = array_map(
3855              'extension_loaded',
3856              array(
3857                  'APCu'      => 'apcu',
3858                  'Redis'     => 'redis',
3859                  'Relay'     => 'relay',
3860                  'Memcache'  => 'memcache',
3861                  'Memcached' => 'memcached',
3862              )
3863          );
3864  
3865          $services = array_keys( array_filter( $extensions ) );
3866  
3867          /**
3868           * Filters the persistent object cache services available to the user.
3869           *
3870           * This can be useful to hide or add services not included in the defaults.
3871           *
3872           * @since 6.1.0
3873           *
3874           * @param string[] $services The list of available persistent object cache services.
3875           */
3876          return apply_filters( 'site_status_available_object_cache_services', $services );
3877      }
3878  }


Generated : Mon May 4 08:20:14 2026 Cross-referenced by PHPXref