From 901909bf2cc79333d424628cef2064770b4b839b Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Tue, 1 Dec 2020 03:42:31 +0000 Subject: [PATCH] Site Health, App Passwords: Ensure REST API responses are properly translated. The REST API requests in Site Health and App Passwords now include `_locale=user` in the request URL to ensure the user's locale is used instead of the site locale. Additionally, the `apiRequest` library now sends a JSON `Accept` header which is required by `determine_locale()` to respect the `_locale` query parameter. The Site Health REST API controllers now manually load the default admin textdomain if not `is_admin()`. This allows for the Site Health tests to be translated even though the translations are part of the administration project and the REST API is not. Props oglekler, kebbet, Clorith, TimothyBlynJacobs, ocean90, SergeyBiryukov, adamsilverstein. Fixes #51871. git-svn-id: https://develop.svn.wordpress.org/trunk@49716 602fd350-edb4-49c9-b593-d223f7449a82 --- .../_enqueues/admin/application-passwords.js | 6 +-- src/js/_enqueues/admin/auth-app.js | 2 +- src/js/_enqueues/admin/site-health.js | 2 +- src/js/_enqueues/wp/api-request.js | 34 ++++++++++----- .../class-wp-rest-site-health-controller.php | 22 ++++++++++ src/wp-includes/script-loader.php | 2 +- tests/qunit/wp-includes/js/api-request.js | 43 ++++++++++++++----- 7 files changed, 84 insertions(+), 27 deletions(-) diff --git a/src/js/_enqueues/admin/application-passwords.js b/src/js/_enqueues/admin/application-passwords.js index 09e4434e2f..b1a20ceb8e 100644 --- a/src/js/_enqueues/admin/application-passwords.js +++ b/src/js/_enqueues/admin/application-passwords.js @@ -47,7 +47,7 @@ request = wp.hooks.applyFilters( 'wp_application_passwords_new_password_request', request, userId ); wp.apiRequest( { - path: '/wp/v2/users/' + userId + '/application-passwords', + path: '/wp/v2/users/' + userId + '/application-passwords?_locale=user', method: 'POST', data: request } ).always( function() { @@ -94,7 +94,7 @@ $submitButton.prop( 'disabled', true ); wp.apiRequest( { - path: '/wp/v2/users/' + userId + '/application-passwords/' + uuid, + path: '/wp/v2/users/' + userId + '/application-passwords/' + uuid + '?_locale=user', method: 'DELETE' } ).always( function() { $submitButton.prop( 'disabled', false ); @@ -123,7 +123,7 @@ $submitButton.prop( 'disabled', true ); wp.apiRequest( { - path: '/wp/v2/users/' + userId + '/application-passwords', + path: '/wp/v2/users/' + userId + '/application-passwords?_locale=user', method: 'DELETE' } ).always( function() { $submitButton.prop( 'disabled', false ); diff --git a/src/js/_enqueues/admin/auth-app.js b/src/js/_enqueues/admin/auth-app.js index 4af3a05fa8..ad4403032d 100644 --- a/src/js/_enqueues/admin/auth-app.js +++ b/src/js/_enqueues/admin/auth-app.js @@ -54,7 +54,7 @@ request = wp.hooks.applyFilters( 'wp_application_passwords_approve_app_request', request, context ); wp.apiRequest( { - path: '/wp/v2/users/me/application-passwords', + path: '/wp/v2/users/me/application-passwords?_locale=user', method: 'POST', data: request } ).done( function( response, textStatus, jqXHR ) { diff --git a/src/js/_enqueues/admin/site-health.js b/src/js/_enqueues/admin/site-health.js index d91b75e90a..9bba750094 100644 --- a/src/js/_enqueues/admin/site-health.js +++ b/src/js/_enqueues/admin/site-health.js @@ -271,7 +271,7 @@ jQuery( document ).ready( function( $ ) { if ( 'undefined' !== typeof( this.has_rest ) && this.has_rest ) { wp.apiRequest( { - url: this.test, + url: wp.url.addQueryArgs( this.test, { _locale: 'user' } ), headers: this.headers } ) .done( function( response ) { diff --git a/src/js/_enqueues/wp/api-request.js b/src/js/_enqueues/wp/api-request.js index 885e5b47f8..58ef531179 100644 --- a/src/js/_enqueues/wp/api-request.js +++ b/src/js/_enqueues/wp/api-request.js @@ -10,6 +10,7 @@ * * @since 4.9.0 * @since 5.6.0 Added overriding of the "PUT" and "DELETE" methods with "POST". + * Added an "application/json" Accept header to all requests. * @output wp-includes/js/api-request.js */ @@ -26,7 +27,7 @@ var path = options.path; var method = options.method; var namespaceTrimmed, endpointTrimmed, apiRoot; - var headers, addNonceHeader, headerName; + var headers, addNonceHeader, addAcceptHeader, headerName; if ( typeof options.namespace === 'string' && @@ -55,19 +56,24 @@ // If ?_wpnonce=... is present, no need to add a nonce header. addNonceHeader = ! ( options.data && options.data._wpnonce ); + addAcceptHeader = true; headers = options.headers || {}; - // If an 'X-WP-Nonce' header (or any case-insensitive variation - // thereof) was specified, no need to add a nonce header. - if ( addNonceHeader ) { - for ( headerName in headers ) { - if ( headers.hasOwnProperty( headerName ) ) { - if ( headerName.toLowerCase() === 'x-wp-nonce' ) { - addNonceHeader = false; - break; - } - } + for ( headerName in headers ) { + if ( ! headers.hasOwnProperty( headerName ) ) { + continue; + } + + // If an 'X-WP-Nonce' or 'Accept' header (or any case-insensitive variation + // thereof) was specified, no need to add the header again. + switch ( headerName.toLowerCase() ) { + case 'x-wp-nonce': + addNonceHeader = false; + break; + case 'accept': + addAcceptHeader = false; + break; } } @@ -78,6 +84,12 @@ }, headers ); } + if ( addAcceptHeader ) { + headers = $.extend( { + 'Accept': 'application/json, */*;q=0.1' + }, headers ); + } + if ( typeof method === 'string' ) { method = method.toUpperCase(); 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 index 6dbc239ec0..f790dadb04 100644 --- 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 @@ -171,6 +171,7 @@ class WP_REST_Site_Health_Controller extends WP_REST_Controller { * @return array */ public function test_background_updates() { + $this->load_admin_textdomain(); return $this->site_health->get_test_background_updates(); } @@ -182,6 +183,7 @@ class WP_REST_Site_Health_Controller extends WP_REST_Controller { * @return array */ public function test_dotorg_communication() { + $this->load_admin_textdomain(); return $this->site_health->get_test_dotorg_communication(); } @@ -193,6 +195,7 @@ class WP_REST_Site_Health_Controller extends WP_REST_Controller { * @return array */ public function test_loopback_requests() { + $this->load_admin_textdomain(); return $this->site_health->get_test_loopback_requests(); } @@ -204,6 +207,7 @@ class WP_REST_Site_Health_Controller extends WP_REST_Controller { * @return array */ public function test_authorization_header() { + $this->load_admin_textdomain(); return $this->site_health->get_test_authorization_header(); } @@ -219,6 +223,8 @@ class WP_REST_Site_Health_Controller extends WP_REST_Controller { require_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php'; } + $this->load_admin_textdomain(); + $sizes_data = WP_Debug_Data::get_sizes(); $all_sizes = array( 'raw' => 0 ); @@ -256,6 +262,22 @@ class WP_REST_Site_Health_Controller extends WP_REST_Controller { return $all_sizes; } + /** + * Loads the admin textdomain for Site Health tests. + * + * The {@see WP_Site_Health} class is defined in WP-Admin, while the REST API operates in a front-end context. + * This means that the translations for Site Health won't be loaded by default in {@see load_default_textdomain()}. + * + * @since 5.6.0 + */ + protected function load_admin_textdomain() { + // Accounts for inner REST API requests in the admin. + if ( ! is_admin() ) { + $locale = determine_locale(); + load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo" ); + } + } + /** * Gets the schema for each site health test. * diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index f3ad2de05c..a0da5a0c4d 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', 'wp-api-request' ), false, 1 ); + $scripts->add( 'site-health', "/wp-admin/js/site-health$suffix.js", array( 'clipboard', 'jquery', 'wp-util', 'wp-a11y', 'wp-api-request', 'wp-url' ), 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/tests/qunit/wp-includes/js/api-request.js b/tests/qunit/wp-includes/js/api-request.js index 86d0d72cda..aa7ee1dea7 100644 --- a/tests/qunit/wp-includes/js/api-request.js +++ b/tests/qunit/wp-includes/js/api-request.js @@ -2,7 +2,10 @@ ( function( QUnit ) { var originalRootUrl = window.wpApiSettings.root; - var nonceHeader = { 'X-WP-Nonce': 'not_a_real_nonce' }; + var expectedHeaders = { + 'X-WP-Nonce': 'not_a_real_nonce', + 'Accept': 'application/json, */*;q=0.1' + }; QUnit.module( 'wp-api-request', { afterEach: function() { @@ -32,6 +35,7 @@ url: 'http://localhost/wp-json/wp/v2/posts', headers: { 'X-WP-Nonce': 'not_a_real_nonce', + 'Accept': 'application/json, */*;q=0.1', 'Header-Name': 'value' }, data: { @@ -64,11 +68,15 @@ var settings = wp.apiRequest.buildAjaxOptions( settingsOriginal ); assert.notStrictEqual( settings, settingsOriginal ); - assert.strictEqual( settings.headers, settingsOriginal.headers ); + + var expected = { + Accept: 'application/json, */*;q=0.1' + }; + expected[ headerName ] = nonceHeader[ headerName ]; assert.deepEqual( settings, { url: 'aaaa', - headers: nonceHeader + headers: expected } ); } ); } ); @@ -87,20 +95,35 @@ assert.deepEqual( settings, { url: 'aaaa', - headers: {}, + headers: { + 'Accept': 'application/json, */*;q=0.1' + }, data: { _wpnonce: 'definitely_not_a_real_nonce' } } ); } ); + QUnit.test( 'does not add accept header if already present', function( assert ) { + var settingsOriginal = { + url: 'aaaa', + headers: { + 'Accept': 'text/xml' + } + }; + + var settings = wp.apiRequest.buildAjaxOptions( settingsOriginal ); + + assert.strictEqual( settingsOriginal.headers.Accept, settings.headers.Accept ); + } ); + QUnit.test( 'accepts namespace and endpoint', function( assert ) { assert.deepEqual( wp.apiRequest.buildAjaxOptions( { namespace: 'wp/v2', endpoint: 'posts' } ), { url: 'http://localhost/wp-json/wp/v2/posts', - headers: nonceHeader + headers: expectedHeaders } ); } ); @@ -110,7 +133,7 @@ endpoint: '/posts' } ), { url: 'http://localhost/wp-json/wp/v2/posts', - headers: nonceHeader + headers: expectedHeaders } ); } ); @@ -120,7 +143,7 @@ endpoint: '' } ), { url: 'http://localhost/wp-json/wp/v2', - headers: nonceHeader + headers: expectedHeaders } ); } ); @@ -130,7 +153,7 @@ endpoint: '' } ), { url: 'http://localhost/wp-json/', - headers: nonceHeader + headers: expectedHeaders } ); } ); @@ -143,7 +166,7 @@ endpoint: '/posts?orderby=title' } ), { url: 'http://localhost/index.php?rest_route=/wp/v2/posts&orderby=title', - headers: nonceHeader + headers: expectedHeaders } ); } ); @@ -157,7 +180,7 @@ endpoint: '' } ), { url: 'http://localhost/index.php?rest_route=/', - headers: nonceHeader + headers: expectedHeaders } ); } );