[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
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 class WP_Site_Health { 11 private static $instance = null; 12 13 private $mysql_min_version_check; 14 private $mysql_rec_version_check; 15 16 public $is_mariadb = false; 17 private $mysql_server_version = ''; 18 private $health_check_mysql_required_version = '5.5'; 19 private $health_check_mysql_rec_version = ''; 20 21 public $php_memory_limit; 22 23 public $schedules; 24 public $crons; 25 public $last_missed_cron = null; 26 public $last_late_cron = null; 27 private $timeout_missed_cron = null; 28 private $timeout_late_cron = null; 29 30 /** 31 * WP_Site_Health constructor. 32 * 33 * @since 5.2.0 34 */ 35 public function __construct() { 36 $this->maybe_create_scheduled_event(); 37 38 // Save memory limit before it's affected by wp_raise_memory_limit( 'admin' ). 39 $this->php_memory_limit = ini_get( 'memory_limit' ); 40 41 $this->timeout_late_cron = 0; 42 $this->timeout_missed_cron = - 5 * MINUTE_IN_SECONDS; 43 44 if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) { 45 $this->timeout_late_cron = - 15 * MINUTE_IN_SECONDS; 46 $this->timeout_missed_cron = - 1 * HOUR_IN_SECONDS; 47 } 48 49 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) ); 50 51 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); 52 add_action( 'wp_site_health_scheduled_check', array( $this, 'wp_cron_scheduled_check' ) ); 53 54 add_action( 'site_health_tab_content', array( $this, 'show_site_health_tab' ) ); 55 } 56 57 /** 58 * Output the content of a tab in the Site Health screen. 59 * 60 * @since 5.8.0 61 * 62 * @param string $tab Slug of the current tab being displayed. 63 */ 64 public function show_site_health_tab( $tab ) { 65 if ( 'debug' === $tab ) { 66 require_once ABSPATH . '/wp-admin/site-health-info.php'; 67 } 68 } 69 70 /** 71 * Return an instance of the WP_Site_Health class, or create one if none exist yet. 72 * 73 * @since 5.4.0 74 * 75 * @return WP_Site_Health|null 76 */ 77 public static function get_instance() { 78 if ( null === self::$instance ) { 79 self::$instance = new WP_Site_Health(); 80 } 81 82 return self::$instance; 83 } 84 85 /** 86 * Enqueues the site health scripts. 87 * 88 * @since 5.2.0 89 */ 90 public function enqueue_scripts() { 91 $screen = get_current_screen(); 92 if ( 'site-health' !== $screen->id && 'dashboard' !== $screen->id ) { 93 return; 94 } 95 96 $health_check_js_variables = array( 97 'screen' => $screen->id, 98 'nonce' => array( 99 'site_status' => wp_create_nonce( 'health-check-site-status' ), 100 'site_status_result' => wp_create_nonce( 'health-check-site-status-result' ), 101 ), 102 'site_status' => array( 103 'direct' => array(), 104 'async' => array(), 105 'issues' => array( 106 'good' => 0, 107 'recommended' => 0, 108 'critical' => 0, 109 ), 110 ), 111 ); 112 113 $issue_counts = get_transient( 'health-check-site-status-result' ); 114 115 if ( false !== $issue_counts ) { 116 $issue_counts = json_decode( $issue_counts ); 117 118 $health_check_js_variables['site_status']['issues'] = $issue_counts; 119 } 120 121 if ( 'site-health' === $screen->id && ( ! isset( $_GET['tab'] ) || empty( $_GET['tab'] ) ) ) { 122 $tests = WP_Site_Health::get_tests(); 123 124 // Don't run https test on development environments. 125 if ( $this->is_development_environment() ) { 126 unset( $tests['async']['https_status'] ); 127 } 128 129 foreach ( $tests['direct'] as $test ) { 130 if ( is_string( $test['test'] ) ) { 131 $test_function = sprintf( 132 'get_test_%s', 133 $test['test'] 134 ); 135 136 if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) { 137 $health_check_js_variables['site_status']['direct'][] = $this->perform_test( array( $this, $test_function ) ); 138 continue; 139 } 140 } 141 142 if ( is_callable( $test['test'] ) ) { 143 $health_check_js_variables['site_status']['direct'][] = $this->perform_test( $test['test'] ); 144 } 145 } 146 147 foreach ( $tests['async'] as $test ) { 148 if ( is_string( $test['test'] ) ) { 149 $health_check_js_variables['site_status']['async'][] = array( 150 'test' => $test['test'], 151 'has_rest' => ( isset( $test['has_rest'] ) ? $test['has_rest'] : false ), 152 'completed' => false, 153 'headers' => isset( $test['headers'] ) ? $test['headers'] : array(), 154 ); 155 } 156 } 157 } 158 159 wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables ); 160 } 161 162 /** 163 * Run a Site Health test directly. 164 * 165 * @since 5.4.0 166 * 167 * @param callable $callback 168 * @return mixed|void 169 */ 170 private function perform_test( $callback ) { 171 /** 172 * Filters the output of a finished Site Health test. 173 * 174 * @since 5.3.0 175 * 176 * @param array $test_result { 177 * An associative array of test result data. 178 * 179 * @type string $label A label describing the test, and is used as a header in the output. 180 * @type string $status The status of the test, which can be a value of `good`, `recommended` or `critical`. 181 * @type array $badge { 182 * Tests are put into categories which have an associated badge shown, these can be modified and assigned here. 183 * 184 * @type string $label The test label, for example `Performance`. 185 * @type string $color Default `blue`. A string representing a color to use for the label. 186 * } 187 * @type string $description A more descriptive explanation of what the test looks for, and why it is important for the end user. 188 * @type string $actions An action to direct the user to where they can resolve the issue, if one exists. 189 * @type string $test The name of the test being ran, used as a reference point. 190 * } 191 */ 192 return apply_filters( 'site_status_test_result', call_user_func( $callback ) ); 193 } 194 195 /** 196 * Run the SQL version checks. 197 * 198 * These values are used in later tests, but the part of preparing them is more easily managed 199 * early in the class for ease of access and discovery. 200 * 201 * @since 5.2.0 202 * 203 * @global wpdb $wpdb WordPress database abstraction object. 204 */ 205 private function prepare_sql_data() { 206 global $wpdb; 207 208 if ( $wpdb->use_mysqli ) { 209 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info 210 $mysql_server_type = mysqli_get_server_info( $wpdb->dbh ); 211 } else { 212 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved 213 $mysql_server_type = mysql_get_server_info( $wpdb->dbh ); 214 } 215 216 $this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' ); 217 218 $this->health_check_mysql_rec_version = '5.6'; 219 220 if ( stristr( $mysql_server_type, 'mariadb' ) ) { 221 $this->is_mariadb = true; 222 $this->health_check_mysql_rec_version = '10.0'; 223 } 224 225 $this->mysql_min_version_check = version_compare( '5.5', $this->mysql_server_version, '<=' ); 226 $this->mysql_rec_version_check = version_compare( $this->health_check_mysql_rec_version, $this->mysql_server_version, '<=' ); 227 } 228 229 /** 230 * Test if `wp_version_check` is blocked. 231 * 232 * It's possible to block updates with the `wp_version_check` filter, but this can't be checked 233 * during an Ajax call, as the filter is never introduced then. 234 * 235 * This filter overrides a standard page request if it's made by an admin through the Ajax call 236 * with the right query argument to check for this. 237 * 238 * @since 5.2.0 239 */ 240 public function check_wp_version_check_exists() { 241 if ( ! is_admin() || ! is_user_logged_in() || ! current_user_can( 'update_core' ) || ! isset( $_GET['health-check-test-wp_version_check'] ) ) { 242 return; 243 } 244 245 echo ( has_filter( 'wp_version_check', 'wp_version_check' ) ? 'yes' : 'no' ); 246 247 die(); 248 } 249 250 /** 251 * Tests for WordPress version and outputs it. 252 * 253 * Gives various results depending on what kind of updates are available, if any, to encourage 254 * the user to install security updates as a priority. 255 * 256 * @since 5.2.0 257 * 258 * @return array The test result. 259 */ 260 public function get_test_wordpress_version() { 261 $result = array( 262 'label' => '', 263 'status' => '', 264 'badge' => array( 265 'label' => __( 'Performance' ), 266 'color' => 'blue', 267 ), 268 'description' => '', 269 'actions' => '', 270 'test' => 'wordpress_version', 271 ); 272 273 $core_current_version = get_bloginfo( 'version' ); 274 $core_updates = get_core_updates(); 275 276 if ( ! is_array( $core_updates ) ) { 277 $result['status'] = 'recommended'; 278 279 $result['label'] = sprintf( 280 /* translators: %s: Your current version of WordPress. */ 281 __( 'WordPress version %s' ), 282 $core_current_version 283 ); 284 285 $result['description'] = sprintf( 286 '<p>%s</p>', 287 __( 'Unable to check if any new versions of WordPress are available.' ) 288 ); 289 290 $result['actions'] = sprintf( 291 '<a href="%s">%s</a>', 292 esc_url( admin_url( 'update-core.php?force-check=1' ) ), 293 __( 'Check for updates manually' ) 294 ); 295 } else { 296 foreach ( $core_updates as $core => $update ) { 297 if ( 'upgrade' === $update->response ) { 298 $current_version = explode( '.', $core_current_version ); 299 $new_version = explode( '.', $update->version ); 300 301 $current_major = $current_version[0] . '.' . $current_version[1]; 302 $new_major = $new_version[0] . '.' . $new_version[1]; 303 304 $result['label'] = sprintf( 305 /* translators: %s: The latest version of WordPress available. */ 306 __( 'WordPress update available (%s)' ), 307 $update->version 308 ); 309 310 $result['actions'] = sprintf( 311 '<a href="%s">%s</a>', 312 esc_url( admin_url( 'update-core.php' ) ), 313 __( 'Install the latest version of WordPress' ) 314 ); 315 316 if ( $current_major !== $new_major ) { 317 // This is a major version mismatch. 318 $result['status'] = 'recommended'; 319 $result['description'] = sprintf( 320 '<p>%s</p>', 321 __( 'A new version of WordPress is available.' ) 322 ); 323 } else { 324 // This is a minor version, sometimes considered more critical. 325 $result['status'] = 'critical'; 326 $result['badge']['label'] = __( 'Security' ); 327 $result['description'] = sprintf( 328 '<p>%s</p>', 329 __( 'A new minor update is available for your site. Because minor updates often address security, it’s important to install them.' ) 330 ); 331 } 332 } else { 333 $result['status'] = 'good'; 334 $result['label'] = sprintf( 335 /* translators: %s: The current version of WordPress installed on this site. */ 336 __( 'Your version of WordPress (%s) is up to date' ), 337 $core_current_version 338 ); 339 340 $result['description'] = sprintf( 341 '<p>%s</p>', 342 __( 'You are currently running the latest version of WordPress available, keep it up!' ) 343 ); 344 } 345 } 346 } 347 348 return $result; 349 } 350 351 /** 352 * Test if plugins are outdated, or unnecessary. 353 * 354 * The tests checks if your plugins are up to date, and encourages you to remove any 355 * that are not in use. 356 * 357 * @since 5.2.0 358 * 359 * @return array The test result. 360 */ 361 public function get_test_plugin_version() { 362 $result = array( 363 'label' => __( 'Your plugins are all up to date' ), 364 'status' => 'good', 365 'badge' => array( 366 'label' => __( 'Security' ), 367 'color' => 'blue', 368 ), 369 'description' => sprintf( 370 '<p>%s</p>', 371 __( 'Plugins extend your site’s functionality with things like contact forms, ecommerce and much more. That means they have deep access to your site, so it’s vital to keep them up to date.' ) 372 ), 373 'actions' => sprintf( 374 '<p><a href="%s">%s</a></p>', 375 esc_url( admin_url( 'plugins.php' ) ), 376 __( 'Manage your plugins' ) 377 ), 378 'test' => 'plugin_version', 379 ); 380 381 $plugins = get_plugins(); 382 $plugin_updates = get_plugin_updates(); 383 384 $plugins_active = 0; 385 $plugins_total = 0; 386 $plugins_need_update = 0; 387 388 // Loop over the available plugins and check their versions and active state. 389 foreach ( $plugins as $plugin_path => $plugin ) { 390 $plugins_total++; 391 392 if ( is_plugin_active( $plugin_path ) ) { 393 $plugins_active++; 394 } 395 396 if ( array_key_exists( $plugin_path, $plugin_updates ) ) { 397 $plugins_need_update++; 398 } 399 } 400 401 // Add a notice if there are outdated plugins. 402 if ( $plugins_need_update > 0 ) { 403 $result['status'] = 'critical'; 404 405 $result['label'] = __( 'You have plugins waiting to be updated' ); 406 407 $result['description'] .= sprintf( 408 '<p>%s</p>', 409 sprintf( 410 /* translators: %d: The number of outdated plugins. */ 411 _n( 412 'Your site has %d plugin waiting to be updated.', 413 'Your site has %d plugins waiting to be updated.', 414 $plugins_need_update 415 ), 416 $plugins_need_update 417 ) 418 ); 419 420 $result['actions'] .= sprintf( 421 '<p><a href="%s">%s</a></p>', 422 esc_url( network_admin_url( 'plugins.php?plugin_status=upgrade' ) ), 423 __( 'Update your plugins' ) 424 ); 425 } else { 426 if ( 1 === $plugins_active ) { 427 $result['description'] .= sprintf( 428 '<p>%s</p>', 429 __( 'Your site has 1 active plugin, and it is up to date.' ) 430 ); 431 } else { 432 $result['description'] .= sprintf( 433 '<p>%s</p>', 434 sprintf( 435 /* translators: %d: The number of active plugins. */ 436 _n( 437 'Your site has %d active plugin, and it is up to date.', 438 'Your site has %d active plugins, and they are all up to date.', 439 $plugins_active 440 ), 441 $plugins_active 442 ) 443 ); 444 } 445 } 446 447 // Check if there are inactive plugins. 448 if ( $plugins_total > $plugins_active && ! is_multisite() ) { 449 $unused_plugins = $plugins_total - $plugins_active; 450 451 $result['status'] = 'recommended'; 452 453 $result['label'] = __( 'You should remove inactive plugins' ); 454 455 $result['description'] .= sprintf( 456 '<p>%s %s</p>', 457 sprintf( 458 /* translators: %d: The number of inactive plugins. */ 459 _n( 460 'Your site has %d inactive plugin.', 461 'Your site has %d inactive plugins.', 462 $unused_plugins 463 ), 464 $unused_plugins 465 ), 466 __( 'Inactive plugins are tempting targets for attackers. If you are not going to use a plugin, you should consider removing it.' ) 467 ); 468 469 $result['actions'] .= sprintf( 470 '<p><a href="%s">%s</a></p>', 471 esc_url( admin_url( 'plugins.php?plugin_status=inactive' ) ), 472 __( 'Manage inactive plugins' ) 473 ); 474 } 475 476 return $result; 477 } 478 479 /** 480 * Test if themes are outdated, or unnecessary. 481 * 482 * Сhecks if your site has a default theme (to fall back on if there is a need), 483 * if your themes are up to date and, finally, encourages you to remove any themes 484 * that are not needed. 485 * 486 * @since 5.2.0 487 * 488 * @return array The test results. 489 */ 490 public function get_test_theme_version() { 491 $result = array( 492 'label' => __( 'Your themes are all up to date' ), 493 'status' => 'good', 494 'badge' => array( 495 'label' => __( 'Security' ), 496 'color' => 'blue', 497 ), 498 'description' => sprintf( 499 '<p>%s</p>', 500 __( 'Themes add your site’s look and feel. It’s important to keep them up to date, to stay consistent with your brand and keep your site secure.' ) 501 ), 502 'actions' => sprintf( 503 '<p><a href="%s">%s</a></p>', 504 esc_url( admin_url( 'themes.php' ) ), 505 __( 'Manage your themes' ) 506 ), 507 'test' => 'theme_version', 508 ); 509 510 $theme_updates = get_theme_updates(); 511 512 $themes_total = 0; 513 $themes_need_updates = 0; 514 $themes_inactive = 0; 515 516 // This value is changed during processing to determine how many themes are considered a reasonable amount. 517 $allowed_theme_count = 1; 518 519 $has_default_theme = false; 520 $has_unused_themes = false; 521 $show_unused_themes = true; 522 $using_default_theme = false; 523 524 // Populate a list of all themes available in the install. 525 $all_themes = wp_get_themes(); 526 $active_theme = wp_get_theme(); 527 528 // If WP_DEFAULT_THEME doesn't exist, fall back to the latest core default theme. 529 $default_theme = wp_get_theme( WP_DEFAULT_THEME ); 530 if ( ! $default_theme->exists() ) { 531 $default_theme = WP_Theme::get_core_default_theme(); 532 } 533 534 if ( $default_theme ) { 535 $has_default_theme = true; 536 537 if ( 538 $active_theme->get_stylesheet() === $default_theme->get_stylesheet() 539 || 540 is_child_theme() && $active_theme->get_template() === $default_theme->get_template() 541 ) { 542 $using_default_theme = true; 543 } 544 } 545 546 foreach ( $all_themes as $theme_slug => $theme ) { 547 $themes_total++; 548 549 if ( array_key_exists( $theme_slug, $theme_updates ) ) { 550 $themes_need_updates++; 551 } 552 } 553 554 // If this is a child theme, increase the allowed theme count by one, to account for the parent. 555 if ( is_child_theme() ) { 556 $allowed_theme_count++; 557 } 558 559 // If there's a default theme installed and not in use, we count that as allowed as well. 560 if ( $has_default_theme && ! $using_default_theme ) { 561 $allowed_theme_count++; 562 } 563 564 if ( $themes_total > $allowed_theme_count ) { 565 $has_unused_themes = true; 566 $themes_inactive = ( $themes_total - $allowed_theme_count ); 567 } 568 569 // Check if any themes need to be updated. 570 if ( $themes_need_updates > 0 ) { 571 $result['status'] = 'critical'; 572 573 $result['label'] = __( 'You have themes waiting to be updated' ); 574 575 $result['description'] .= sprintf( 576 '<p>%s</p>', 577 sprintf( 578 /* translators: %d: The number of outdated themes. */ 579 _n( 580 'Your site has %d theme waiting to be updated.', 581 'Your site has %d themes waiting to be updated.', 582 $themes_need_updates 583 ), 584 $themes_need_updates 585 ) 586 ); 587 } else { 588 // Give positive feedback about the site being good about keeping things up to date. 589 if ( 1 === $themes_total ) { 590 $result['description'] .= sprintf( 591 '<p>%s</p>', 592 __( 'Your site has 1 installed theme, and it is up to date.' ) 593 ); 594 } else { 595 $result['description'] .= sprintf( 596 '<p>%s</p>', 597 sprintf( 598 /* translators: %d: The number of themes. */ 599 _n( 600 'Your site has %d installed theme, and it is up to date.', 601 'Your site has %d installed themes, and they are all up to date.', 602 $themes_total 603 ), 604 $themes_total 605 ) 606 ); 607 } 608 } 609 610 if ( $has_unused_themes && $show_unused_themes && ! is_multisite() ) { 611 612 // This is a child theme, so we want to be a bit more explicit in our messages. 613 if ( $active_theme->parent() ) { 614 // Recommend removing inactive themes, except a default theme, your current one, and the parent theme. 615 $result['status'] = 'recommended'; 616 617 $result['label'] = __( 'You should remove inactive themes' ); 618 619 if ( $using_default_theme ) { 620 $result['description'] .= sprintf( 621 '<p>%s %s</p>', 622 sprintf( 623 /* translators: %d: The number of inactive themes. */ 624 _n( 625 'Your site has %d inactive theme.', 626 'Your site has %d inactive themes.', 627 $themes_inactive 628 ), 629 $themes_inactive 630 ), 631 sprintf( 632 /* translators: 1: The currently active theme. 2: The active theme's parent theme. */ 633 __( 'To enhance your site’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.' ), 634 $active_theme->name, 635 $active_theme->parent()->name 636 ) 637 ); 638 } else { 639 $result['description'] .= sprintf( 640 '<p>%s %s</p>', 641 sprintf( 642 /* translators: %d: The number of inactive themes. */ 643 _n( 644 'Your site has %d inactive theme.', 645 'Your site has %d inactive themes.', 646 $themes_inactive 647 ), 648 $themes_inactive 649 ), 650 sprintf( 651 /* translators: 1: The default theme for WordPress. 2: The currently active theme. 3: The active theme's parent theme. */ 652 __( 'To enhance your site’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.' ), 653 $default_theme ? $default_theme->name : WP_DEFAULT_THEME, 654 $active_theme->name, 655 $active_theme->parent()->name 656 ) 657 ); 658 } 659 } else { 660 // Recommend removing all inactive themes. 661 $result['status'] = 'recommended'; 662 663 $result['label'] = __( 'You should remove inactive themes' ); 664 665 if ( $using_default_theme ) { 666 $result['description'] .= sprintf( 667 '<p>%s %s</p>', 668 sprintf( 669 /* translators: 1: The amount of inactive themes. 2: The currently active theme. */ 670 _n( 671 'Your site has %1$d inactive theme, other than %2$s, your active theme.', 672 'Your site has %1$d inactive themes, other than %2$s, your active theme.', 673 $themes_inactive 674 ), 675 $themes_inactive, 676 $active_theme->name 677 ), 678 __( 'You should consider removing any unused themes to enhance your site’s security.' ) 679 ); 680 } else { 681 $result['description'] .= sprintf( 682 '<p>%s %s</p>', 683 sprintf( 684 /* translators: 1: The amount of inactive themes. 2: The default theme for WordPress. 3: The currently active theme. */ 685 _n( 686 'Your site has %1$d inactive theme, other than %2$s, the default WordPress theme, and %3$s, your active theme.', 687 'Your site has %1$d inactive themes, other than %2$s, the default WordPress theme, and %3$s, your active theme.', 688 $themes_inactive 689 ), 690 $themes_inactive, 691 $default_theme ? $default_theme->name : WP_DEFAULT_THEME, 692 $active_theme->name 693 ), 694 __( 'You should consider removing any unused themes to enhance your site’s security.' ) 695 ); 696 } 697 } 698 } 699 700 // If no default Twenty* theme exists. 701 if ( ! $has_default_theme ) { 702 $result['status'] = 'recommended'; 703 704 $result['label'] = __( 'Have a default theme available' ); 705 706 $result['description'] .= sprintf( 707 '<p>%s</p>', 708 __( 'Your site does not have any default theme. Default themes are used by WordPress automatically if anything is wrong with your chosen theme.' ) 709 ); 710 } 711 712 return $result; 713 } 714 715 /** 716 * Test if the supplied PHP version is supported. 717 * 718 * @since 5.2.0 719 * 720 * @return array The test results. 721 */ 722 public function get_test_php_version() { 723 $response = wp_check_php_version(); 724 725 $result = array( 726 'label' => sprintf( 727 /* translators: %s: The current PHP version. */ 728 __( 'Your site is running the current version of PHP (%s)' ), 729 PHP_VERSION 730 ), 731 'status' => 'good', 732 'badge' => array( 733 'label' => __( 'Performance' ), 734 'color' => 'blue', 735 ), 736 'description' => sprintf( 737 '<p>%s</p>', 738 sprintf( 739 /* translators: %s: The minimum recommended PHP version. */ 740 __( 'PHP is the programming language used to build and maintain WordPress. Newer versions of PHP are created with increased performance in mind, so you may see a positive effect on your site’s performance. The minimum recommended version of PHP is %s.' ), 741 $response ? $response['recommended_version'] : '' 742 ) 743 ), 744 'actions' => sprintf( 745 '<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', 746 esc_url( wp_get_update_php_url() ), 747 __( 'Learn more about updating PHP' ), 748 /* translators: Accessibility text. */ 749 __( '(opens in a new tab)' ) 750 ), 751 'test' => 'php_version', 752 ); 753 754 // PHP is up to date. 755 if ( ! $response || version_compare( PHP_VERSION, $response['recommended_version'], '>=' ) ) { 756 return $result; 757 } 758 759 // The PHP version is older than the recommended version, but still receiving active support. 760 if ( $response['is_supported'] ) { 761 $result['label'] = sprintf( 762 /* translators: %s: The server PHP version. */ 763 __( 'Your site is running an older version of PHP (%s)' ), 764 PHP_VERSION 765 ); 766 $result['status'] = 'recommended'; 767 768 return $result; 769 } 770 771 // The PHP version is only receiving security fixes. 772 if ( $response['is_secure'] ) { 773 $result['label'] = sprintf( 774 /* translators: %s: The server PHP version. */ 775 __( 'Your site is running an older version of PHP (%s), which should be updated' ), 776 PHP_VERSION 777 ); 778 $result['status'] = 'recommended'; 779 780 return $result; 781 } 782 783 // Anything no longer secure must be updated. 784 $result['label'] = sprintf( 785 /* translators: %s: The server PHP version. */ 786 __( 'Your site is running an outdated version of PHP (%s), which requires an update' ), 787 PHP_VERSION 788 ); 789 $result['status'] = 'critical'; 790 $result['badge']['label'] = __( 'Security' ); 791 792 return $result; 793 } 794 795 /** 796 * Check if the passed extension or function are available. 797 * 798 * Make the check for available PHP modules into a simple boolean operator for a cleaner test runner. 799 * 800 * @since 5.2.0 801 * @since 5.3.0 The `$constant_name` and `$class_name` parameters were added. 802 * 803 * @param string $extension_name Optional. The extension name to test. Default null. 804 * @param string $function_name Optional. The function name to test. Default null. 805 * @param string $constant_name Optional. The constant name to test for. Default null. 806 * @param string $class_name Optional. The class name to test for. Default null. 807 * @return bool Whether or not the extension and function are available. 808 */ 809 private function test_php_extension_availability( $extension_name = null, $function_name = null, $constant_name = null, $class_name = null ) { 810 // If no extension or function is passed, claim to fail testing, as we have nothing to test against. 811 if ( ! $extension_name && ! $function_name && ! $constant_name && ! $class_name ) { 812 return false; 813 } 814 815 if ( $extension_name && ! extension_loaded( $extension_name ) ) { 816 return false; 817 } 818 819 if ( $function_name && ! function_exists( $function_name ) ) { 820 return false; 821 } 822 823 if ( $constant_name && ! defined( $constant_name ) ) { 824 return false; 825 } 826 827 if ( $class_name && ! class_exists( $class_name ) ) { 828 return false; 829 } 830 831 return true; 832 } 833 834 /** 835 * Test if required PHP modules are installed on the host. 836 * 837 * This test builds on the recommendations made by the WordPress Hosting Team 838 * as seen at https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions 839 * 840 * @since 5.2.0 841 * 842 * @return array 843 */ 844 public function get_test_php_extensions() { 845 $result = array( 846 'label' => __( 'Required and recommended modules are installed' ), 847 'status' => 'good', 848 'badge' => array( 849 'label' => __( 'Performance' ), 850 'color' => 'blue', 851 ), 852 'description' => sprintf( 853 '<p>%s</p><p>%s</p>', 854 __( '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.' ), 855 sprintf( 856 /* translators: 1: Link to the hosting group page about recommended PHP modules. 2: Additional link attributes. 3: Accessibility text. */ 857 __( '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>.' ), 858 /* translators: Localized team handbook, if one exists. */ 859 esc_url( __( 'https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions' ) ), 860 'target="_blank" rel="noopener"', 861 sprintf( 862 ' <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span>', 863 /* translators: Accessibility text. */ 864 __( '(opens in a new tab)' ) 865 ) 866 ) 867 ), 868 'actions' => '', 869 'test' => 'php_extensions', 870 ); 871 872 $modules = array( 873 'curl' => array( 874 'function' => 'curl_version', 875 'required' => false, 876 ), 877 'dom' => array( 878 'class' => 'DOMNode', 879 'required' => false, 880 ), 881 'exif' => array( 882 'function' => 'exif_read_data', 883 'required' => false, 884 ), 885 'fileinfo' => array( 886 'function' => 'finfo_file', 887 'required' => false, 888 ), 889 'hash' => array( 890 'function' => 'hash', 891 'required' => false, 892 ), 893 'imagick' => array( 894 'extension' => 'imagick', 895 'required' => false, 896 ), 897 'json' => array( 898 'function' => 'json_last_error', 899 'required' => true, 900 ), 901 'mbstring' => array( 902 'function' => 'mb_check_encoding', 903 'required' => false, 904 ), 905 'mysqli' => array( 906 'function' => 'mysqli_connect', 907 'required' => false, 908 ), 909 'libsodium' => array( 910 'constant' => 'SODIUM_LIBRARY_VERSION', 911 'required' => false, 912 'php_bundled_version' => '7.2.0', 913 ), 914 'openssl' => array( 915 'function' => 'openssl_encrypt', 916 'required' => false, 917 ), 918 'pcre' => array( 919 'function' => 'preg_match', 920 'required' => false, 921 ), 922 'mod_xml' => array( 923 'extension' => 'libxml', 924 'required' => false, 925 ), 926 'zip' => array( 927 'class' => 'ZipArchive', 928 'required' => false, 929 ), 930 'filter' => array( 931 'function' => 'filter_list', 932 'required' => false, 933 ), 934 'gd' => array( 935 'extension' => 'gd', 936 'required' => false, 937 'fallback_for' => 'imagick', 938 ), 939 'iconv' => array( 940 'function' => 'iconv', 941 'required' => false, 942 ), 943 'intl' => array( 944 'extension' => 'intl', 945 'required' => false, 946 ), 947 'mcrypt' => array( 948 'extension' => 'mcrypt', 949 'required' => false, 950 'fallback_for' => 'libsodium', 951 ), 952 'simplexml' => array( 953 'extension' => 'simplexml', 954 'required' => false, 955 'fallback_for' => 'mod_xml', 956 ), 957 'xmlreader' => array( 958 'extension' => 'xmlreader', 959 'required' => false, 960 'fallback_for' => 'mod_xml', 961 ), 962 'zlib' => array( 963 'extension' => 'zlib', 964 'required' => false, 965 'fallback_for' => 'zip', 966 ), 967 ); 968 969 /** 970 * An array representing all the modules we wish to test for. 971 * 972 * @since 5.2.0 973 * @since 5.3.0 The `$constant` and `$class` parameters were added. 974 * 975 * @param array $modules { 976 * An associative array of modules to test for. 977 * 978 * @type array ...$0 { 979 * An associative array of module properties used during testing. 980 * One of either `$function` or `$extension` must be provided, or they will fail by default. 981 * 982 * @type string $function Optional. A function name to test for the existence of. 983 * @type string $extension Optional. An extension to check if is loaded in PHP. 984 * @type string $constant Optional. A constant name to check for to verify an extension exists. 985 * @type string $class Optional. A class name to check for to verify an extension exists. 986 * @type bool $required Is this a required feature or not. 987 * @type string $fallback_for Optional. The module this module replaces as a fallback. 988 * } 989 * } 990 */ 991 $modules = apply_filters( 'site_status_test_php_modules', $modules ); 992 993 $failures = array(); 994 995 foreach ( $modules as $library => $module ) { 996 $extension_name = ( isset( $module['extension'] ) ? $module['extension'] : null ); 997 $function_name = ( isset( $module['function'] ) ? $module['function'] : null ); 998 $constant_name = ( isset( $module['constant'] ) ? $module['constant'] : null ); 999 $class_name = ( isset( $module['class'] ) ? $module['class'] : null ); 1000 1001 // If this module is a fallback for another function, check if that other function passed. 1002 if ( isset( $module['fallback_for'] ) ) { 1003 /* 1004 * If that other function has a failure, mark this module as required for usual operations. 1005 * If that other function hasn't failed, skip this test as it's only a fallback. 1006 */ 1007 if ( isset( $failures[ $module['fallback_for'] ] ) ) { 1008 $module['required'] = true; 1009 } else { 1010 continue; 1011 } 1012 } 1013 1014 if ( ! $this->test_php_extension_availability( $extension_name, $function_name, $constant_name, $class_name ) 1015 && ( ! isset( $module['php_bundled_version'] ) 1016 || version_compare( PHP_VERSION, $module['php_bundled_version'], '<' ) ) 1017 ) { 1018 if ( $module['required'] ) { 1019 $result['status'] = 'critical'; 1020 1021 $class = 'error'; 1022 $screen_reader = __( 'Error' ); 1023 $message = sprintf( 1024 /* translators: %s: The module name. */ 1025 __( 'The required module, %s, is not installed, or has been disabled.' ), 1026 $library 1027 ); 1028 } else { 1029 $class = 'warning'; 1030 $screen_reader = __( 'Warning' ); 1031 $message = sprintf( 1032 /* translators: %s: The module name. */ 1033 __( 'The optional module, %s, is not installed, or has been disabled.' ), 1034 $library 1035 ); 1036 } 1037 1038 if ( ! $module['required'] && 'good' === $result['status'] ) { 1039 $result['status'] = 'recommended'; 1040 } 1041 1042 $failures[ $library ] = "<span class='dashicons $class'><span class='screen-reader-text'>$screen_reader</span></span> $message"; 1043 } 1044 } 1045 1046 if ( ! empty( $failures ) ) { 1047 $output = '<ul>'; 1048 1049 foreach ( $failures as $failure ) { 1050 $output .= sprintf( 1051 '<li>%s</li>', 1052 $failure 1053 ); 1054 } 1055 1056 $output .= '</ul>'; 1057 } 1058 1059 if ( 'good' !== $result['status'] ) { 1060 if ( 'recommended' === $result['status'] ) { 1061 $result['label'] = __( 'One or more recommended modules are missing' ); 1062 } 1063 if ( 'critical' === $result['status'] ) { 1064 $result['label'] = __( 'One or more required modules are missing' ); 1065 } 1066 1067 $result['description'] .= $output; 1068 } 1069 1070 return $result; 1071 } 1072 1073 /** 1074 * Test if the PHP default timezone is set to UTC. 1075 * 1076 * @since 5.3.1 1077 * 1078 * @return array The test results. 1079 */ 1080 public function get_test_php_default_timezone() { 1081 $result = array( 1082 'label' => __( 'PHP default timezone is valid' ), 1083 'status' => 'good', 1084 'badge' => array( 1085 'label' => __( 'Performance' ), 1086 'color' => 'blue', 1087 ), 1088 'description' => sprintf( 1089 '<p>%s</p>', 1090 __( 'PHP default timezone was configured by WordPress on loading. This is necessary for correct calculations of dates and times.' ) 1091 ), 1092 'actions' => '', 1093 'test' => 'php_default_timezone', 1094 ); 1095 1096 if ( 'UTC' !== date_default_timezone_get() ) { 1097 $result['status'] = 'critical'; 1098 1099 $result['label'] = __( 'PHP default timezone is invalid' ); 1100 1101 $result['description'] = sprintf( 1102 '<p>%s</p>', 1103 sprintf( 1104 /* translators: %s: date_default_timezone_set() */ 1105 __( 'PHP default timezone was changed after WordPress loading by a %s function call. This interferes with correct calculations of dates and times.' ), 1106 '<code>date_default_timezone_set()</code>' 1107 ) 1108 ); 1109 } 1110 1111 return $result; 1112 } 1113 1114 /** 1115 * Test if there's an active PHP session that can affect loopback requests. 1116 * 1117 * @since 5.5.0 1118 * 1119 * @return array The test results. 1120 */ 1121 public function get_test_php_sessions() { 1122 $result = array( 1123 'label' => __( 'No PHP sessions detected' ), 1124 'status' => 'good', 1125 'badge' => array( 1126 'label' => __( 'Performance' ), 1127 'color' => 'blue', 1128 ), 1129 'description' => sprintf( 1130 '<p>%s</p>', 1131 sprintf( 1132 /* translators: 1: session_start(), 2: session_write_close() */ 1133 __( '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.' ), 1134 '<code>session_start()</code>', 1135 '<code>session_write_close()</code>' 1136 ) 1137 ), 1138 'test' => 'php_sessions', 1139 ); 1140 1141 if ( function_exists( 'session_status' ) && PHP_SESSION_ACTIVE === session_status() ) { 1142 $result['status'] = 'critical'; 1143 1144 $result['label'] = __( 'An active PHP session was detected' ); 1145 1146 $result['description'] = sprintf( 1147 '<p>%s</p>', 1148 sprintf( 1149 /* translators: 1: session_start(), 2: session_write_close() */ 1150 __( '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.' ), 1151 '<code>session_start()</code>', 1152 '<code>session_write_close()</code>' 1153 ) 1154 ); 1155 } 1156 1157 return $result; 1158 } 1159 1160 /** 1161 * Test if the SQL server is up to date. 1162 * 1163 * @since 5.2.0 1164 * 1165 * @return array The test results. 1166 */ 1167 public function get_test_sql_server() { 1168 if ( ! $this->mysql_server_version ) { 1169 $this->prepare_sql_data(); 1170 } 1171 1172 $result = array( 1173 'label' => __( 'SQL server is up to date' ), 1174 'status' => 'good', 1175 'badge' => array( 1176 'label' => __( 'Performance' ), 1177 'color' => 'blue', 1178 ), 1179 'description' => sprintf( 1180 '<p>%s</p>', 1181 __( 'The SQL server is a required piece of software for the database WordPress uses to store all your site’s content and settings.' ) 1182 ), 1183 'actions' => sprintf( 1184 '<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', 1185 /* translators: Localized version of WordPress requirements if one exists. */ 1186 esc_url( __( 'https://wordpress.org/about/requirements/' ) ), 1187 __( 'Learn more about what WordPress requires to run.' ), 1188 /* translators: Accessibility text. */ 1189 __( '(opens in a new tab)' ) 1190 ), 1191 'test' => 'sql_server', 1192 ); 1193 1194 $db_dropin = file_exists( WP_CONTENT_DIR . '/db.php' ); 1195 1196 if ( ! $this->mysql_rec_version_check ) { 1197 $result['status'] = 'recommended'; 1198 1199 $result['label'] = __( 'Outdated SQL server' ); 1200 1201 $result['description'] .= sprintf( 1202 '<p>%s</p>', 1203 sprintf( 1204 /* translators: 1: The database engine in use (MySQL or MariaDB). 2: Database server recommended version number. */ 1205 __( '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.' ), 1206 ( $this->is_mariadb ? 'MariaDB' : 'MySQL' ), 1207 $this->health_check_mysql_rec_version 1208 ) 1209 ); 1210 } 1211 1212 if ( ! $this->mysql_min_version_check ) { 1213 $result['status'] = 'critical'; 1214 1215 $result['label'] = __( 'Severely outdated SQL server' ); 1216 $result['badge']['label'] = __( 'Security' ); 1217 1218 $result['description'] .= sprintf( 1219 '<p>%s</p>', 1220 sprintf( 1221 /* translators: 1: The database engine in use (MySQL or MariaDB). 2: Database server minimum version number. */ 1222 __( 'WordPress requires %1$s version %2$s or higher. Contact your web hosting company to correct this.' ), 1223 ( $this->is_mariadb ? 'MariaDB' : 'MySQL' ), 1224 $this->health_check_mysql_required_version 1225 ) 1226 ); 1227 } 1228 1229 if ( $db_dropin ) { 1230 $result['description'] .= sprintf( 1231 '<p>%s</p>', 1232 wp_kses( 1233 sprintf( 1234 /* translators: 1: The name of the drop-in. 2: The name of the database engine. */ 1235 __( 'You are using a %1$s drop-in which might mean that a %2$s database is not being used.' ), 1236 '<code>wp-content/db.php</code>', 1237 ( $this->is_mariadb ? 'MariaDB' : 'MySQL' ) 1238 ), 1239 array( 1240 'code' => true, 1241 ) 1242 ) 1243 ); 1244 } 1245 1246 return $result; 1247 } 1248 1249 /** 1250 * Test if the database server is capable of using utf8mb4. 1251 * 1252 * @since 5.2.0 1253 * 1254 * @return array The test results. 1255 */ 1256 public function get_test_utf8mb4_support() { 1257 global $wpdb; 1258 1259 if ( ! $this->mysql_server_version ) { 1260 $this->prepare_sql_data(); 1261 } 1262 1263 $result = array( 1264 'label' => __( 'UTF8MB4 is supported' ), 1265 'status' => 'good', 1266 'badge' => array( 1267 'label' => __( 'Performance' ), 1268 'color' => 'blue', 1269 ), 1270 'description' => sprintf( 1271 '<p>%s</p>', 1272 __( 'UTF8MB4 is the character set WordPress prefers for database storage because it safely supports the widest set of characters and encodings, including Emoji, enabling better support for non-English languages.' ) 1273 ), 1274 'actions' => '', 1275 'test' => 'utf8mb4_support', 1276 ); 1277 1278 if ( ! $this->is_mariadb ) { 1279 if ( version_compare( $this->mysql_server_version, '5.5.3', '<' ) ) { 1280 $result['status'] = 'recommended'; 1281 1282 $result['label'] = __( 'utf8mb4 requires a MySQL update' ); 1283 1284 $result['description'] .= sprintf( 1285 '<p>%s</p>', 1286 sprintf( 1287 /* translators: %s: Version number. */ 1288 __( 'WordPress’ utf8mb4 support requires MySQL version %s or greater. Please contact your server administrator.' ), 1289 '5.5.3' 1290 ) 1291 ); 1292 } else { 1293 $result['description'] .= sprintf( 1294 '<p>%s</p>', 1295 __( 'Your MySQL version supports utf8mb4.' ) 1296 ); 1297 } 1298 } else { // MariaDB introduced utf8mb4 support in 5.5.0. 1299 if ( version_compare( $this->mysql_server_version, '5.5.0', '<' ) ) { 1300 $result['status'] = 'recommended'; 1301 1302 $result['label'] = __( 'utf8mb4 requires a MariaDB update' ); 1303 1304 $result['description'] .= sprintf( 1305 '<p>%s</p>', 1306 sprintf( 1307 /* translators: %s: Version number. */ 1308 __( 'WordPress’ utf8mb4 support requires MariaDB version %s or greater. Please contact your server administrator.' ), 1309 '5.5.0' 1310 ) 1311 ); 1312 } else { 1313 $result['description'] .= sprintf( 1314 '<p>%s</p>', 1315 __( 'Your MariaDB version supports utf8mb4.' ) 1316 ); 1317 } 1318 } 1319 1320 if ( $wpdb->use_mysqli ) { 1321 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_client_info 1322 $mysql_client_version = mysqli_get_client_info(); 1323 } else { 1324 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved 1325 $mysql_client_version = mysql_get_client_info(); 1326 } 1327 1328 /* 1329 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server. 1330 * mysqlnd has supported utf8mb4 since 5.0.9. 1331 */ 1332 if ( false !== strpos( $mysql_client_version, 'mysqlnd' ) ) { 1333 $mysql_client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $mysql_client_version ); 1334 if ( version_compare( $mysql_client_version, '5.0.9', '<' ) ) { 1335 $result['status'] = 'recommended'; 1336 1337 $result['label'] = __( 'utf8mb4 requires a newer client library' ); 1338 1339 $result['description'] .= sprintf( 1340 '<p>%s</p>', 1341 sprintf( 1342 /* translators: 1: Name of the library, 2: Number of version. */ 1343 __( 'WordPress’ utf8mb4 support requires MySQL client library (%1$s) version %2$s or newer. Please contact your server administrator.' ), 1344 'mysqlnd', 1345 '5.0.9' 1346 ) 1347 ); 1348 } 1349 } else { 1350 if ( version_compare( $mysql_client_version, '5.5.3', '<' ) ) { 1351 $result['status'] = 'recommended'; 1352 1353 $result['label'] = __( 'utf8mb4 requires a newer client library' ); 1354 1355 $result['description'] .= sprintf( 1356 '<p>%s</p>', 1357 sprintf( 1358 /* translators: 1: Name of the library, 2: Number of version. */ 1359 __( 'WordPress’ utf8mb4 support requires MySQL client library (%1$s) version %2$s or newer. Please contact your server administrator.' ), 1360 'libmysql', 1361 '5.5.3' 1362 ) 1363 ); 1364 } 1365 } 1366 1367 return $result; 1368 } 1369 1370 /** 1371 * Test if the site can communicate with WordPress.org. 1372 * 1373 * @since 5.2.0 1374 * 1375 * @return array The test results. 1376 */ 1377 public function get_test_dotorg_communication() { 1378 $result = array( 1379 'label' => __( 'Can communicate with WordPress.org' ), 1380 'status' => '', 1381 'badge' => array( 1382 'label' => __( 'Security' ), 1383 'color' => 'blue', 1384 ), 1385 'description' => sprintf( 1386 '<p>%s</p>', 1387 __( 'Communicating with the WordPress servers is used to check for new versions, and to both install and update WordPress core, themes or plugins.' ) 1388 ), 1389 'actions' => '', 1390 'test' => 'dotorg_communication', 1391 ); 1392 1393 $wp_dotorg = wp_remote_get( 1394 'https://api.wordpress.org', 1395 array( 1396 'timeout' => 10, 1397 ) 1398 ); 1399 if ( ! is_wp_error( $wp_dotorg ) ) { 1400 $result['status'] = 'good'; 1401 } else { 1402 $result['status'] = 'critical'; 1403 1404 $result['label'] = __( 'Could not reach WordPress.org' ); 1405 1406 $result['description'] .= sprintf( 1407 '<p>%s</p>', 1408 sprintf( 1409 '<span class="error"><span class="screen-reader-text">%s</span></span> %s', 1410 __( 'Error' ), 1411 sprintf( 1412 /* translators: 1: The IP address WordPress.org resolves to. 2: The error returned by the lookup. */ 1413 __( 'Your site is unable to reach WordPress.org at %1$s, and returned the error: %2$s' ), 1414 gethostbyname( 'api.wordpress.org' ), 1415 $wp_dotorg->get_error_message() 1416 ) 1417 ) 1418 ); 1419 1420 $result['actions'] = sprintf( 1421 '<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', 1422 /* translators: Localized Support reference. */ 1423 esc_url( __( 'https://wordpress.org/support' ) ), 1424 __( 'Get help resolving this issue.' ), 1425 /* translators: Accessibility text. */ 1426 __( '(opens in a new tab)' ) 1427 ); 1428 } 1429 1430 return $result; 1431 } 1432 1433 /** 1434 * Test if debug information is enabled. 1435 * 1436 * When WP_DEBUG is enabled, errors and information may be disclosed to site visitors, 1437 * or logged to a publicly accessible file. 1438 * 1439 * Debugging is also frequently left enabled after looking for errors on a site, 1440 * as site owners do not understand the implications of this. 1441 * 1442 * @since 5.2.0 1443 * 1444 * @return array The test results. 1445 */ 1446 public function get_test_is_in_debug_mode() { 1447 $result = array( 1448 'label' => __( 'Your site is not set to output debug information' ), 1449 'status' => 'good', 1450 'badge' => array( 1451 'label' => __( 'Security' ), 1452 'color' => 'blue', 1453 ), 1454 'description' => sprintf( 1455 '<p>%s</p>', 1456 __( '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.' ) 1457 ), 1458 'actions' => sprintf( 1459 '<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', 1460 /* translators: Documentation explaining debugging in WordPress. */ 1461 esc_url( __( 'https://wordpress.org/support/article/debugging-in-wordpress/' ) ), 1462 __( 'Learn more about debugging in WordPress.' ), 1463 /* translators: Accessibility text. */ 1464 __( '(opens in a new tab)' ) 1465 ), 1466 'test' => 'is_in_debug_mode', 1467 ); 1468 1469 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 1470 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { 1471 $result['label'] = __( 'Your site is set to log errors to a potentially public file' ); 1472 1473 $result['status'] = ( 0 === strpos( ini_get( 'error_log' ), ABSPATH ) ) ? 'critical' : 'recommended'; 1474 1475 $result['description'] .= sprintf( 1476 '<p>%s</p>', 1477 sprintf( 1478 /* translators: %s: WP_DEBUG_LOG */ 1479 __( 'The value, %s, has been added to this website’s configuration file. This means any errors on the site will be written to a file which is potentially available to all users.' ), 1480 '<code>WP_DEBUG_LOG</code>' 1481 ) 1482 ); 1483 } 1484 1485 if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) { 1486 $result['label'] = __( 'Your site is set to display errors to site visitors' ); 1487 1488 $result['status'] = 'critical'; 1489 1490 // On development environments, set the status to recommended. 1491 if ( $this->is_development_environment() ) { 1492 $result['status'] = 'recommended'; 1493 } 1494 1495 $result['description'] .= sprintf( 1496 '<p>%s</p>', 1497 sprintf( 1498 /* translators: 1: WP_DEBUG_DISPLAY, 2: WP_DEBUG */ 1499 __( '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.' ), 1500 '<code>WP_DEBUG_DISPLAY</code>', 1501 '<code>WP_DEBUG</code>' 1502 ) 1503 ); 1504 } 1505 } 1506 1507 return $result; 1508 } 1509 1510 /** 1511 * Test if your site is serving content over HTTPS. 1512 * 1513 * Many sites have varying degrees of HTTPS support, the most common of which is sites that have it 1514 * enabled, but only if you visit the right site address. 1515 * 1516 * @since 5.2.0 1517 * @since 5.7.0 Updated to rely on {@see wp_is_using_https()} and {@see wp_is_https_supported()}. 1518 * 1519 * @return array The test results. 1520 */ 1521 public function get_test_https_status() { 1522 // Enforce fresh HTTPS detection results. This is normally invoked by using cron, 1523 // but for Site Health it should always rely on the latest results. 1524 wp_update_https_detection_errors(); 1525 1526 $default_update_url = wp_get_default_update_https_url(); 1527 1528 $result = array( 1529 'label' => __( 'Your website is using an active HTTPS connection' ), 1530 'status' => 'good', 1531 'badge' => array( 1532 'label' => __( 'Security' ), 1533 'color' => 'blue', 1534 ), 1535 'description' => sprintf( 1536 '<p>%s</p>', 1537 __( '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.' ) 1538 ), 1539 'actions' => sprintf( 1540 '<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', 1541 esc_url( $default_update_url ), 1542 __( 'Learn more about why you should use HTTPS' ), 1543 /* translators: Accessibility text. */ 1544 __( '(opens in a new tab)' ) 1545 ), 1546 'test' => 'https_status', 1547 ); 1548 1549 if ( ! wp_is_using_https() ) { 1550 // If the website is not using HTTPS, provide more information 1551 // about whether it is supported and how it can be enabled. 1552 $result['status'] = 'recommended'; 1553 $result['label'] = __( 'Your website does not use HTTPS' ); 1554 1555 if ( wp_is_site_url_using_https() ) { 1556 if ( is_ssl() ) { 1557 $result['description'] = sprintf( 1558 '<p>%s</p>', 1559 sprintf( 1560 /* translators: %s: URL to Settings > General > Site Address. */ 1561 __( 'You are accessing this website using HTTPS, but your <a href="%s">Site Address</a> is not set up to use HTTPS by default.' ), 1562 esc_url( admin_url( 'options-general.php' ) . '#home' ) 1563 ) 1564 ); 1565 } else { 1566 $result['description'] = sprintf( 1567 '<p>%s</p>', 1568 sprintf( 1569 /* translators: %s: URL to Settings > General > Site Address. */ 1570 __( 'Your <a href="%s">Site Address</a> is not set up to use HTTPS.' ), 1571 esc_url( admin_url( 'options-general.php' ) . '#home' ) 1572 ) 1573 ); 1574 } 1575 } else { 1576 if ( is_ssl() ) { 1577 $result['description'] = sprintf( 1578 '<p>%s</p>', 1579 sprintf( 1580 /* translators: 1: URL to Settings > General > WordPress Address, 2: URL to Settings > General > Site Address. */ 1581 __( '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.' ), 1582 esc_url( admin_url( 'options-general.php' ) . '#siteurl' ), 1583 esc_url( admin_url( 'options-general.php' ) . '#home' ) 1584 ) 1585 ); 1586 } else { 1587 $result['description'] = sprintf( 1588 '<p>%s</p>', 1589 sprintf( 1590 /* translators: 1: URL to Settings > General > WordPress Address, 2: URL to Settings > General > Site Address. */ 1591 __( 'Your <a href="%1$s">WordPress Address</a> and <a href="%2$s">Site Address</a> are not set up to use HTTPS.' ), 1592 esc_url( admin_url( 'options-general.php' ) . '#siteurl' ), 1593 esc_url( admin_url( 'options-general.php' ) . '#home' ) 1594 ) 1595 ); 1596 } 1597 } 1598 1599 if ( wp_is_https_supported() ) { 1600 $result['description'] .= sprintf( 1601 '<p>%s</p>', 1602 __( 'HTTPS is already supported for your website.' ) 1603 ); 1604 1605 if ( defined( 'WP_HOME' ) || defined( 'WP_SITEURL' ) ) { 1606 $result['description'] .= sprintf( 1607 '<p>%s</p>', 1608 sprintf( 1609 /* translators: 1: wp-config.php, 2: WP_HOME, 3: WP_SITEURL */ 1610 __( '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.' ), 1611 '<code>wp-config.php</code>', 1612 '<code>WP_HOME</code>', 1613 '<code>WP_SITEURL</code>' 1614 ) 1615 ); 1616 } elseif ( current_user_can( 'update_https' ) ) { 1617 $default_direct_update_url = add_query_arg( 'action', 'update_https', wp_nonce_url( admin_url( 'site-health.php' ), 'wp_update_https' ) ); 1618 $direct_update_url = wp_get_direct_update_https_url(); 1619 1620 if ( ! empty( $direct_update_url ) ) { 1621 $result['actions'] = sprintf( 1622 '<p class="button-container"><a class="button button-primary" href="%1$s" target="_blank" rel="noopener">%2$s<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', 1623 esc_url( $direct_update_url ), 1624 __( 'Update your site to use HTTPS' ), 1625 /* translators: Accessibility text. */ 1626 __( '(opens in a new tab)' ) 1627 ); 1628 } else { 1629 $result['actions'] = sprintf( 1630 '<p class="button-container"><a class="button button-primary" href="%1$s">%2$s</a></p>', 1631 esc_url( $default_direct_update_url ), 1632 __( 'Update your site to use HTTPS' ) 1633 ); 1634 } 1635 } 1636 } else { 1637 // If host-specific "Update HTTPS" URL is provided, include a link. 1638 $update_url = wp_get_update_https_url(); 1639 if ( $update_url !== $default_update_url ) { 1640 $result['description'] .= sprintf( 1641 '<p><a href="%s" target="_blank" rel="noopener">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', 1642 esc_url( $update_url ), 1643 __( 'Talk to your web host about supporting HTTPS for your website.' ), 1644 /* translators: Accessibility text. */ 1645 __( '(opens in a new tab)' ) 1646 ); 1647 } else { 1648 $result['description'] .= sprintf( 1649 '<p>%s</p>', 1650 __( 'Talk to your web host about supporting HTTPS for your website.' ) 1651 ); 1652 } 1653 } 1654 } 1655 1656 return $result; 1657 } 1658 1659 /** 1660 * Check if the HTTP API can handle SSL/TLS requests. 1661 * 1662 * @since 5.2.0 1663 * 1664 * @return array The test results. 1665 */ 1666 public function get_test_ssl_support() { 1667 $result = array( 1668 'label' => '', 1669 'status' => '', 1670 'badge' => array( 1671 'label' => __( 'Security' ), 1672 'color' => 'blue', 1673 ), 1674 'description' => sprintf( 1675 '<p>%s</p>', 1676 __( 'Securely communicating between servers are needed for transactions such as fetching files, conducting sales on store sites, and much more.' ) 1677 ), 1678 'actions' => '', 1679 'test' => 'ssl_support', 1680 ); 1681 1682 $supports_https = wp_http_supports( array( 'ssl' ) ); 1683 1684 if ( $supports_https ) { 1685 $result['status'] = 'good'; 1686 1687 $result['label'] = __( 'Your site can communicate securely with other services' ); 1688 } else { 1689 $result['status'] = 'critical'; 1690 1691 $result['label'] = __( 'Your site is unable to communicate securely with other services' ); 1692 1693 $result['description'] .= sprintf( 1694 '<p>%s</p>', 1695 __( 'Talk to your web host about OpenSSL support for PHP.' ) 1696 ); 1697 } 1698 1699 return $result; 1700 } 1701 1702 /** 1703 * Test if scheduled events run as intended. 1704 * 1705 * If scheduled events are not running, this may indicate something with WP_Cron is not working 1706 * as intended, or that there are orphaned events hanging around from older code. 1707 * 1708 * @since 5.2.0 1709 * 1710 * @return array The test results. 1711 */ 1712 public function get_test_scheduled_events() { 1713 $result = array( 1714 'label' => __( 'Scheduled events are running' ), 1715 'status' => 'good', 1716 'badge' => array( 1717 'label' => __( 'Performance' ), 1718 'color' => 'blue', 1719 ), 1720 'description' => sprintf( 1721 '<p>%s</p>', 1722 __( '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.' ) 1723 ), 1724 'actions' => '', 1725 'test' => 'scheduled_events', 1726 ); 1727 1728 $this->wp_schedule_test_init(); 1729 1730 if ( is_wp_error( $this->has_missed_cron() ) ) { 1731 $result['status'] = 'critical'; 1732 1733 $result['label'] = __( 'It was not possible to check your scheduled events' ); 1734 1735 $result['description'] = sprintf( 1736 '<p>%s</p>', 1737 sprintf( 1738 /* translators: %s: The error message returned while from the cron scheduler. */ 1739 __( 'While trying to test your site’s scheduled events, the following error was returned: %s' ), 1740 $this->has_missed_cron()->get_error_message() 1741 ) 1742 ); 1743 } elseif ( $this->has_missed_cron() ) { 1744 $result['status'] = 'recommended'; 1745 1746 $result['label'] = __( 'A scheduled event has failed' ); 1747 1748 $result['description'] = sprintf( 1749 '<p>%s</p>', 1750 sprintf( 1751 /* translators: %s: The name of the failed cron event. */ 1752 __( '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.' ), 1753 $this->last_missed_cron 1754 ) 1755 ); 1756 } elseif ( $this->has_late_cron() ) { 1757 $result['status'] = 'recommended'; 1758 1759 $result['label'] = __( 'A scheduled event is late' ); 1760 1761 $result['description'] = sprintf( 1762 '<p>%s</p>', 1763 sprintf( 1764 /* translators: %s: The name of the late cron event. */ 1765 __( '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.' ), 1766 $this->last_late_cron 1767 ) 1768 ); 1769 } 1770 1771 return $result; 1772 } 1773 1774 /** 1775 * Test if WordPress can run automated background updates. 1776 * 1777 * Background updates in WordPress are primarily used for minor releases and security updates. 1778 * It's important to either have these working, or be aware that they are intentionally disabled 1779 * for whatever reason. 1780 * 1781 * @since 5.2.0 1782 * 1783 * @return array The test results. 1784 */ 1785 public function get_test_background_updates() { 1786 $result = array( 1787 'label' => __( 'Background updates are working' ), 1788 'status' => 'good', 1789 'badge' => array( 1790 'label' => __( 'Security' ), 1791 'color' => 'blue', 1792 ), 1793 'description' => sprintf( 1794 '<p>%s</p>', 1795 __( 'Background updates ensure that WordPress can auto-update if a security update is released for the version you are currently using.' ) 1796 ), 1797 'actions' => '', 1798 'test' => 'background_updates', 1799 ); 1800 1801 if ( ! class_exists( 'WP_Site_Health_Auto_Updates' ) ) { 1802 require_once ABSPATH . 'wp-admin/includes/class-wp-site-health-auto-updates.php'; 1803 } 1804 1805 // Run the auto-update tests in a separate class, 1806 // as there are many considerations to be made. 1807 $automatic_updates = new WP_Site_Health_Auto_Updates(); 1808 $tests = $automatic_updates->run_tests(); 1809 1810 $output = '<ul>'; 1811 1812 foreach ( $tests as $test ) { 1813 $severity_string = __( 'Passed' ); 1814 1815 if ( 'fail' === $test->severity ) { 1816 $result['label'] = __( 'Background updates are not working as expected' ); 1817 1818 $result['status'] = 'critical'; 1819 1820 $severity_string = __( 'Error' ); 1821 } 1822 1823 if ( 'warning' === $test->severity && 'good' === $result['status'] ) { 1824 $result['label'] = __( 'Background updates may not be working properly' ); 1825 1826 $result['status'] = 'recommended'; 1827 1828 $severity_string = __( 'Warning' ); 1829 } 1830 1831 $output .= sprintf( 1832 '<li><span class="dashicons %s"><span class="screen-reader-text">%s</span></span> %s</li>', 1833 esc_attr( $test->severity ), 1834 $severity_string, 1835 $test->description 1836 ); 1837 } 1838 1839 $output .= '</ul>'; 1840 1841 if ( 'good' !== $result['status'] ) { 1842 $result['description'] .= $output; 1843 } 1844 1845 return $result; 1846 } 1847 1848 /** 1849 * Test if plugin and theme auto-updates appear to be configured correctly. 1850 * 1851 * @since 5.5.0 1852 * 1853 * @return array The test results. 1854 */ 1855 public function get_test_plugin_theme_auto_updates() { 1856 $result = array( 1857 'label' => __( 'Plugin and theme auto-updates appear to be configured correctly' ), 1858 'status' => 'good', 1859 'badge' => array( 1860 'label' => __( 'Security' ), 1861 'color' => 'blue', 1862 ), 1863 'description' => sprintf( 1864 '<p>%s</p>', 1865 __( 'Plugin and theme auto-updates ensure that the latest versions are always installed.' ) 1866 ), 1867 'actions' => '', 1868 'test' => 'plugin_theme_auto_updates', 1869 ); 1870 1871 $check_plugin_theme_updates = $this->detect_plugin_theme_auto_update_issues(); 1872 1873 $result['status'] = $check_plugin_theme_updates->status; 1874 1875 if ( 'good' !== $result['status'] ) { 1876 $result['label'] = __( 'Your site may have problems auto-updating plugins and themes' ); 1877 1878 $result['description'] .= sprintf( 1879 '<p>%s</p>', 1880 $check_plugin_theme_updates->message 1881 ); 1882 } 1883 1884 return $result; 1885 } 1886 1887 /** 1888 * Test if loopbacks work as expected. 1889 * 1890 * A loopback is when WordPress queries itself, for example to start a new WP_Cron instance, 1891 * or when editing a plugin or theme. This has shown itself to be a recurring issue, 1892 * as code can very easily break this interaction. 1893 * 1894 * @since 5.2.0 1895 * 1896 * @return array The test results. 1897 */ 1898 public function get_test_loopback_requests() { 1899 $result = array( 1900 'label' => __( 'Your site can perform loopback requests' ), 1901 'status' => 'good', 1902 'badge' => array( 1903 'label' => __( 'Performance' ), 1904 'color' => 'blue', 1905 ), 1906 'description' => sprintf( 1907 '<p>%s</p>', 1908 __( '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.' ) 1909 ), 1910 'actions' => '', 1911 'test' => 'loopback_requests', 1912 ); 1913 1914 $check_loopback = $this->can_perform_loopback(); 1915 1916 $result['status'] = $check_loopback->status; 1917 1918 if ( 'good' !== $result['status'] ) { 1919 $result['label'] = __( 'Your site could not complete a loopback request' ); 1920 1921 $result['description'] .= sprintf( 1922 '<p>%s</p>', 1923 $check_loopback->message 1924 ); 1925 } 1926 1927 return $result; 1928 } 1929 1930 /** 1931 * Test if HTTP requests are blocked. 1932 * 1933 * It's possible to block all outgoing communication (with the possibility of allowing certain 1934 * hosts) via the HTTP API. This may create problems for users as many features are running as 1935 * services these days. 1936 * 1937 * @since 5.2.0 1938 * 1939 * @return array The test results. 1940 */ 1941 public function get_test_http_requests() { 1942 $result = array( 1943 'label' => __( 'HTTP requests seem to be working as expected' ), 1944 'status' => 'good', 1945 'badge' => array( 1946 'label' => __( 'Performance' ), 1947 'color' => 'blue', 1948 ), 1949 'description' => sprintf( 1950 '<p>%s</p>', 1951 __( '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.' ) 1952 ), 1953 'actions' => '', 1954 'test' => 'http_requests', 1955 ); 1956 1957 $blocked = false; 1958 $hosts = array(); 1959 1960 if ( defined( 'WP_HTTP_BLOCK_EXTERNAL' ) && WP_HTTP_BLOCK_EXTERNAL ) { 1961 $blocked = true; 1962 } 1963 1964 if ( defined( 'WP_ACCESSIBLE_HOSTS' ) ) { 1965 $hosts = explode( ',', WP_ACCESSIBLE_HOSTS ); 1966 } 1967 1968 if ( $blocked && 0 === count( $hosts ) ) { 1969 $result['status'] = 'critical'; 1970 1971 $result['label'] = __( 'HTTP requests are blocked' ); 1972 1973 $result['description'] .= sprintf( 1974 '<p>%s</p>', 1975 sprintf( 1976 /* translators: %s: Name of the constant used. */ 1977 __( 'HTTP requests have been blocked by the %s constant, with no allowed hosts.' ), 1978 '<code>WP_HTTP_BLOCK_EXTERNAL</code>' 1979 ) 1980 ); 1981 } 1982 1983 if ( $blocked && 0 < count( $hosts ) ) { 1984 $result['status'] = 'recommended'; 1985 1986 $result['label'] = __( 'HTTP requests are partially blocked' ); 1987 1988 $result['description'] .= sprintf( 1989 '<p>%s</p>', 1990 sprintf( 1991 /* translators: 1: Name of the constant used. 2: List of allowed hostnames. */ 1992 __( 'HTTP requests have been blocked by the %1$s constant, with some allowed hosts: %2$s.' ), 1993 '<code>WP_HTTP_BLOCK_EXTERNAL</code>', 1994 implode( ',', $hosts ) 1995 ) 1996 ); 1997 } 1998 1999 return $result; 2000 } 2001 2002 /** 2003 * Test if the REST API is accessible. 2004 * 2005 * Various security measures may block the REST API from working, or it may have been disabled in general. 2006 * This is required for the new block editor to work, so we explicitly test for this. 2007 * 2008 * @since 5.2.0 2009 * 2010 * @return array The test results. 2011 */ 2012 public function get_test_rest_availability() { 2013 $result = array( 2014 'label' => __( 'The REST API is available' ), 2015 'status' => 'good', 2016 'badge' => array( 2017 'label' => __( 'Performance' ), 2018 'color' => 'blue', 2019 ), 2020 'description' => sprintf( 2021 '<p>%s</p>', 2022 __( 'The REST API is one way WordPress, and other applications, communicate with the server. One example is the block editor screen, which relies on this to display, and save, your posts and pages.' ) 2023 ), 2024 'actions' => '', 2025 'test' => 'rest_availability', 2026 ); 2027 2028 $cookies = wp_unslash( $_COOKIE ); 2029 $timeout = 10; 2030 $headers = array( 2031 'Cache-Control' => 'no-cache', 2032 'X-WP-Nonce' => wp_create_nonce( 'wp_rest' ), 2033 ); 2034 /** This filter is documented in wp-includes/class-wp-http-streams.php */ 2035 $sslverify = apply_filters( 'https_local_ssl_verify', false ); 2036 2037 // Include Basic auth in loopback requests. 2038 if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { 2039 $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); 2040 } 2041 2042 $url = rest_url( 'wp/v2/types/post' ); 2043 2044 // The context for this is editing with the new block editor. 2045 $url = add_query_arg( 2046 array( 2047 'context' => 'edit', 2048 ), 2049 $url 2050 ); 2051 2052 $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) ); 2053 2054 if ( is_wp_error( $r ) ) { 2055 $result['status'] = 'critical'; 2056 2057 $result['label'] = __( 'The REST API encountered an error' ); 2058 2059 $result['description'] .= sprintf( 2060 '<p>%s</p>', 2061 sprintf( 2062 '%s<br>%s', 2063 __( 'The REST API request failed due to an error.' ), 2064 sprintf( 2065 /* translators: 1: The WordPress error message. 2: The WordPress error code. */ 2066 __( 'Error: %1$s (%2$s)' ), 2067 $r->get_error_message(), 2068 $r->get_error_code() 2069 ) 2070 ) 2071 ); 2072 } elseif ( 200 !== wp_remote_retrieve_response_code( $r ) ) { 2073 $result['status'] = 'recommended'; 2074 2075 $result['label'] = __( 'The REST API encountered an unexpected result' ); 2076 2077 $result['description'] .= sprintf( 2078 '<p>%s</p>', 2079 sprintf( 2080 /* translators: 1: The HTTP error code. 2: The HTTP error message. */ 2081 __( 'The REST API call gave the following unexpected result: (%1$d) %2$s.' ), 2082 wp_remote_retrieve_response_code( $r ), 2083 esc_html( wp_remote_retrieve_body( $r ) ) 2084 ) 2085 ); 2086 } else { 2087 $json = json_decode( wp_remote_retrieve_body( $r ), true ); 2088 2089 if ( false !== $json && ! isset( $json['capabilities'] ) ) { 2090 $result['status'] = 'recommended'; 2091 2092 $result['label'] = __( 'The REST API did not behave correctly' ); 2093 2094 $result['description'] .= sprintf( 2095 '<p>%s</p>', 2096 sprintf( 2097 /* translators: %s: The name of the query parameter being tested. */ 2098 __( 'The REST API did not process the %s query parameter correctly.' ), 2099 '<code>context</code>' 2100 ) 2101 ); 2102 } 2103 } 2104 2105 return $result; 2106 } 2107 2108 /** 2109 * Test if 'file_uploads' directive in PHP.ini is turned off. 2110 * 2111 * @since 5.5.0 2112 * 2113 * @return array The test results. 2114 */ 2115 public function get_test_file_uploads() { 2116 $result = array( 2117 'label' => __( 'Files can be uploaded' ), 2118 'status' => 'good', 2119 'badge' => array( 2120 'label' => __( 'Performance' ), 2121 'color' => 'blue', 2122 ), 2123 'description' => sprintf( 2124 '<p>%s</p>', 2125 sprintf( 2126 /* translators: 1: file_uploads, 2: php.ini */ 2127 __( 'The %1$s directive in %2$s determines if uploading files is allowed on your site.' ), 2128 '<code>file_uploads</code>', 2129 '<code>php.ini</code>' 2130 ) 2131 ), 2132 'actions' => '', 2133 'test' => 'file_uploads', 2134 ); 2135 2136 if ( ! function_exists( 'ini_get' ) ) { 2137 $result['status'] = 'critical'; 2138 $result['description'] .= sprintf( 2139 /* translators: %s: ini_get() */ 2140 __( 'The %s function has been disabled, some media settings are unavailable because of this.' ), 2141 '<code>ini_get()</code>' 2142 ); 2143 return $result; 2144 } 2145 2146 if ( empty( ini_get( 'file_uploads' ) ) ) { 2147 $result['status'] = 'critical'; 2148 $result['description'] .= sprintf( 2149 '<p>%s</p>', 2150 sprintf( 2151 /* translators: 1: file_uploads, 2: 0 */ 2152 __( '%1$s is set to %2$s. You won\'t be able to upload files on your site.' ), 2153 '<code>file_uploads</code>', 2154 '<code>0</code>' 2155 ) 2156 ); 2157 return $result; 2158 } 2159 2160 $post_max_size = ini_get( 'post_max_size' ); 2161 $upload_max_filesize = ini_get( 'upload_max_filesize' ); 2162 2163 if ( wp_convert_hr_to_bytes( $post_max_size ) < wp_convert_hr_to_bytes( $upload_max_filesize ) ) { 2164 $result['label'] = sprintf( 2165 /* translators: 1: post_max_size, 2: upload_max_filesize */ 2166 __( 'The "%1$s" value is smaller than "%2$s"' ), 2167 'post_max_size', 2168 'upload_max_filesize' 2169 ); 2170 $result['status'] = 'recommended'; 2171 2172 if ( 0 === wp_convert_hr_to_bytes( $post_max_size ) ) { 2173 $result['description'] = sprintf( 2174 '<p>%s</p>', 2175 sprintf( 2176 /* translators: 1: post_max_size, 2: upload_max_filesize */ 2177 __( '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.' ), 2178 '<code>post_max_size</code>', 2179 '<code>upload_max_filesize</code>' 2180 ) 2181 ); 2182 } else { 2183 $result['description'] = sprintf( 2184 '<p>%s</p>', 2185 sprintf( 2186 /* translators: 1: post_max_size, 2: upload_max_filesize */ 2187 __( 'The setting for %1$s is smaller than %2$s, this could cause some problems when trying to upload files.' ), 2188 '<code>post_max_size</code>', 2189 '<code>upload_max_filesize</code>' 2190 ) 2191 ); 2192 } 2193 2194 return $result; 2195 } 2196 2197 return $result; 2198 } 2199 2200 /** 2201 * Tests if the Authorization header has the expected values. 2202 * 2203 * @since 5.6.0 2204 * 2205 * @return array 2206 */ 2207 public function get_test_authorization_header() { 2208 $result = array( 2209 'label' => __( 'The Authorization header is working as expected' ), 2210 'status' => 'good', 2211 'badge' => array( 2212 'label' => __( 'Security' ), 2213 'color' => 'blue', 2214 ), 2215 'description' => sprintf( 2216 '<p>%s</p>', 2217 __( 'The Authorization header comes from the third-party applications you approve. Without it, those apps cannot connect to your site.' ) 2218 ), 2219 'actions' => '', 2220 'test' => 'authorization_header', 2221 ); 2222 2223 if ( ! isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) { 2224 $result['label'] = __( 'The authorization header is missing' ); 2225 } elseif ( 'user' !== $_SERVER['PHP_AUTH_USER'] || 'pwd' !== $_SERVER['PHP_AUTH_PW'] ) { 2226 $result['label'] = __( 'The authorization header is invalid' ); 2227 } else { 2228 return $result; 2229 } 2230 2231 $result['status'] = 'recommended'; 2232 2233 if ( ! function_exists( 'got_mod_rewrite' ) ) { 2234 require_once ABSPATH . 'wp-admin/includes/misc.php'; 2235 } 2236 2237 if ( got_mod_rewrite() ) { 2238 $result['actions'] .= sprintf( 2239 '<p><a href="%s">%s</a></p>', 2240 esc_url( admin_url( 'options-permalink.php' ) ), 2241 __( 'Flush permalinks' ) 2242 ); 2243 } else { 2244 $result['actions'] .= sprintf( 2245 '<p><a href="%s" target="_blank" rel="noopener">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', 2246 __( 'https://developer.wordpress.org/rest-api/frequently-asked-questions/#why-is-authentication-not-working' ), 2247 __( 'Learn how to configure the Authorization header.' ), 2248 /* translators: Accessibility text. */ 2249 __( '(opens in a new tab)' ) 2250 ); 2251 } 2252 2253 return $result; 2254 } 2255 2256 /** 2257 * Return a set of tests that belong to the site status page. 2258 * 2259 * Each site status test is defined here, they may be `direct` tests, that run on page load, or `async` tests 2260 * which will run later down the line via JavaScript calls to improve page performance and hopefully also user 2261 * experiences. 2262 * 2263 * @since 5.2.0 2264 * @since 5.6.0 Added support for `has_rest` and `permissions`. 2265 * 2266 * @return array The list of tests to run. 2267 */ 2268 public static function get_tests() { 2269 $tests = array( 2270 'direct' => array( 2271 'wordpress_version' => array( 2272 'label' => __( 'WordPress Version' ), 2273 'test' => 'wordpress_version', 2274 ), 2275 'plugin_version' => array( 2276 'label' => __( 'Plugin Versions' ), 2277 'test' => 'plugin_version', 2278 ), 2279 'theme_version' => array( 2280 'label' => __( 'Theme Versions' ), 2281 'test' => 'theme_version', 2282 ), 2283 'php_version' => array( 2284 'label' => __( 'PHP Version' ), 2285 'test' => 'php_version', 2286 ), 2287 'php_extensions' => array( 2288 'label' => __( 'PHP Extensions' ), 2289 'test' => 'php_extensions', 2290 ), 2291 'php_default_timezone' => array( 2292 'label' => __( 'PHP Default Timezone' ), 2293 'test' => 'php_default_timezone', 2294 ), 2295 'php_sessions' => array( 2296 'label' => __( 'PHP Sessions' ), 2297 'test' => 'php_sessions', 2298 ), 2299 'sql_server' => array( 2300 'label' => __( 'Database Server version' ), 2301 'test' => 'sql_server', 2302 ), 2303 'utf8mb4_support' => array( 2304 'label' => __( 'MySQL utf8mb4 support' ), 2305 'test' => 'utf8mb4_support', 2306 ), 2307 'ssl_support' => array( 2308 'label' => __( 'Secure communication' ), 2309 'test' => 'ssl_support', 2310 ), 2311 'scheduled_events' => array( 2312 'label' => __( 'Scheduled events' ), 2313 'test' => 'scheduled_events', 2314 ), 2315 'http_requests' => array( 2316 'label' => __( 'HTTP Requests' ), 2317 'test' => 'http_requests', 2318 ), 2319 'rest_availability' => array( 2320 'label' => __( 'REST API availability' ), 2321 'test' => 'rest_availability', 2322 'skip_cron' => true, 2323 ), 2324 'debug_enabled' => array( 2325 'label' => __( 'Debugging enabled' ), 2326 'test' => 'is_in_debug_mode', 2327 ), 2328 'file_uploads' => array( 2329 'label' => __( 'File uploads' ), 2330 'test' => 'file_uploads', 2331 ), 2332 'plugin_theme_auto_updates' => array( 2333 'label' => __( 'Plugin and theme auto-updates' ), 2334 'test' => 'plugin_theme_auto_updates', 2335 ), 2336 ), 2337 'async' => array( 2338 'dotorg_communication' => array( 2339 'label' => __( 'Communication with WordPress.org' ), 2340 'test' => rest_url( 'wp-site-health/v1/tests/dotorg-communication' ), 2341 'has_rest' => true, 2342 'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_dotorg_communication' ), 2343 ), 2344 'background_updates' => array( 2345 'label' => __( 'Background updates' ), 2346 'test' => rest_url( 'wp-site-health/v1/tests/background-updates' ), 2347 'has_rest' => true, 2348 'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_background_updates' ), 2349 ), 2350 'loopback_requests' => array( 2351 'label' => __( 'Loopback request' ), 2352 'test' => rest_url( 'wp-site-health/v1/tests/loopback-requests' ), 2353 'has_rest' => true, 2354 'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_loopback_requests' ), 2355 ), 2356 'https_status' => array( 2357 'label' => __( 'HTTPS status' ), 2358 'test' => rest_url( 'wp-site-health/v1/tests/https-status' ), 2359 'has_rest' => true, 2360 'async_direct_test' => array( WP_Site_Health::get_instance(), 'get_test_https_status' ), 2361 ), 2362 ), 2363 ); 2364 2365 // Conditionally include Authorization header test if the site isn't protected by Basic Auth. 2366 if ( ! wp_is_site_protected_by_basic_auth() ) { 2367 $tests['async']['authorization_header'] = array( 2368 'label' => __( 'Authorization header' ), 2369 'test' => rest_url( 'wp-site-health/v1/tests/authorization-header' ), 2370 'has_rest' => true, 2371 'headers' => array( 'Authorization' => 'Basic ' . base64_encode( 'user:pwd' ) ), 2372 'skip_cron' => true, 2373 ); 2374 } 2375 2376 /** 2377 * Add or modify which site status tests are run on a site. 2378 * 2379 * The site health is determined by a set of tests based on best practices from 2380 * both the WordPress Hosting Team and web standards in general. 2381 * 2382 * Some sites may not have the same requirements, for example the automatic update 2383 * checks may be handled by a host, and are therefore disabled in core. 2384 * Or maybe you want to introduce a new test, is caching enabled/disabled/stale for example. 2385 * 2386 * Tests may be added either as direct, or asynchronous ones. Any test that may require some time 2387 * to complete should run asynchronously, to avoid extended loading periods within wp-admin. 2388 * 2389 * @since 5.2.0 2390 * @since 5.6.0 Added the `async_direct_test` array key for asynchronous tests. 2391 * Added the `skip_cron` array key for all tests. 2392 * 2393 * @param array[] $tests { 2394 * An associative array of direct and asynchronous tests. 2395 * 2396 * @type array[] $direct { 2397 * An array of direct tests. 2398 * 2399 * @type array ...$identifier { 2400 * `$identifier` should be a unique identifier for the test. Plugins and themes are encouraged to 2401 * prefix test identifiers with their slug to avoid collisions between tests. 2402 * 2403 * @type string $label The friendly label to identify the test. 2404 * @type callable $test The callback function that runs the test and returns its result. 2405 * @type bool $skip_cron Whether to skip this test when running as cron. 2406 * } 2407 * } 2408 * @type array[] $async { 2409 * An array of asynchronous tests. 2410 * 2411 * @type array ...$identifier { 2412 * `$identifier` should be a unique identifier for the test. Plugins and themes are encouraged to 2413 * prefix test identifiers with their slug to avoid collisions between tests. 2414 * 2415 * @type string $label The friendly label to identify the test. 2416 * @type string $test An admin-ajax.php action to be called to perform the test, or 2417 * if `$has_rest` is true, a URL to a REST API endpoint to perform 2418 * the test. 2419 * @type bool $has_rest Whether the `$test` property points to a REST API endpoint. 2420 * @type bool $skip_cron Whether to skip this test when running as cron. 2421 * @type callable $async_direct_test A manner of directly calling the test marked as asynchronous, 2422 * as the scheduled event can not authenticate, and endpoints 2423 * may require authentication. 2424 * } 2425 * } 2426 * } 2427 */ 2428 $tests = apply_filters( 'site_status_tests', $tests ); 2429 2430 // Ensure that the filtered tests contain the required array keys. 2431 $tests = array_merge( 2432 array( 2433 'direct' => array(), 2434 'async' => array(), 2435 ), 2436 $tests 2437 ); 2438 2439 return $tests; 2440 } 2441 2442 /** 2443 * Add a class to the body HTML tag. 2444 * 2445 * Filters the body class string for admin pages and adds our own class for easier styling. 2446 * 2447 * @since 5.2.0 2448 * 2449 * @param string $body_class The body class string. 2450 * @return string The modified body class string. 2451 */ 2452 public function admin_body_class( $body_class ) { 2453 $screen = get_current_screen(); 2454 if ( 'site-health' !== $screen->id ) { 2455 return $body_class; 2456 } 2457 2458 $body_class .= ' site-health'; 2459 2460 return $body_class; 2461 } 2462 2463 /** 2464 * Initiate the WP_Cron schedule test cases. 2465 * 2466 * @since 5.2.0 2467 */ 2468 private function wp_schedule_test_init() { 2469 $this->schedules = wp_get_schedules(); 2470 $this->get_cron_tasks(); 2471 } 2472 2473 /** 2474 * Populate our list of cron events and store them to a class-wide variable. 2475 * 2476 * @since 5.2.0 2477 */ 2478 private function get_cron_tasks() { 2479 $cron_tasks = _get_cron_array(); 2480 2481 if ( empty( $cron_tasks ) ) { 2482 $this->crons = new WP_Error( 'no_tasks', __( 'No scheduled events exist on this site.' ) ); 2483 return; 2484 } 2485 2486 $this->crons = array(); 2487 2488 foreach ( $cron_tasks as $time => $cron ) { 2489 foreach ( $cron as $hook => $dings ) { 2490 foreach ( $dings as $sig => $data ) { 2491 2492 $this->crons[ "$hook-$sig-$time" ] = (object) array( 2493 'hook' => $hook, 2494 'time' => $time, 2495 'sig' => $sig, 2496 'args' => $data['args'], 2497 'schedule' => $data['schedule'], 2498 'interval' => isset( $data['interval'] ) ? $data['interval'] : null, 2499 ); 2500 2501 } 2502 } 2503 } 2504 } 2505 2506 /** 2507 * Check if any scheduled tasks have been missed. 2508 * 2509 * Returns a boolean value of `true` if a scheduled task has been missed and ends processing. 2510 * 2511 * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value. 2512 * 2513 * @since 5.2.0 2514 * 2515 * @return bool|WP_Error True if a cron was missed, false if not. WP_Error if the cron is set to that. 2516 */ 2517 public function has_missed_cron() { 2518 if ( is_wp_error( $this->crons ) ) { 2519 return $this->crons; 2520 } 2521 2522 foreach ( $this->crons as $id => $cron ) { 2523 if ( ( $cron->time - time() ) < $this->timeout_missed_cron ) { 2524 $this->last_missed_cron = $cron->hook; 2525 return true; 2526 } 2527 } 2528 2529 return false; 2530 } 2531 2532 /** 2533 * Check if any scheduled tasks are late. 2534 * 2535 * Returns a boolean value of `true` if a scheduled task is late and ends processing. 2536 * 2537 * If the list of crons is an instance of WP_Error, returns the instance instead of a boolean value. 2538 * 2539 * @since 5.3.0 2540 * 2541 * @return bool|WP_Error True if a cron is late, false if not. WP_Error if the cron is set to that. 2542 */ 2543 public function has_late_cron() { 2544 if ( is_wp_error( $this->crons ) ) { 2545 return $this->crons; 2546 } 2547 2548 foreach ( $this->crons as $id => $cron ) { 2549 $cron_offset = $cron->time - time(); 2550 if ( 2551 $cron_offset >= $this->timeout_missed_cron && 2552 $cron_offset < $this->timeout_late_cron 2553 ) { 2554 $this->last_late_cron = $cron->hook; 2555 return true; 2556 } 2557 } 2558 2559 return false; 2560 } 2561 2562 /** 2563 * Check for potential issues with plugin and theme auto-updates. 2564 * 2565 * Though there is no way to 100% determine if plugin and theme auto-updates are configured 2566 * correctly, a few educated guesses could be made to flag any conditions that would 2567 * potentially cause unexpected behaviors. 2568 * 2569 * @since 5.5.0 2570 * 2571 * @return object The test results. 2572 */ 2573 public function detect_plugin_theme_auto_update_issues() { 2574 $mock_plugin = (object) array( 2575 'id' => 'w.org/plugins/a-fake-plugin', 2576 'slug' => 'a-fake-plugin', 2577 'plugin' => 'a-fake-plugin/a-fake-plugin.php', 2578 'new_version' => '9.9', 2579 'url' => 'https://wordpress.org/plugins/a-fake-plugin/', 2580 'package' => 'https://downloads.wordpress.org/plugin/a-fake-plugin.9.9.zip', 2581 'icons' => array( 2582 '2x' => 'https://ps.w.org/a-fake-plugin/assets/icon-256x256.png', 2583 '1x' => 'https://ps.w.org/a-fake-plugin/assets/icon-128x128.png', 2584 ), 2585 'banners' => array( 2586 '2x' => 'https://ps.w.org/a-fake-plugin/assets/banner-1544x500.png', 2587 '1x' => 'https://ps.w.org/a-fake-plugin/assets/banner-772x250.png', 2588 ), 2589 'banners_rtl' => array(), 2590 'tested' => '5.5.0', 2591 'requires_php' => '5.6.20', 2592 'compatibility' => new stdClass(), 2593 ); 2594 2595 $mock_theme = (object) array( 2596 'theme' => 'a-fake-theme', 2597 'new_version' => '9.9', 2598 'url' => 'https://wordpress.org/themes/a-fake-theme/', 2599 'package' => 'https://downloads.wordpress.org/theme/a-fake-theme.9.9.zip', 2600 'requires' => '5.0.0', 2601 'requires_php' => '5.6.20', 2602 ); 2603 2604 $test_plugins_enabled = wp_is_auto_update_forced_for_item( 'plugin', true, $mock_plugin ); 2605 $test_themes_enabled = wp_is_auto_update_forced_for_item( 'theme', true, $mock_theme ); 2606 2607 $ui_enabled_for_plugins = wp_is_auto_update_enabled_for_type( 'plugin' ); 2608 $ui_enabled_for_themes = wp_is_auto_update_enabled_for_type( 'theme' ); 2609 $plugin_filter_present = has_filter( 'auto_update_plugin' ); 2610 $theme_filter_present = has_filter( 'auto_update_theme' ); 2611 2612 if ( ( ! $test_plugins_enabled && $ui_enabled_for_plugins ) 2613 || ( ! $test_themes_enabled && $ui_enabled_for_themes ) 2614 ) { 2615 return (object) array( 2616 'status' => 'critical', 2617 '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.' ), 2618 ); 2619 } 2620 2621 if ( ( ! $test_plugins_enabled && $plugin_filter_present ) 2622 && ( ! $test_themes_enabled && $theme_filter_present ) 2623 ) { 2624 return (object) array( 2625 'status' => 'recommended', 2626 'message' => __( 'Auto-updates for plugins and themes appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ), 2627 ); 2628 } elseif ( ! $test_plugins_enabled && $plugin_filter_present ) { 2629 return (object) array( 2630 'status' => 'recommended', 2631 'message' => __( 'Auto-updates for plugins appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ), 2632 ); 2633 } elseif ( ! $test_themes_enabled && $theme_filter_present ) { 2634 return (object) array( 2635 'status' => 'recommended', 2636 'message' => __( 'Auto-updates for themes appear to be disabled. This will prevent your site from receiving new versions automatically when available.' ), 2637 ); 2638 } 2639 2640 return (object) array( 2641 'status' => 'good', 2642 'message' => __( 'There appear to be no issues with plugin and theme auto-updates.' ), 2643 ); 2644 } 2645 2646 /** 2647 * Run a loopback test on our site. 2648 * 2649 * Loopbacks are what WordPress uses to communicate with itself to start up WP_Cron, scheduled posts, 2650 * make sure plugin or theme edits don't cause site failures and similar. 2651 * 2652 * @since 5.2.0 2653 * 2654 * @return object The test results. 2655 */ 2656 public function can_perform_loopback() { 2657 $body = array( 'site-health' => 'loopback-test' ); 2658 $cookies = wp_unslash( $_COOKIE ); 2659 $timeout = 10; 2660 $headers = array( 2661 'Cache-Control' => 'no-cache', 2662 ); 2663 /** This filter is documented in wp-includes/class-wp-http-streams.php */ 2664 $sslverify = apply_filters( 'https_local_ssl_verify', false ); 2665 2666 // Include Basic auth in loopback requests. 2667 if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { 2668 $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); 2669 } 2670 2671 $url = site_url( 'wp-cron.php' ); 2672 2673 /* 2674 * A post request is used for the wp-cron.php loopback test to cause the file 2675 * to finish early without triggering cron jobs. This has two benefits: 2676 * - cron jobs are not triggered a second time on the site health page, 2677 * - the loopback request finishes sooner providing a quicker result. 2678 * 2679 * Using a POST request causes the loopback to differ slightly to the standard 2680 * GET request WordPress uses for wp-cron.php loopback requests but is close 2681 * enough. See https://core.trac.wordpress.org/ticket/52547 2682 */ 2683 $r = wp_remote_post( $url, compact( 'body', 'cookies', 'headers', 'timeout', 'sslverify' ) ); 2684 2685 if ( is_wp_error( $r ) ) { 2686 return (object) array( 2687 'status' => 'critical', 2688 'message' => sprintf( 2689 '%s<br>%s', 2690 __( 'The loopback request to your site failed, this means features relying on them are not currently working as expected.' ), 2691 sprintf( 2692 /* translators: 1: The WordPress error message. 2: The WordPress error code. */ 2693 __( 'Error: %1$s (%2$s)' ), 2694 $r->get_error_message(), 2695 $r->get_error_code() 2696 ) 2697 ), 2698 ); 2699 } 2700 2701 if ( 200 !== wp_remote_retrieve_response_code( $r ) ) { 2702 return (object) array( 2703 'status' => 'recommended', 2704 'message' => sprintf( 2705 /* translators: %d: The HTTP response code returned. */ 2706 __( '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.' ), 2707 wp_remote_retrieve_response_code( $r ) 2708 ), 2709 ); 2710 } 2711 2712 return (object) array( 2713 'status' => 'good', 2714 'message' => __( 'The loopback request to your site completed successfully.' ), 2715 ); 2716 } 2717 2718 /** 2719 * Create a weekly cron event, if one does not already exist. 2720 * 2721 * @since 5.4.0 2722 */ 2723 public function maybe_create_scheduled_event() { 2724 if ( ! wp_next_scheduled( 'wp_site_health_scheduled_check' ) && ! wp_installing() ) { 2725 wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', 'wp_site_health_scheduled_check' ); 2726 } 2727 } 2728 2729 /** 2730 * Run our scheduled event to check and update the latest site health status for the website. 2731 * 2732 * @since 5.4.0 2733 */ 2734 public function wp_cron_scheduled_check() { 2735 // Bootstrap wp-admin, as WP_Cron doesn't do this for us. 2736 require_once trailingslashit( ABSPATH ) . 'wp-admin/includes/admin.php'; 2737 2738 $tests = WP_Site_Health::get_tests(); 2739 2740 $results = array(); 2741 2742 $site_status = array( 2743 'good' => 0, 2744 'recommended' => 0, 2745 'critical' => 0, 2746 ); 2747 2748 // Don't run https test on development environments. 2749 if ( $this->is_development_environment() ) { 2750 unset( $tests['async']['https_status'] ); 2751 } 2752 2753 foreach ( $tests['direct'] as $test ) { 2754 if ( ! empty( $test['skip_cron'] ) ) { 2755 continue; 2756 } 2757 2758 if ( is_string( $test['test'] ) ) { 2759 $test_function = sprintf( 2760 'get_test_%s', 2761 $test['test'] 2762 ); 2763 2764 if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) { 2765 $results[] = $this->perform_test( array( $this, $test_function ) ); 2766 continue; 2767 } 2768 } 2769 2770 if ( is_callable( $test['test'] ) ) { 2771 $results[] = $this->perform_test( $test['test'] ); 2772 } 2773 } 2774 2775 foreach ( $tests['async'] as $test ) { 2776 if ( ! empty( $test['skip_cron'] ) ) { 2777 continue; 2778 } 2779 2780 // Local endpoints may require authentication, so asynchronous tests can pass a direct test runner as well. 2781 if ( ! empty( $test['async_direct_test'] ) && is_callable( $test['async_direct_test'] ) ) { 2782 // This test is callable, do so and continue to the next asynchronous check. 2783 $results[] = $this->perform_test( $test['async_direct_test'] ); 2784 continue; 2785 } 2786 2787 if ( is_string( $test['test'] ) ) { 2788 // Check if this test has a REST API endpoint. 2789 if ( isset( $test['has_rest'] ) && $test['has_rest'] ) { 2790 $result_fetch = wp_remote_get( 2791 $test['test'], 2792 array( 2793 'body' => array( 2794 '_wpnonce' => wp_create_nonce( 'wp_rest' ), 2795 ), 2796 ) 2797 ); 2798 } else { 2799 $result_fetch = wp_remote_post( 2800 admin_url( 'admin-ajax.php' ), 2801 array( 2802 'body' => array( 2803 'action' => $test['test'], 2804 '_wpnonce' => wp_create_nonce( 'health-check-site-status' ), 2805 ), 2806 ) 2807 ); 2808 } 2809 2810 if ( ! is_wp_error( $result_fetch ) && 200 === wp_remote_retrieve_response_code( $result_fetch ) ) { 2811 $result = json_decode( wp_remote_retrieve_body( $result_fetch ), true ); 2812 } else { 2813 $result = false; 2814 } 2815 2816 if ( is_array( $result ) ) { 2817 $results[] = $result; 2818 } else { 2819 $results[] = array( 2820 'status' => 'recommended', 2821 'label' => __( 'A test is unavailable' ), 2822 ); 2823 } 2824 } 2825 } 2826 2827 foreach ( $results as $result ) { 2828 if ( 'critical' === $result['status'] ) { 2829 $site_status['critical']++; 2830 } elseif ( 'recommended' === $result['status'] ) { 2831 $site_status['recommended']++; 2832 } else { 2833 $site_status['good']++; 2834 } 2835 } 2836 2837 set_transient( 'health-check-site-status-result', wp_json_encode( $site_status ) ); 2838 } 2839 2840 /** 2841 * Checks if the current environment type is set to 'development' or 'local'. 2842 * 2843 * @since 5.6.0 2844 * 2845 * @return bool True if it is a development environment, false if not. 2846 */ 2847 public function is_development_environment() { 2848 return in_array( wp_get_environment_type(), array( 'development', 'local' ), true ); 2849 } 2850 2851 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |