-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
REST API: Add locale suggestion endpoint & helpers for plugins and th…
…emes (#609) * REST API: Add locale suggestion endpoint & helpers for plugins and themes * Fix spelling error * Make the alternate suggestion string more human-friendly * Switch to an abstract class & validation function * Add type to `debug` param, use built-in validation & sanitization * Remove the unnecessary param check
- Loading branch information
Showing
6 changed files
with
642 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
<?php | ||
/** | ||
* Set up some helper functions for fetching locale data. | ||
*/ | ||
|
||
namespace WordPressdotorg\MU_Plugins\Helpers\Locale; | ||
|
||
/** | ||
* Get all locales with subdomain mapping. | ||
*/ | ||
function get_all_locales_with_subdomain() { | ||
global $wpdb; | ||
return $wpdb->get_results( | ||
"SELECT locale, subdomain FROM wporg_locales WHERE locale NOT LIKE '%\_%\_%'", | ||
OBJECT_K | ||
); | ||
} | ||
|
||
/** | ||
* Get all available locales with valid WordPress locale values. | ||
* | ||
* Not all locales have valid WordPress sites, this filters out those that | ||
* don't exist. | ||
*/ | ||
function get_all_valid_locales() { | ||
$all_locales = get_all_locales_with_subdomain(); | ||
// Retrieve all the WordPress locales. | ||
$all_locales = wp_list_pluck( $all_locales, 'locale' ); | ||
|
||
return array_filter( | ||
$all_locales, | ||
function( $locale ) { | ||
return \GP_Locales::by_field( 'wp_locale', $locale ); | ||
} | ||
); | ||
} | ||
|
||
/** | ||
* Get locales matching the HTTP accept language header. | ||
* | ||
* @return array List of locales. | ||
*/ | ||
function get_locale_from_header() { | ||
$res = array(); | ||
|
||
$available_locales = get_all_valid_locales(); | ||
if ( ! $available_locales ) { | ||
return $res; | ||
} | ||
|
||
if ( ! isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) { | ||
return $res; | ||
} | ||
|
||
$http_locales = get_http_locales( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ); // phpcs:ignore | ||
|
||
if ( is_array( $http_locales ) ) { | ||
foreach ( $http_locales as $http_locale ) { | ||
$lang = $http_locale; | ||
$region = $http_locale; | ||
if ( str_contains( $http_locale, '-' ) ) { | ||
list( $lang, $region ) = explode( '-', $http_locale ); | ||
} | ||
|
||
/* | ||
* Discard English -- it's the default for all browsers, | ||
* ergo not very reliable information | ||
*/ | ||
if ( 'en' === $lang ) { | ||
continue; | ||
} | ||
|
||
// Region should be uppercase. | ||
$region = strtoupper( $region ); | ||
|
||
$mapped = map_locale( $lang, $region, $available_locales ); | ||
if ( $mapped ) { | ||
$res[] = $mapped; | ||
} | ||
} | ||
|
||
$res = array_unique( $res ); | ||
} | ||
|
||
return $res; | ||
} | ||
|
||
/** | ||
* Given a HTTP Accept-Language header $header | ||
* returns all the locales in it. | ||
* | ||
* @param string $header HTTP acccept header. | ||
* @return array Matched locales. | ||
*/ | ||
function get_http_locales( $header ) { | ||
$locale_part_re = '[a-z]{2,}'; | ||
$locale_re = "($locale_part_re(\-$locale_part_re)?)"; | ||
|
||
if ( preg_match_all( "/$locale_re/i", $header, $matches ) ) { | ||
return $matches[0]; | ||
} else { | ||
return []; | ||
} | ||
} | ||
|
||
/** | ||
* Tries to map a lang/region pair to one of our locales. | ||
* | ||
* @param string $lang Lang part of the HTTP accept header. | ||
* @param string $region Region part of the HTTP accept header. | ||
* @param array $available_locales List of available locales. | ||
* @return string|false Our locale matching $lang and $region, false otherwise. | ||
*/ | ||
function map_locale( $lang, $region, $available_locales ) { | ||
$uregion = strtoupper( $region ); | ||
$ulang = strtoupper( $lang ); | ||
$variants = array( | ||
"$lang-$region", | ||
"{$lang}_$region", | ||
"$lang-$uregion", | ||
"{$lang}_$uregion", | ||
"{$lang}_$ulang", | ||
$lang, | ||
); | ||
|
||
foreach ( $variants as $variant ) { | ||
if ( in_array( $variant, $available_locales ) ) { | ||
return $variant; | ||
} | ||
} | ||
|
||
foreach ( $available_locales as $locale ) { | ||
list( $locale_lang, ) = preg_split( '/[_-]/', $locale ); | ||
if ( $lang === $locale_lang ) { | ||
return $locale; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Get the active language packs for a package. | ||
* | ||
* @param string $type Package type. One of "theme", "plugin". | ||
* @param string $slug Slug of the requested item (e.g., `jetpack`, `twentynineteen`). | ||
* | ||
* @return array | ||
*/ | ||
function get_translated_locales( $type, $slug ) { | ||
global $wpdb; | ||
|
||
$language_packs = $wpdb->get_results( | ||
$wpdb->prepare( | ||
'SELECT * | ||
FROM language_packs | ||
WHERE | ||
type = %s AND | ||
domain = %s AND | ||
active = 1 | ||
GROUP BY language', | ||
$type, | ||
$slug | ||
) | ||
); | ||
|
||
// Retrieve all the WordPress locales in which the theme is translated. | ||
$translated_locales = wp_list_pluck( $language_packs, 'language' ); | ||
|
||
require_once GLOTPRESS_LOCALES_PATH; | ||
|
||
// Validate the list of locales can be found by `wp_locale`. | ||
return array_filter( | ||
$translated_locales, | ||
function( $locale ) { | ||
return \GP_Locales::by_field( 'wp_locale', $locale ); | ||
} | ||
); | ||
} |
66 changes: 66 additions & 0 deletions
66
mu-plugins/rest-api/endpoints/class-wporg-base-locale-banner-controller.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
|
||
namespace WordPressdotorg\MU_Plugins\REST_API; | ||
|
||
/** | ||
* Base_Locale_Banner_Controller | ||
*/ | ||
abstract class Base_Locale_Banner_Controller extends \WP_REST_Controller { | ||
/** | ||
* Register the endpoint routes used across both themes and plugins. | ||
* | ||
* @see register_rest_route() | ||
*/ | ||
public function register_routes() { | ||
register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base, | ||
array( | ||
'methods' => \WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_response' ), | ||
'args' => array( | ||
'debug' => array( | ||
'type' => 'boolean', | ||
), | ||
), | ||
'permission_callback' => '__return_true', | ||
) | ||
); | ||
register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base . '/(?P<slug>[^/]+)/', | ||
array( | ||
'methods' => \WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_response_for_item' ), | ||
'args' => array( | ||
'debug' => array( | ||
'type' => 'boolean', | ||
), | ||
'slug' => array( | ||
'validate_callback' => array( $this, 'check_slug' ), | ||
), | ||
), | ||
'permission_callback' => '__return_true', | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* Check if the given slug is a valid item. | ||
* | ||
* Must be defined in the child class. | ||
*/ | ||
abstract public function check_slug( $param ); | ||
|
||
/** | ||
* Send the response as plain text so it can be used as-is. | ||
*/ | ||
public function send_plain_text( $result ) { | ||
header( 'Content-Type: text/text' ); | ||
if ( $result ) { | ||
echo '<div>' . $result . '</div>'; // phpcs:ignore | ||
} | ||
|
||
return null; | ||
} | ||
} |
Oops, something went wrong.