From 039ce3f16f19742c8cbf40cf9aa5cdd2fa7449ed Mon Sep 17 00:00:00 2001
From: TimothyBlynJacobs
Date: Thu, 15 Oct 2020 02:00:08 +0000
Subject: [PATCH] Site Health, REST API: Move async tests to REST API
endpoints.
This provides more flexibility when writing tests and benefits from running in a front-end context which is necessary for some tests like checking that updates are supported. Additionally, this provides a more robust interface for developers who want to integrate with Site Health tests.
Because the `wp/v2` endpoint is reserved for modeling core entities, site health is registered in its own `wp-site-health/v1` namespace.
The existing ajax actions have been maintained for backward compatibility.
Props Clorith, chrisvanpatten, afragen, pokhriyal, TimothyBlynJacobs.
Fixes #48105.
Built from https://develop.svn.wordpress.org/trunk@49154
git-svn-id: http://core.svn.wordpress.org/trunk@48916 1a063a9b-81f0-0310-95a4-ce76da25c4cd
---
wp-admin/admin-ajax.php | 10 +-
wp-admin/includes/ajax-actions.php | 52 ++++
.../class-wp-site-health-auto-updates.php | 51 +--
wp-admin/includes/class-wp-site-health.php | 24 +-
wp-admin/js/site-health.js | 115 ++++---
wp-admin/js/site-health.min.js | 2 +-
wp-includes/rest-api.php | 5 +
.../class-wp-rest-site-health-controller.php | 293 ++++++++++++++++++
wp-includes/script-loader.php | 2 +-
wp-includes/version.php | 2 +-
wp-settings.php | 1 +
11 files changed, 466 insertions(+), 91 deletions(-)
create mode 100644 wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php
diff --git a/wp-admin/admin-ajax.php b/wp-admin/admin-ajax.php
index 28caf3d227..144facf7fe 100644
--- a/wp-admin/admin-ajax.php
+++ b/wp-admin/admin-ajax.php
@@ -143,7 +143,15 @@ $core_actions_post = array(
);
// Deprecated.
-$core_actions_post_deprecated = array( 'wp-fullscreen-save-post', 'press-this-save-post', 'press-this-add-category' );
+$core_actions_post_deprecated = array(
+ 'wp-fullscreen-save-post',
+ 'press-this-save-post',
+ 'press-this-add-category',
+ 'health-check-dotorg-communication',
+ 'health-check-is-in-debug-mode',
+ 'health-check-background-updates',
+ 'health-check-loopback-requests',
+);
$core_actions_post = array_merge( $core_actions_post, $core_actions_post_deprecated );
// Register core Ajax calls.
diff --git a/wp-admin/includes/ajax-actions.php b/wp-admin/includes/ajax-actions.php
index 357fdc8d52..f359732b15 100644
--- a/wp-admin/includes/ajax-actions.php
+++ b/wp-admin/includes/ajax-actions.php
@@ -5142,8 +5142,21 @@ function wp_ajax_wp_privacy_erase_personal_data() {
* Ajax handler for site health checks on server communication.
*
* @since 5.2.0
+ * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_dotorg_communication()
+ * @see WP_REST_Site_Health_Controller::test_dotorg_communication()
*/
function wp_ajax_health_check_dotorg_communication() {
+ _doing_it_wrong(
+ 'wp_ajax_health_check_dotorg_communication',
+ sprintf(
+ // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
+ __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
+ 'wp_ajax_health_check_dotorg_communication',
+ 'WP_REST_Site_Health_Controller::test_dotorg_communication'
+ ),
+ '5.6.0'
+ );
+
check_ajax_referer( 'health-check-site-status' );
if ( ! current_user_can( 'view_site_health_checks' ) ) {
@@ -5162,8 +5175,21 @@ function wp_ajax_health_check_dotorg_communication() {
* Ajax handler for site health checks on background updates.
*
* @since 5.2.0
+ * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_background_updates()
+ * @see WP_REST_Site_Health_Controller::test_background_updates()
*/
function wp_ajax_health_check_background_updates() {
+ _doing_it_wrong(
+ 'wp_ajax_health_check_background_updates',
+ sprintf(
+ // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
+ __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
+ 'wp_ajax_health_check_background_updates',
+ 'WP_REST_Site_Health_Controller::test_background_updates'
+ ),
+ '5.6.0'
+ );
+
check_ajax_referer( 'health-check-site-status' );
if ( ! current_user_can( 'view_site_health_checks' ) ) {
@@ -5182,8 +5208,21 @@ function wp_ajax_health_check_background_updates() {
* Ajax handler for site health checks on loopback requests.
*
* @since 5.2.0
+ * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_loopback_requests()
+ * @see WP_REST_Site_Health_Controller::test_loopback_requests()
*/
function wp_ajax_health_check_loopback_requests() {
+ _doing_it_wrong(
+ 'wp_ajax_health_check_loopback_requests',
+ sprintf(
+ // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
+ __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
+ 'wp_ajax_health_check_loopback_requests',
+ 'WP_REST_Site_Health_Controller::test_loopback_requests'
+ ),
+ '5.6.0'
+ );
+
check_ajax_referer( 'health-check-site-status' );
if ( ! current_user_can( 'view_site_health_checks' ) ) {
@@ -5219,8 +5258,21 @@ function wp_ajax_health_check_site_status_result() {
* Ajax handler for site health check to get directories and database sizes.
*
* @since 5.2.0
+ * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::get_directory_sizes()
+ * @see WP_REST_Site_Health_Controller::get_directory_sizes()
*/
function wp_ajax_health_check_get_sizes() {
+ _doing_it_wrong(
+ 'wp_ajax_health_check_get_sizes',
+ sprintf(
+ // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
+ __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
+ 'wp_ajax_health_check_get_sizes',
+ 'WP_REST_Site_Health_Controller::get_directory_sizes'
+ ),
+ '5.6.0'
+ );
+
check_ajax_referer( 'health-check-site-status-result' );
if ( ! current_user_can( 'view_site_health_checks' ) || is_multisite() ) {
diff --git a/wp-admin/includes/class-wp-site-health-auto-updates.php b/wp-admin/includes/class-wp-site-health-auto-updates.php
index 4e3383eb1a..0bcdbf28d2 100644
--- a/wp-admin/includes/class-wp-site-health-auto-updates.php
+++ b/wp-admin/includes/class-wp-site-health-auto-updates.php
@@ -90,46 +90,7 @@ class WP_Site_Health_Auto_Updates {
* @return array The test results.
*/
public function test_wp_version_check_attached() {
- if ( ! is_main_site() ) {
- return;
- }
-
- $cookies = wp_unslash( $_COOKIE );
- $timeout = 10;
- $headers = array(
- 'Cache-Control' => 'no-cache',
- );
- /** This filter is documented in wp-includes/class-wp-http-streams.php */
- $sslverify = apply_filters( 'https_local_ssl_verify', false );
-
- // Include Basic auth in loopback requests.
- if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
- $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
- }
-
- $url = add_query_arg(
- array(
- 'health-check-test-wp_version_check' => true,
- ),
- admin_url( 'site-health.php' )
- );
-
- $test = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
-
- if ( is_wp_error( $test ) ) {
- return array(
- 'description' => sprintf(
- /* translators: %s: Name of the filter used. */
- __( 'Could not confirm that the %s filter is available.' ),
- 'wp_version_check()
'
- ),
- 'severity' => 'warning',
- );
- }
-
- $response = wp_remote_retrieve_body( $test );
-
- if ( 'yes' !== $response ) {
+ if ( ! has_filter( 'wp_version_check', 'wp_version_check' ) ) {
return array(
'description' => sprintf(
/* translators: %s: Name of the filter used. */
@@ -310,6 +271,11 @@ class WP_Site_Health_Auto_Updates {
* @return array The test results.
*/
function test_check_wp_filesystem_method() {
+ // Make sure the `request_filesystem_credentials` function is available during our REST call.
+ if ( ! function_exists( 'request_filesystem_credentials' ) ) {
+ require_once ABSPATH . '/wp-admin/includes/file.php';
+ }
+
$skin = new Automatic_Upgrader_Skin;
$success = $skin->request_filesystem_credentials( false, ABSPATH );
@@ -356,6 +322,11 @@ class WP_Site_Health_Auto_Updates {
return false;
}
+ // Make sure the `get_core_checksums` function is available during our REST call.
+ if ( ! function_exists( 'get_core_checksums' ) ) {
+ require_once ABSPATH . '/wp-admin/includes/update.php';
+ }
+
$checksums = get_core_checksums( $wp_version, 'en_US' );
$dev = ( false !== strpos( $wp_version, '-' ) );
// Get the last stable version's files and test against that.
diff --git a/wp-admin/includes/class-wp-site-health.php b/wp-admin/includes/class-wp-site-health.php
index de698d7a30..6070a57b9b 100644
--- a/wp-admin/includes/class-wp-site-health.php
+++ b/wp-admin/includes/class-wp-site-health.php
@@ -133,6 +133,7 @@ class WP_Site_Health {
if ( is_string( $test['test'] ) ) {
$health_check_js_variables['site_status']['async'][] = array(
'test' => $test['test'],
+ 'has_rest' => ( isset( $test['has_rest'] ) ? $test['has_rest'] : false ),
'completed' => false,
);
}
@@ -2080,6 +2081,7 @@ class WP_Site_Health {
* experiences.
*
* @since 5.2.0
+ * @since 5.6.0 Added support for `has_rest` and `permissions`.
*
* @return array The list of tests to run.
*/
@@ -2153,16 +2155,19 @@ class WP_Site_Health {
),
'async' => array(
'dotorg_communication' => array(
- 'label' => __( 'Communication with WordPress.org' ),
- 'test' => 'dotorg_communication',
+ 'label' => __( 'Communication with WordPress.org' ),
+ 'test' => rest_url( 'wp-site-health/v1/tests/dotorg-communication' ),
+ 'has_rest' => true,
),
'background_updates' => array(
- 'label' => __( 'Background updates' ),
- 'test' => 'background_updates',
+ 'label' => __( 'Background updates' ),
+ 'test' => rest_url( 'wp-site-health/v1/tests/background-updates' ),
+ 'has_rest' => true,
),
'loopback_requests' => array(
- 'label' => __( 'Loopback request' ),
- 'test' => 'loopback_requests',
+ 'label' => __( 'Loopback request' ),
+ 'test' => rest_url( 'wp-site-health/v1/tests/loopback-requests' ),
+ 'has_rest' => true,
),
),
);
@@ -2199,9 +2204,10 @@ class WP_Site_Health {
* Plugins and themes are encouraged to prefix test identifiers with their slug
* to avoid any collisions between tests.
*
- * @type string $label A friendly label for your test to identify it by.
- * @type mixed $test A callable to perform a direct test, or a string Ajax action to be called
- * to perform an async test.
+ * @type string $label A friendly label for your test to identify it by.
+ * @type mixed $test A callable to perform a direct test, or a string AJAX action to be
+ * called to perform an async test.
+ * @type boolean $has_rest Optional. Denote if `$test` has a REST API endpoint.
* }
* }
*/
diff --git a/wp-admin/js/site-health.js b/wp-admin/js/site-health.js
index 73d4fe922f..54497464cd 100644
--- a/wp-admin/js/site-health.js
+++ b/wp-admin/js/site-health.js
@@ -11,7 +11,6 @@ jQuery( document ).ready( function( $ ) {
var __ = wp.i18n.__,
_n = wp.i18n._n,
sprintf = wp.i18n.sprintf,
- data,
clipboard = new ClipboardJS( '.site-health-copy-buttons .copy-button' ),
isDebugTab = $( '.health-check-body.health-check-debug-tab' ).length,
pathsSizesSection = $( '#health-check-accordion-block-wp-paths-sizes' ),
@@ -78,11 +77,16 @@ jQuery( document ).ready( function( $ ) {
issueWrapper = $( '#health-check-issues-' + issue.status ),
heading,
count;
-
+
SiteHealth.site_status.issues[ issue.status ]++;
count = SiteHealth.site_status.issues[ issue.status ];
+ // If no test name is supplied, append a placeholder for markup references.
+ if ( typeof issue.test === 'undefined' ) {
+ issue.test = issue.status + count;
+ }
+
if ( 'critical' === issue.status ) {
heading = sprintf(
_n( '%s critical issue', '%s critical issues', count ),
@@ -119,10 +123,10 @@ jQuery( document ).ready( function( $ ) {
var $progressLabel = $( '.site-health-progress-label', $wrapper );
var $circle = $( '.site-health-progress svg #bar' );
var totalTests = parseInt( SiteHealth.site_status.issues.good, 0 ) +
- parseInt( SiteHealth.site_status.issues.recommended, 0 ) +
- ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 );
+ parseInt( SiteHealth.site_status.issues.recommended, 0 ) +
+ ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 );
var failedTests = ( parseInt( SiteHealth.site_status.issues.recommended, 0 ) * 0.5 ) +
- ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 );
+ ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 );
var val = 100 - Math.ceil( ( failedTests / totalTests ) * 100 );
if ( 0 === totalTests ) {
@@ -206,15 +210,49 @@ jQuery( document ).ready( function( $ ) {
this.completed = true;
- $.post(
- ajaxurl,
- data,
- function( response ) {
+ if ( 'undefined' !== typeof( this.has_rest ) && this.has_rest ) {
+ wp.apiRequest( {
+ url: this.test
+ } )
+ .done( function( response ) {
+ /** This filter is documented in wp-admin/includes/class-wp-site-health.php */
+ appendIssue( wp.hooks.applyFilters( 'site_status_test_result', response ) );
+ } )
+ .fail( function( response ) {
+ var description;
+
+ if ( 'undefined' !== typeof( response.responseJSON ) && 'undefined' !== typeof( response.responseJSON.message ) ) {
+ description = response.responseJSON.message;
+ } else {
+ description = __( 'No details available' );
+ }
+
+ addFailedSiteHealthCheckNotice( this.url, description );
+ } )
+ .always( function() {
+ maybeRunNextAsyncTest();
+ } );
+ } else {
+ $.post(
+ ajaxurl,
+ data
+ ).done( function( response ) {
/** This filter is documented in wp-admin/includes/class-wp-site-health.php */
appendIssue( wp.hooks.applyFilters( 'site_status_test_result', response.data ) );
+ } ).fail( function( response ) {
+ var description;
+
+ if ( 'undefined' !== typeof( response.responseJSON ) && 'undefined' !== typeof( response.responseJSON.message ) ) {
+ description = response.responseJSON.message;
+ } else {
+ description = __( 'No details available' );
+ }
+
+ addFailedSiteHealthCheckNotice( this.url, description );
+ } ).always( function() {
maybeRunNextAsyncTest();
- }
- );
+ } );
+ }
return false;
} );
@@ -225,6 +263,29 @@ jQuery( document ).ready( function( $ ) {
}
}
+ /**
+ * Add the details of a failed asynchronous test to the list of test results.
+ *
+ * @since 5.6.0
+ */
+ function addFailedSiteHealthCheckNotice( url, description ) {
+ var issue;
+
+ issue = {
+ 'status': 'recommended',
+ 'label': __( 'A test is unavailable' ),
+ 'badge': {
+ 'color': 'red',
+ 'label': __( 'Unavailable' )
+ },
+ 'description': '' + url + '
' + description + '
',
+ 'actions': ''
+ };
+
+ /** This filter is documented in wp-admin/includes/class-wp-site-health.php */
+ appendIssue( wp.hooks.applyFilters( 'site_status_test_result', issue ) );
+ }
+
if ( 'undefined' !== typeof SiteHealth && ! isDebugTab ) {
if ( 0 === SiteHealth.site_status.direct.length && 0 === SiteHealth.site_status.async.length ) {
recalculateProgression();
@@ -243,32 +304,13 @@ jQuery( document ).ready( function( $ ) {
}
if ( 0 < SiteHealth.site_status.async.length ) {
- data = {
- 'action': 'health-check-' + SiteHealth.site_status.async[0].test.replace( '_', '-' ),
- '_wpnonce': SiteHealth.nonce.site_status
- };
-
- SiteHealth.site_status.async[0].completed = true;
-
- $.post(
- ajaxurl,
- data,
- function( response ) {
- appendIssue( response.data );
- maybeRunNextAsyncTest();
- }
- );
+ maybeRunNextAsyncTest();
} else {
recalculateProgression();
}
}
function getDirectorySizes() {
- var data = {
- action: 'health-check-get-sizes',
- _wpnonce: SiteHealth.nonce.site_status_result
- };
-
var timestamp = ( new Date().getTime() );
// After 3 seconds announce that we're still waiting for directory sizes.
@@ -276,20 +318,17 @@ jQuery( document ).ready( function( $ ) {
wp.a11y.speak( __( 'Please wait...' ) );
}, 3000 );
- $.post( {
- type: 'POST',
- url: ajaxurl,
- data: data,
- dataType: 'json'
+ wp.apiRequest( {
+ path: '/wp-site-health/v1/directory-sizes'
} ).done( function( response ) {
- updateDirSizes( response.data || {} );
+ updateDirSizes( response || {} );
} ).always( function() {
var delay = ( new Date().getTime() ) - timestamp;
$( '.health-check-wp-paths-sizes.spinner' ).css( 'visibility', 'hidden' );
recalculateProgression();
- if ( delay > 3000 ) {
+ if ( delay > 3000 ) {
/*
* We have announced that we're waiting.
* Announce that we're ready after giving at least 3 seconds
diff --git a/wp-admin/js/site-health.min.js b/wp-admin/js/site-health.min.js
index 9056768ddd..fecd022138 100644
--- a/wp-admin/js/site-health.min.js
+++ b/wp-admin/js/site-health.min.js
@@ -1,2 +1,2 @@
/*! This file is auto-generated */
-jQuery(document).ready(function(l){var e,a,t,s,i,r=wp.i18n.__,n=wp.i18n._n,c=wp.i18n.sprintf,h=new ClipboardJS(".site-health-copy-buttons .copy-button"),u=l(".health-check-body.health-check-debug-tab").length,o=l("#health-check-accordion-block-wp-paths-sizes");function d(e){var t,s,a=wp.template("health-check-issue"),i=l("#health-check-issues-"+e.status);SiteHealth.site_status.issues[e.status]++,s=SiteHealth.site_status.issues[e.status],"critical"===e.status?t=c(n("%s critical issue","%s critical issues",s),''+s+""):"recommended"===e.status?t=c(n("%s recommended improvement","%s recommended improvements",s),''+s+""):"good"===e.status&&(t=c(n("%s item with no issues detected","%s items with no issues detected",s),''+s+"")),t&&l(".site-health-issue-count-title",i).html(t),l(".issues","#health-check-issues-"+e.status).append(a(e))}function p(){var e,t,s=l(".site-health-progress"),a=s.closest(".site-health-progress-wrapper"),i=l(".site-health-progress-label",a),n=l(".site-health-progress svg #bar"),c=parseInt(SiteHealth.site_status.issues.good,0)+parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),h=.5*parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),o=100-Math.ceil(h/c*100);0!==c?(a.removeClass("loading"),e=n.attr("r"),o<0&&(o=0),100'+s+""):"recommended"===e.status?t=o(n("%s recommended improvement","%s recommended improvements",s),''+s+""):"good"===e.status&&(t=o(n("%s item with no issues detected","%s items with no issues detected",s),''+s+"")),t&&c(".site-health-issue-count-title",i).html(t),c(".issues","#health-check-issues-"+e.status).append(a(e))}function u(){var e,t,s=c(".site-health-progress"),a=s.closest(".site-health-progress-wrapper"),i=c(".site-health-progress-label",a),n=c(".site-health-progress svg #bar"),o=parseInt(SiteHealth.site_status.issues.good,0)+parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),l=.5*parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),r=100-Math.ceil(l/o*100);0!==o?(a.removeClass("loading"),e=n.attr("r"),r<0&&(r=0),100"+e+"
"+t+"
",actions:""},r(wp.hooks.applyFilters("site_status_test_result",s))}i.on("success",function(e){var t=c(e.trigger),s=c(".success",t.closest("div"));e.clearSelection(),t.focus(),clearTimeout(a),s.removeClass("hidden"),a=setTimeout(function(){s.addClass("hidden"),i.clipboardAction.fakeElem&&i.clipboardAction.removeFake&&i.clipboardAction.removeFake()},3e3),wp.a11y.speak(h("Site information has been copied to your clipboard."))}),c(".health-check-accordion").on("click",".health-check-accordion-trigger",function(){"true"===c(this).attr("aria-expanded")?(c(this).attr("aria-expanded","false"),c("#"+c(this).attr("aria-controls")).attr("hidden",!0)):(c(this).attr("aria-expanded","true"),c("#"+c(this).attr("aria-controls")).attr("hidden",!1))}),c(".site-health-view-passed").on("click",function(){var e=c("#health-check-issues-good");e.toggleClass("hidden"),c(this).attr("aria-expanded",!e.hasClass("hidden"))}),"undefined"==typeof SiteHealth||d||(0===SiteHealth.site_status.direct.length&&0===SiteHealth.site_status.async.length?u():SiteHealth.site_status.issues={good:0,recommended:0,critical:0},0register_routes();
+
+ // Site Health
+ $site_health = WP_Site_Health::get_instance();
+ $controller = new WP_REST_Site_Health_Controller( $site_health );
+ $controller->register_routes();
}
/**
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php
new file mode 100644
index 0000000000..b1eb6addb0
--- /dev/null
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php
@@ -0,0 +1,293 @@
+namespace = 'wp-site-health/v1';
+ $this->rest_base = 'tests';
+
+ $this->site_health = $site_health;
+ }
+
+ /**
+ * Registers API routes.
+ *
+ * @since 5.6.0
+ *
+ * @see register_rest_route()
+ */
+ public function register_routes() {
+ register_rest_route(
+ $this->namespace,
+ sprintf(
+ '/%s/%s',
+ $this->rest_base,
+ 'background-updates'
+ ),
+ array(
+ array(
+ 'methods' => 'GET',
+ 'callback' => array( $this, 'test_background_updates' ),
+ 'permission_callback' => function () {
+ return $this->validate_request_permission( 'background_updates' );
+ },
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ sprintf(
+ '/%s/%s',
+ $this->rest_base,
+ 'loopback-requests'
+ ),
+ array(
+ array(
+ 'methods' => 'GET',
+ 'callback' => array( $this, 'test_loopback_requests' ),
+ 'permission_callback' => function () {
+ return $this->validate_request_permission( 'loopback_requests' );
+ },
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ sprintf(
+ '/%s/%s',
+ $this->rest_base,
+ 'dotorg-communication'
+ ),
+ array(
+ array(
+ 'methods' => 'GET',
+ 'callback' => array( $this, 'test_dotorg_communication' ),
+ 'permission_callback' => function () {
+ return $this->validate_request_permission( 'dotorg_communication' );
+ },
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ sprintf(
+ '/%s',
+ 'directory-sizes'
+ ),
+ array(
+ 'methods' => 'GET',
+ 'callback' => array( $this, 'get_directory_sizes' ),
+ 'permission_callback' => function() {
+ return $this->validate_request_permission( 'debug_enabled' ) && ! is_multisite();
+ },
+ )
+ );
+ }
+
+ /**
+ * Validates if the current user can request this REST endpoint.
+ *
+ * @since 5.6.0
+ *
+ * @param string $check The endpoint check being ran.
+ * @return bool
+ */
+ protected function validate_request_permission( $check ) {
+ $default_capability = 'view_site_health_checks';
+
+ /**
+ * Filters the capability needed to run a given Site Health check.
+ *
+ * @since 5.6.0
+ *
+ * @param string $default_capability The default capability required for this check.
+ * @param string $check The Site Health check being performed.
+ */
+ $capability = apply_filters( "site_health_test_rest_capability_{$check}", $default_capability, $check );
+
+ return current_user_can( $capability );
+ }
+
+ /**
+ * Checks if background updates work as expected.
+ *
+ * @since 5.6.0
+ *
+ * @return array
+ */
+ public function test_background_updates() {
+ return $this->site_health->get_test_background_updates();
+ }
+
+ /**
+ * Checks that the site can reach the WordPress.org API.
+ *
+ * @since 5.6.0
+ *
+ * @return array
+ */
+ public function test_dotorg_communication() {
+ return $this->site_health->get_test_dotorg_communication();
+ }
+
+ /**
+ * Checks that loopbacks can be performed.
+ *
+ * @since 5.6.0
+ *
+ * @return array
+ */
+ public function test_loopback_requests() {
+ return $this->site_health->get_test_loopback_requests();
+ }
+
+ /**
+ * Gets the current directory sizes for this install.
+ *
+ * @since 5.6.0
+ *
+ * @return array|WP_Error
+ */
+ public function get_directory_sizes() {
+ if ( ! class_exists( 'WP_Debug_Data' ) ) {
+ require_once( ABSPATH . 'wp-admin/includes/class-wp-debug-data.php' );
+ }
+
+ $sizes_data = WP_Debug_Data::get_sizes();
+ $all_sizes = array( 'raw' => 0 );
+
+ foreach ( $sizes_data as $name => $value ) {
+ $name = sanitize_text_field( $name );
+ $data = array();
+
+ if ( isset( $value['size'] ) ) {
+ if ( is_string( $value['size'] ) ) {
+ $data['size'] = sanitize_text_field( $value['size'] );
+ } else {
+ $data['size'] = (int) $value['size'];
+ }
+ }
+
+ if ( isset( $value['debug'] ) ) {
+ if ( is_string( $value['debug'] ) ) {
+ $data['debug'] = sanitize_text_field( $value['debug'] );
+ } else {
+ $data['debug'] = (int) $value['debug'];
+ }
+ }
+
+ if ( ! empty( $value['raw'] ) ) {
+ $data['raw'] = (int) $value['raw'];
+ }
+
+ $all_sizes[ $name ] = $data;
+ }
+
+ if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) {
+ return new WP_Error( 'not_available', __( 'Directory sizes could not be returned.' ), array( 'status' => 500 ) );
+ }
+
+ return $all_sizes;
+ }
+
+ /**
+ * Gets the schema for each site health test.
+ *
+ * @since 5.6.0
+ *
+ * @return array The test schema.
+ */
+ public function get_item_schema() {
+ if ( $this->schema ) {
+ return $this->schema;
+ }
+
+ $this->schema = array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => 'wp-site-health-test',
+ 'type' => 'object',
+ 'properties' => array(
+ 'test' => array(
+ 'type' => 'string',
+ 'description' => __( 'The name of the test being run.' ),
+ 'readonly' => true,
+ ),
+ 'label' => array(
+ 'type' => 'string',
+ 'description' => __( 'A label describing the test.' ),
+ 'readonly' => true,
+ ),
+ 'status' => array(
+ 'type' => 'string',
+ 'description' => __( 'The status of the test.' ),
+ 'enum' => array( 'good', 'recommended', 'critical' ),
+ 'readonly' => true,
+ ),
+ 'badge' => array(
+ 'type' => 'object',
+ 'description' => __( 'The category this test is grouped in.' ),
+ 'properties' => array(
+ 'label' => array(
+ 'type' => 'string',
+ 'readonly' => true,
+ ),
+ 'color' => array(
+ 'type' => 'string',
+ 'enum' => array( 'blue', 'orange', 'red', 'green', 'purple', 'gray' ),
+ 'readonly' => true,
+ ),
+ ),
+ 'readonly' => true,
+ ),
+ 'description' => array(
+ 'type' => 'string',
+ 'description' => __( 'A more descriptive explanation of what the test looks for, and why it is important for the user.' ),
+ 'readonly' => true,
+ ),
+ 'actions' => array(
+ 'type' => 'string',
+ 'description' => __( 'HTML containing an action to direct the user to where they can resolve the issue.' ),
+ 'readonly' => true,
+ ),
+ ),
+ );
+
+ return $this->schema;
+ }
+}
diff --git a/wp-includes/script-loader.php b/wp-includes/script-loader.php
index 1dd43e9393..02abb7381e 100644
--- a/wp-includes/script-loader.php
+++ b/wp-includes/script-loader.php
@@ -1286,7 +1286,7 @@ function wp_default_scripts( $scripts ) {
$scripts->add( 'plugin-install', "/wp-admin/js/plugin-install$suffix.js", array( 'jquery', 'jquery-ui-core', 'thickbox' ), false, 1 );
$scripts->set_translations( 'plugin-install' );
- $scripts->add( 'site-health', "/wp-admin/js/site-health$suffix.js", array( 'clipboard', 'jquery', 'wp-util', 'wp-a11y' ), false, 1 );
+ $scripts->add( 'site-health', "/wp-admin/js/site-health$suffix.js", array( 'clipboard', 'jquery', 'wp-util', 'wp-a11y', 'wp-api-request' ), false, 1 );
$scripts->set_translations( 'site-health' );
$scripts->add( 'privacy-tools', "/wp-admin/js/privacy-tools$suffix.js", array( 'jquery', 'wp-a11y' ), false, 1 );
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 77e5fcc855..767065b043 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -13,7 +13,7 @@
*
* @global string $wp_version
*/
-$wp_version = '5.6-alpha-49153';
+$wp_version = '5.6-alpha-49154';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
diff --git a/wp-settings.php b/wp-settings.php
index 8b3cc1297b..c636ffb937 100644
--- a/wp-settings.php
+++ b/wp-settings.php
@@ -261,6 +261,7 @@ require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-themes-controller.p
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-plugins-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-block-directory-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-application-passwords-controller.php';
+require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-site-health-controller.php';
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php';
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php';
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php';