From efe06cdceac62f928f1adf008bb55a4293f9a3c2 Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Thu, 15 Oct 2020 01:58:28 +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. git-svn-id: https://develop.svn.wordpress.org/trunk@49154 602fd350-edb4-49c9-b593-d223f7449a82 --- src/js/_enqueues/admin/site-health.js | 115 ++++--- src/wp-admin/admin-ajax.php | 10 +- src/wp-admin/includes/ajax-actions.php | 52 ++++ .../class-wp-site-health-auto-updates.php | 51 +-- .../includes/class-wp-site-health.php | 24 +- src/wp-includes/rest-api.php | 5 + .../class-wp-rest-site-health-controller.php | 293 ++++++++++++++++++ src/wp-includes/script-loader.php | 2 +- src/wp-settings.php | 1 + .../tests/rest-api/rest-schema-setup.php | 8 +- .../rest-api/rest-site-health-controller.php | 95 ++++++ tests/qunit/fixtures/wp-api-generated.js | 117 ++++++- 12 files changed, 682 insertions(+), 91 deletions(-) create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php create mode 100644 tests/phpunit/tests/rest-api/rest-site-health-controller.php diff --git a/src/js/_enqueues/admin/site-health.js b/src/js/_enqueues/admin/site-health.js index 73d4fe922f..54497464cd 100644 --- a/src/js/_enqueues/admin/site-health.js +++ b/src/js/_enqueues/admin/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/src/wp-admin/admin-ajax.php b/src/wp-admin/admin-ajax.php index 28caf3d227..144facf7fe 100644 --- a/src/wp-admin/admin-ajax.php +++ b/src/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/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index 357fdc8d52..f359732b15 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/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/src/wp-admin/includes/class-wp-site-health-auto-updates.php b/src/wp-admin/includes/class-wp-site-health-auto-updates.php index 4e3383eb1a..0bcdbf28d2 100644 --- a/src/wp-admin/includes/class-wp-site-health-auto-updates.php +++ b/src/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/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index de698d7a30..6070a57b9b 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/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/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 2c96ef6bff..bbbeae3532 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -316,6 +316,11 @@ function create_initial_rest_routes() { // Block Directory. $controller = new WP_REST_Block_Directory_Controller(); $controller->register_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/src/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php new file mode 100644 index 0000000000..b1eb6addb0 --- /dev/null +++ b/src/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/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 1dd43e9393..02abb7381e 100644 --- a/src/wp-includes/script-loader.php +++ b/src/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/src/wp-settings.php b/src/wp-settings.php index 8b3cc1297b..c636ffb937 100644 --- a/src/wp-settings.php +++ b/src/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'; diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index 4bc19ab649..086edeab9b 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -132,6 +132,11 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { '/wp/v2/plugins', '/wp/v2/plugins/(?P[^.\/]+(?:\/[^.\/]+)?)', '/wp/v2/block-directory/search', + '/wp-site-health/v1', + '/wp-site-health/v1/tests/background-updates', + '/wp-site-health/v1/tests/loopback-requests', + '/wp-site-health/v1/tests/dotorg-communication', + '/wp-site-health/v1/directory-sizes', ); $this->assertSameSets( $expected_routes, $routes ); @@ -141,7 +146,8 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { return ( '/' === $route || preg_match( '#^/oembed/1\.0(/.+)?$#', $route ) || - preg_match( '#^/wp/v2(/.+)?$#', $route ) + preg_match( '#^/wp/v2(/.+)?$#', $route ) || + preg_match( '#^/wp-site-health/v1(/.+)?$#', $route ) ); } diff --git a/tests/phpunit/tests/rest-api/rest-site-health-controller.php b/tests/phpunit/tests/rest-api/rest-site-health-controller.php new file mode 100644 index 0000000000..670380f30d --- /dev/null +++ b/tests/phpunit/tests/rest-api/rest-site-health-controller.php @@ -0,0 +1,95 @@ +user->create( + array( + 'role' => 'subscriber', + ) + ); + self::$admin = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); + } + + /** + * Clean up test fixtures. + * + * @since 5.6.0 + */ + public static function wpTearDownAfterClass() { + self::delete_user( self::$subscriber ); + self::delete_user( self::$admin ); + } + + public function test_logged_out() { + $response = rest_do_request( '/wp-site-health/v1/tests/dotorg-communication' ); + $this->assertErrorResponse( 'rest_forbidden', $response, 401 ); + } + + public function test_insufficient_caps() { + wp_set_current_user( self::$subscriber ); + $response = rest_do_request( '/wp-site-health/v1/tests/dotorg-communication' ); + $this->assertErrorResponse( 'rest_forbidden', $response, 403 ); + } + + public function test_custom_capability() { + wp_set_current_user( self::$admin ); + + add_filter( + 'site_health_test_rest_capability_dotorg_communication', + static function () { + return 'a_custom_capability'; + } + ); + + $response = rest_do_request( '/wp-site-health/v1/tests/dotorg-communication' ); + $this->assertErrorResponse( 'rest_forbidden', $response, 403 ); + } + + public function test() { + wp_set_current_user( self::$admin ); + $response = rest_do_request( '/wp-site-health/v1/tests/dotorg-communication' ); + $this->assertEquals( 'dotorg_communication', $response->get_data()['test'] ); + } +} diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 99bec50763..ccd13d4a46 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -14,7 +14,8 @@ mockedApiResponse.Schema = { "timezone_string": "", "namespaces": [ "oembed/1.0", - "wp/v2" + "wp/v2", + "wp-site-health/v1" ], "authentication": [], "routes": { @@ -5058,6 +5059,120 @@ mockedApiResponse.Schema = { } ] } + }, + "/wp-site-health/v1": { + "namespace": "wp-site-health/v1", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "namespace": { + "required": false, + "default": "wp-site-health/v1" + }, + "context": { + "required": false, + "default": "view" + } + } + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp-site-health/v1" + } + ] + } + }, + "/wp-site-health/v1/tests/background-updates": { + "namespace": "wp-site-health/v1", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": [] + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp-site-health/v1/tests/background-updates" + } + ] + } + }, + "/wp-site-health/v1/tests/loopback-requests": { + "namespace": "wp-site-health/v1", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": [] + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp-site-health/v1/tests/loopback-requests" + } + ] + } + }, + "/wp-site-health/v1/tests/dotorg-communication": { + "namespace": "wp-site-health/v1", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": [] + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp-site-health/v1/tests/dotorg-communication" + } + ] + } + }, + "/wp-site-health/v1/directory-sizes": { + "namespace": "wp-site-health/v1", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": [] + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp-site-health/v1/directory-sizes" + } + ] + } } } };