Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/wp-includes/formatting.php
Original file line number Diff line number Diff line change
Expand Up @@ -2466,6 +2466,11 @@ function sanitize_html_class( $classname, $fallback = '' ) {
* @return string The sanitized value.
*/
function sanitize_locale_name( $locale_name ) {
// Request values can arrive as arrays, and preg_replace() would map over them.
if ( ! is_string( $locale_name ) ) {
return '';
}

// Limit to A-Z, a-z, 0-9, '_', '-'.
$sanitized = preg_replace( '/[^A-Za-z0-9_-]/', '', $locale_name );

Expand Down
46 changes: 40 additions & 6 deletions src/wp-includes/l10n.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,19 @@ function get_locale() {
global $locale, $wp_local_package;

if ( isset( $locale ) ) {
// The global may be set by wp-config.php, by a plugin, or by an earlier call to this function.
if ( empty( $locale ) || ! is_string( $locale ) ) {
$locale = 'en_US';
}

/** This filter is documented in wp-includes/l10n.php */
return apply_filters( 'locale', $locale );
$filtered_locale = apply_filters( 'locale', $locale );

if ( empty( $filtered_locale ) || ! is_string( $filtered_locale ) ) {
return $locale;
}

return $filtered_locale;
}

if ( isset( $wp_local_package ) ) {
Expand Down Expand Up @@ -66,18 +77,27 @@ function get_locale() {
}
}

if ( empty( $locale ) ) {
// This value may come from a global, a constant or an option, none of which are validated.
if ( empty( $locale ) || ! is_string( $locale ) ) {
$locale = 'en_US';
}

/**
* Filters the locale ID of the WordPress installation.
*
* A non-string or empty value is ignored.
*
* @since 1.5.0
*
* @param string $locale The locale ID.
*/
return apply_filters( 'locale', $locale );
$filtered_locale = apply_filters( 'locale', $locale );

if ( empty( $filtered_locale ) || ! is_string( $filtered_locale ) ) {
return $locale;
}

return $filtered_locale;
}

/**
Expand Down Expand Up @@ -106,9 +126,14 @@ function get_user_locale( $user = 0 ) {
return get_locale();
}

// WP_User has no `locale` property: this reads the unvalidated `locale` user meta row.
$locale = $user_object->locale;

return $locale ? $locale : get_locale();
if ( empty( $locale ) || ! is_string( $locale ) ) {
return get_locale();
}

return $locale;
}

/**
Expand Down Expand Up @@ -162,18 +187,27 @@ function determine_locale() {
}
}

if ( ! $determined_locale ) {
// This value may come from user meta or from a global, and is not validated.
if ( empty( $determined_locale ) || ! is_string( $determined_locale ) ) {
$determined_locale = get_locale();
}

/**
* Filters the locale for the current request.
*
* A non-string or empty value is ignored.
*
* @since 5.0.0
*
* @param string $determined_locale The locale.
*/
return apply_filters( 'determine_locale', $determined_locale );
$filtered_locale = apply_filters( 'determine_locale', $determined_locale );

if ( empty( $filtered_locale ) || ! is_string( $filtered_locale ) ) {
return $determined_locale;
}

return $filtered_locale;
}

/**
Expand Down
9 changes: 9 additions & 0 deletions tests/phpunit/tests/formatting/sanitizeLocaleName.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,13 @@ public function data_sanitize_locale_name_returns_empty_string() {
array( '@///' ),
);
}

/**
* Request parameters can arrive as arrays.
*
* @ticket 66106
*/
public function test_sanitize_locale_name_returns_empty_string_for_a_non_string() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the other test methods have void:

Suggested change
public function test_sanitize_locale_name_returns_empty_string_for_a_non_string() {
public function test_sanitize_locale_name_returns_empty_string_for_a_non_string(): void {

$this->assertSame( '', sanitize_locale_name( array( 'de_DE' ) ) );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->assertSame( '', sanitize_locale_name( array( 'de_DE' ) ) );
$this->assertSame( '', sanitize_locale_name( array( 'de_DE' ) ) ); // @phpstan-ignore argument.type (Passing an array is intentional here.)

}
}
33 changes: 33 additions & 0 deletions tests/phpunit/tests/l10n/determineLocale.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,4 +308,37 @@ public function test_wp_local_package_global_installing() {
wp_installing( true );
$this->assertSame( 'de_DE', determine_locale() );
}

/**
* @ticket 66106
*/
public function test_wp_login_get_param_on_login_page_array(): void {
$GLOBALS['pagenow'] = 'wp-login.php';
$_GET['wp_lang'] = array( 'de_DE' );

$this->assertSame( 'en_US', determine_locale() );
}

/**
* An array locale reaches WP_Textdomain_Registry::set(), which uses it as
* an array key and throws a TypeError, so translating any string for an
* unloaded text domain ends the request.
*
* @ticket 66106
*/
public function test_array_wp_lang_param_does_not_fatal_in_the_textdomain_registry(): void {
$GLOBALS['pagenow'] = 'wp-login.php';
$_GET['wp_lang'] = array( 'de_DE' );

$this->assertSame( 'Some text', __( 'Some text', 'my-login-plugin' ) );
}

/**
* @ticket 66106
*/
public function test_ignores_a_non_string_determine_locale_filter(): void {
add_filter( 'determine_locale', '__return_empty_array' );

$this->assertSame( 'en_US', determine_locale() );
}
}
58 changes: 58 additions & 0 deletions tests/phpunit/tests/l10n/getLocale.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,62 @@ public function test_should_respect_get_locale_filter() {
public function filter_get_locale() {
return 'foo';
}

/**
* Nothing checks the type of the `WPLANG` option on the way out, so a row
* written by a direct database query or a migration reaches the return value.
*
* @ticket 66106
*
* @group ms-excluded

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The methods could add global docs:

Suggested change
* @group ms-excluded
* @group ms-excluded
*
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $locale The current locale.

*/
public function test_should_fall_back_on_en_US_for_a_non_string_option(): void {
global $locale, $wpdb;
$old_locale = $locale;
$locale = null;
Comment on lines +104 to +105

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be done in set_up. (And in the other test methods too.)


$wpdb->replace(
$wpdb->options,
array(
'option_name' => 'WPLANG',
'option_value' => maybe_serialize( array( 'de_DE' ) ),
)
);
wp_cache_flush();

$found = get_locale();
$locale = $old_locale;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be done in tear_down. (And in the other test methods too.)


$this->assertSame( 'en_US', $found );
}

/**
* @ticket 66106
*/
public function test_should_fall_back_on_en_US_for_a_non_string_locale_global(): void {
global $locale;
$old_locale = $locale;
$locale = array( 'de_DE' );

$found = get_locale();
$locale = $old_locale;

$this->assertSame( 'en_US', $found );
}

/**
* @ticket 66106
*/
public function test_should_ignore_a_non_string_locale_filter(): void {
global $locale;
$old_locale = $locale;
$locale = 'es_ES';

add_filter( 'locale', '__return_empty_array' );

$found = get_locale();
$locale = $old_locale;

$this->assertSame( 'es_ES', $found );
}
}
13 changes: 13 additions & 0 deletions tests/phpunit/tests/l10n/getUserLocale.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,17 @@ public function test_user_id_argument_with_invalid_type() {
$user_locale = get_user_locale( 'string' );
$this->assertSame( get_locale(), $user_locale );
}

/**
* A `locale` user meta row holding an array is truthy, so a truthiness
* check alone passes it through to callers that expect a string.
*
* @ticket 66106
*/
public function test_returns_site_locale_for_non_string_user_locale_meta(): void {
set_current_screen( 'dashboard' );
update_user_meta( self::$administrator_de_de, 'locale', array( 'de_DE' ) );

$this->assertSame( get_locale(), get_user_locale() );
}
}
Loading