Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/l10n.php (17 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Core Translation API
4
 *
5
 * @package WordPress
6
 * @subpackage i18n
7
 * @since 1.2.0
8
 */
9
10
/**
11
 * Retrieves the current locale.
12
 *
13
 * If the locale is set, then it will filter the locale in the {@see 'locale'}
14
 * filter hook and return the value.
15
 *
16
 * If the locale is not set already, then the WPLANG constant is used if it is
17
 * defined. Then it is filtered through the {@see 'locale'} filter hook and
18
 * the value for the locale global set and the locale is returned.
19
 *
20
 * The process to get the locale should only be done once, but the locale will
21
 * always be filtered using the {@see 'locale'} hook.
22
 *
23
 * @since 1.5.0
24
 *
25
 * @global string $locale
26
 * @global string $wp_local_package
27
 *
28
 * @return string The locale of the blog or from the {@see 'locale'} hook.
29
 */
30
function get_locale() {
31
	global $locale, $wp_local_package;
32
33
	if ( isset( $locale ) ) {
34
		/**
35
		 * Filters WordPress install's locale ID.
36
		 *
37
		 * @since 1.5.0
38
		 *
39
		 * @param string $locale The locale ID.
40
		 */
41
		return apply_filters( 'locale', $locale );
42
	}
43
44
	if ( isset( $wp_local_package ) ) {
45
		$locale = $wp_local_package;
46
	}
47
48
	// WPLANG was defined in wp-config.
49
	if ( defined( 'WPLANG' ) ) {
50
		$locale = WPLANG;
51
	}
52
53
	// If multisite, check options.
54
	if ( is_multisite() ) {
55
		// Don't check blog option when installing.
56
		if ( wp_installing() || ( false === $ms_locale = get_option( 'WPLANG' ) ) ) {
57
			$ms_locale = get_site_option( 'WPLANG' );
58
		}
59
60
		if ( $ms_locale !== false ) {
61
			$locale = $ms_locale;
62
		}
63
	} else {
64
		$db_locale = get_option( 'WPLANG' );
65
		if ( $db_locale !== false ) {
66
			$locale = $db_locale;
67
		}
68
	}
69
70
	if ( empty( $locale ) ) {
71
		$locale = 'en_US';
72
	}
73
74
	/** This filter is documented in wp-includes/l10n.php */
75
	return apply_filters( 'locale', $locale );
76
}
77
78
/**
79
 * Retrieves the locale of a user.
80
 *
81
 * If the user has a locale set to a non-empty string then it will be
82
 * returned. Otherwise it returns the locale of get_locale().
83
 *
84
 * @since 4.7.0
85
 *
86
 * @param int|WP_User $user_id User's ID or a WP_User object. Defaults to current user.
0 ignored issues
show
Consider making the type for parameter $user_id a bit more specific; maybe use integer.
Loading history...
87
 * @return string The locale of the user.
88
 */
89
function get_user_locale( $user_id = 0 ) {
90
	$user = false;
91
	if ( 0 === $user_id && function_exists( 'wp_get_current_user' ) ) {
92
		$user = wp_get_current_user();
93
	} elseif ( $user_id instanceof WP_User ) {
94
		$user = $user_id;
95
	} elseif ( $user_id && is_numeric( $user_id ) ) {
96
		$user = get_user_by( 'id', $user_id );
97
	}
98
99
	if ( ! $user ) {
100
		return get_locale();
101
	}
102
103
	$locale = $user->locale;
104
	return $locale ? $locale : get_locale();
105
}
106
107
/**
108
 * Retrieve the translation of $text.
109
 *
110
 * If there is no translation, or the text domain isn't loaded, the original text is returned.
111
 *
112
 * *Note:* Don't use translate() directly, use __() or related functions.
113
 *
114
 * @since 2.2.0
115
 *
116
 * @param string $text   Text to translate.
117
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
118
 *                       Default 'default'.
119
 * @return string Translated text
120
 */
121
function translate( $text, $domain = 'default' ) {
122
	$translations = get_translations_for_domain( $domain );
123
	$translation  = $translations->translate( $text );
124
125
	/**
126
	 * Filters text with its translation.
127
	 *
128
	 * @since 2.0.11
129
	 *
130
	 * @param string $translation  Translated text.
131
	 * @param string $text         Text to translate.
132
	 * @param string $domain       Text domain. Unique identifier for retrieving translated strings.
133
	 */
134
	return apply_filters( 'gettext', $translation, $text, $domain );
135
}
136
137
/**
138
 * Remove last item on a pipe-delimited string.
139
 *
140
 * Meant for removing the last item in a string, such as 'Role name|User role'. The original
141
 * string will be returned if no pipe '|' characters are found in the string.
142
 *
143
 * @since 2.8.0
144
 *
145
 * @param string $string A pipe-delimited string.
146
 * @return string Either $string or everything before the last pipe.
147
 */
148
function before_last_bar( $string ) {
149
	$last_bar = strrpos( $string, '|' );
150
	if ( false === $last_bar ) {
151
		return $string;
152
	} else {
153
		return substr( $string, 0, $last_bar );
154
	}
155
}
156
157
/**
158
 * Retrieve the translation of $text in the context defined in $context.
159
 *
160
 * If there is no translation, or the text domain isn't loaded the original
161
 * text is returned.
162
 *
163
 * *Note:* Don't use translate_with_gettext_context() directly, use _x() or related functions.
164
 *
165
 * @since 2.8.0
166
 *
167
 * @param string $text    Text to translate.
168
 * @param string $context Context information for the translators.
169
 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
170
 *                        Default 'default'.
171
 * @return string Translated text on success, original text on failure.
172
 */
173
function translate_with_gettext_context( $text, $context, $domain = 'default' ) {
174
	$translations = get_translations_for_domain( $domain );
175
	$translation  = $translations->translate( $text, $context );
176
	/**
177
	 * Filters text with its translation based on context information.
178
	 *
179
	 * @since 2.8.0
180
	 *
181
	 * @param string $translation  Translated text.
182
	 * @param string $text         Text to translate.
183
	 * @param string $context      Context information for the translators.
184
	 * @param string $domain       Text domain. Unique identifier for retrieving translated strings.
185
	 */
186
	return apply_filters( 'gettext_with_context', $translation, $text, $context, $domain );
187
}
188
189
/**
190
 * Retrieve the translation of $text.
191
 *
192
 * If there is no translation, or the text domain isn't loaded, the original text is returned.
193
 *
194
 * @since 2.1.0
195
 *
196
 * @param string $text   Text to translate.
197
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
198
 *                       Default 'default'.
199
 * @return string Translated text.
200
 */
201
function __( $text, $domain = 'default' ) {
0 ignored issues
show
This method's name is shorter than the configured minimum length of 3 characters.

Even though PHP does not care about the name of your methods, it is generally a good practice to choose method names which can be easily understood by other human readers.

Loading history...
202
	return translate( $text, $domain );
203
}
204
205
/**
206
 * Retrieve the translation of $text and escapes it for safe use in an attribute.
207
 *
208
 * If there is no translation, or the text domain isn't loaded, the original text is returned.
209
 *
210
 * @since 2.8.0
211
 *
212
 * @param string $text   Text to translate.
213
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
214
 *                       Default 'default'.
215
 * @return string Translated text on success, original text on failure.
216
 */
217
function esc_attr__( $text, $domain = 'default' ) {
218
	return esc_attr( translate( $text, $domain ) );
219
}
220
221
/**
222
 * Retrieve the translation of $text and escapes it for safe use in HTML output.
223
 *
224
 * If there is no translation, or the text domain isn't loaded, the original text is returned.
225
 *
226
 * @since 2.8.0
227
 *
228
 * @param string $text   Text to translate.
229
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
230
 *                       Default 'default'.
231
 * @return string Translated text
232
 */
233
function esc_html__( $text, $domain = 'default' ) {
234
	return esc_html( translate( $text, $domain ) );
235
}
236
237
/**
238
 * Display translated text.
239
 *
240
 * @since 1.2.0
241
 *
242
 * @param string $text   Text to translate.
243
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
244
 *                       Default 'default'.
245
 */
246
function _e( $text, $domain = 'default' ) {
0 ignored issues
show
This method's name is shorter than the configured minimum length of 3 characters.

Even though PHP does not care about the name of your methods, it is generally a good practice to choose method names which can be easily understood by other human readers.

Loading history...
247
	echo translate( $text, $domain );
248
}
249
250
/**
251
 * Display translated text that has been escaped for safe use in an attribute.
252
 *
253
 * @since 2.8.0
254
 *
255
 * @param string $text   Text to translate.
256
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
257
 *                       Default 'default'.
258
 */
259
function esc_attr_e( $text, $domain = 'default' ) {
260
	echo esc_attr( translate( $text, $domain ) );
261
}
262
263
/**
264
 * Display translated text that has been escaped for safe use in HTML output.
265
 *
266
 * @since 2.8.0
267
 *
268
 * @param string $text   Text to translate.
269
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
270
 *                       Default 'default'.
271
 */
272
function esc_html_e( $text, $domain = 'default' ) {
273
	echo esc_html( translate( $text, $domain ) );
274
}
275
276
/**
277
 * Retrieve translated string with gettext context.
278
 *
279
 * Quite a few times, there will be collisions with similar translatable text
280
 * found in more than two places, but with different translated context.
281
 *
282
 * By including the context in the pot file, translators can translate the two
283
 * strings differently.
284
 *
285
 * @since 2.8.0
286
 *
287
 * @param string $text    Text to translate.
288
 * @param string $context Context information for the translators.
289
 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
290
 *                        Default 'default'.
291
 * @return string Translated context string without pipe.
292
 */
293
function _x( $text, $context, $domain = 'default' ) {
0 ignored issues
show
This method's name is shorter than the configured minimum length of 3 characters.

Even though PHP does not care about the name of your methods, it is generally a good practice to choose method names which can be easily understood by other human readers.

Loading history...
294
	return translate_with_gettext_context( $text, $context, $domain );
295
}
296
297
/**
298
 * Display translated string with gettext context.
299
 *
300
 * @since 3.0.0
301
 *
302
 * @param string $text    Text to translate.
303
 * @param string $context Context information for the translators.
304
 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
305
 *                        Default 'default'.
306
 * @return string Translated context string without pipe.
0 ignored issues
show
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
307
 */
308
function _ex( $text, $context, $domain = 'default' ) {
309
	echo _x( $text, $context, $domain );
310
}
311
312
/**
313
 * Translate string with gettext context, and escapes it for safe use in an attribute.
314
 *
315
 * @since 2.8.0
316
 *
317
 * @param string $text    Text to translate.
318
 * @param string $context Context information for the translators.
319
 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
320
 *                        Default 'default'.
321
 * @return string Translated text
322
 */
323
function esc_attr_x( $text, $context, $domain = 'default' ) {
324
	return esc_attr( translate_with_gettext_context( $text, $context, $domain ) );
325
}
326
327
/**
328
 * Translate string with gettext context, and escapes it for safe use in HTML output.
329
 *
330
 * @since 2.9.0
331
 *
332
 * @param string $text    Text to translate.
333
 * @param string $context Context information for the translators.
334
 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
335
 *                        Default 'default'.
336
 * @return string Translated text.
337
 */
338
function esc_html_x( $text, $context, $domain = 'default' ) {
339
	return esc_html( translate_with_gettext_context( $text, $context, $domain ) );
340
}
341
342
/**
343
 * Translates and retrieves the singular or plural form based on the supplied number.
344
 *
345
 * Used when you want to use the appropriate form of a string based on whether a
346
 * number is singular or plural.
347
 *
348
 * Example:
349
 *
350
 *     printf( _n( '%s person', '%s people', $count, 'text-domain' ), number_format_i18n( $count ) );
351
 *
352
 * @since 2.8.0
353
 *
354
 * @param string $single The text to be used if the number is singular.
355
 * @param string $plural The text to be used if the number is plural.
356
 * @param int    $number The number to compare against to use either the singular or plural form.
357
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
358
 *                       Default 'default'.
359
 * @return string The translated singular or plural form.
360
 */
361 View Code Duplication
function _n( $single, $plural, $number, $domain = 'default' ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
This method's name is shorter than the configured minimum length of 3 characters.

Even though PHP does not care about the name of your methods, it is generally a good practice to choose method names which can be easily understood by other human readers.

Loading history...
362
	$translations = get_translations_for_domain( $domain );
363
	$translation  = $translations->translate_plural( $single, $plural, $number );
364
365
	/**
366
	 * Filters the singular or plural form of a string.
367
	 *
368
	 * @since 2.2.0
369
	 *
370
	 * @param string $translation Translated text.
371
	 * @param string $single      The text to be used if the number is singular.
372
	 * @param string $plural      The text to be used if the number is plural.
373
	 * @param string $number      The number to compare against to use either the singular or plural form.
374
	 * @param string $domain      Text domain. Unique identifier for retrieving translated strings.
375
	 */
376
	return apply_filters( 'ngettext', $translation, $single, $plural, $number, $domain );
377
}
378
379
/**
380
 * Translates and retrieves the singular or plural form based on the supplied number, with gettext context.
381
 *
382
 * This is a hybrid of _n() and _x(). It supports context and plurals.
383
 *
384
 * Used when you want to use the appropriate form of a string with context based on whether a
385
 * number is singular or plural.
386
 *
387
 * Example of a generic phrase which is disambiguated via the context parameter:
388
 *
389
 *     printf( _nx( '%s group', '%s groups', $people, 'group of people', 'text-domain' ), number_format_i18n( $people ) );
390
 *     printf( _nx( '%s group', '%s groups', $animals, 'group of animals', 'text-domain' ), number_format_i18n( $animals ) );
391
 *
392
 * @since 2.8.0
393
 *
394
 * @param string $single  The text to be used if the number is singular.
395
 * @param string $plural  The text to be used if the number is plural.
396
 * @param int    $number  The number to compare against to use either the singular or plural form.
397
 * @param string $context Context information for the translators.
398
 * @param string $domain  Optional. Text domain. Unique identifier for retrieving translated strings.
399
 *                        Default 'default'.
400
 * @return string The translated singular or plural form.
401
 */
402 View Code Duplication
function _nx($single, $plural, $number, $context, $domain = 'default') {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
403
	$translations = get_translations_for_domain( $domain );
404
	$translation  = $translations->translate_plural( $single, $plural, $number, $context );
405
406
	/**
407
	 * Filters the singular or plural form of a string with gettext context.
408
	 *
409
	 * @since 2.8.0
410
	 *
411
	 * @param string $translation Translated text.
412
	 * @param string $single      The text to be used if the number is singular.
413
	 * @param string $plural      The text to be used if the number is plural.
414
	 * @param string $number      The number to compare against to use either the singular or plural form.
415
	 * @param string $context     Context information for the translators.
416
	 * @param string $domain      Text domain. Unique identifier for retrieving translated strings.
417
	 */
418
	return apply_filters( 'ngettext_with_context', $translation, $single, $plural, $number, $context, $domain );
419
}
420
421
/**
422
 * Registers plural strings in POT file, but does not translate them.
423
 *
424
 * Used when you want to keep structures with translatable plural
425
 * strings and use them later when the number is known.
426
 *
427
 * Example:
428
 *
429
 *     $message = _n_noop( '%s post', '%s posts', 'text-domain' );
430
 *     ...
431
 *     printf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) );
432
 *
433
 * @since 2.5.0
434
 *
435
 * @param string $singular Singular form to be localized.
436
 * @param string $plural   Plural form to be localized.
437
 * @param string $domain   Optional. Text domain. Unique identifier for retrieving translated strings.
438
 *                         Default null.
439
 * @return array {
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<*,string|null>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
440
 *     Array of translation information for the strings.
441
 *
442
 *     @type string $0        Singular form to be localized. No longer used.
443
 *     @type string $1        Plural form to be localized. No longer used.
444
 *     @type string $singular Singular form to be localized.
445
 *     @type string $plural   Plural form to be localized.
446
 *     @type null   $context  Context information for the translators.
447
 *     @type string $domain   Text domain.
448
 * }
449
 */
450
function _n_noop( $singular, $plural, $domain = null ) {
451
	return array( 0 => $singular, 1 => $plural, 'singular' => $singular, 'plural' => $plural, 'context' => null, 'domain' => $domain );
452
}
453
454
/**
455
 * Registers plural strings with gettext context in POT file, but does not translate them.
456
 *
457
 * Used when you want to keep structures with translatable plural
458
 * strings and use them later when the number is known.
459
 *
460
 * Example of a generic phrase which is disambiguated via the context parameter:
461
 *
462
 *     $messages = array(
463
 *      	'people'  => _nx_noop( '%s group', '%s groups', 'people', 'text-domain' ),
464
 *      	'animals' => _nx_noop( '%s group', '%s groups', 'animals', 'text-domain' ),
465
 *     );
466
 *     ...
467
 *     $message = $messages[ $type ];
468
 *     printf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) );
469
 *
470
 * @since 2.8.0
471
 *
472
 * @param string $singular Singular form to be localized.
473
 * @param string $plural   Plural form to be localized.
474
 * @param string $context  Context information for the translators.
475
 * @param string $domain   Optional. Text domain. Unique identifier for retrieving translated strings.
476
 *                         Default null.
477
 * @return array {
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<*,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
478
 *     Array of translation information for the strings.
479
 *
480
 *     @type string $0        Singular form to be localized. No longer used.
481
 *     @type string $1        Plural form to be localized. No longer used.
482
 *     @type string $2        Context information for the translators. No longer used.
483
 *     @type string $singular Singular form to be localized.
484
 *     @type string $plural   Plural form to be localized.
485
 *     @type string $context  Context information for the translators.
486
 *     @type string $domain   Text domain.
487
 * }
488
 */
489
function _nx_noop( $singular, $plural, $context, $domain = null ) {
490
	return array( 0 => $singular, 1 => $plural, 2 => $context, 'singular' => $singular, 'plural' => $plural, 'context' => $context, 'domain' => $domain );
491
}
492
493
/**
494
 * Translates and retrieves the singular or plural form of a string that's been registered
495
 * with _n_noop() or _nx_noop().
496
 *
497
 * Used when you want to use a translatable plural string once the number is known.
498
 *
499
 * Example:
500
 *
501
 *     $message = _n_noop( '%s post', '%s posts', 'text-domain' );
502
 *     ...
503
 *     printf( translate_nooped_plural( $message, $count, 'text-domain' ), number_format_i18n( $count ) );
504
 *
505
 * @since 3.1.0
506
 *
507
 * @param array  $nooped_plural Array with singular, plural, and context keys, usually the result of _n_noop() or _nx_noop().
508
 * @param int    $count         Number of objects.
509
 * @param string $domain        Optional. Text domain. Unique identifier for retrieving translated strings. If $nooped_plural contains
510
 *                              a text domain passed to _n_noop() or _nx_noop(), it will override this value. Default 'default'.
511
 * @return string Either $single or $plural translated text.
512
 */
513
function translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) {
514
	if ( $nooped_plural['domain'] )
515
		$domain = $nooped_plural['domain'];
516
517
	if ( $nooped_plural['context'] )
518
		return _nx( $nooped_plural['singular'], $nooped_plural['plural'], $count, $nooped_plural['context'], $domain );
519
	else
520
		return _n( $nooped_plural['singular'], $nooped_plural['plural'], $count, $domain );
521
}
522
523
/**
524
 * Load a .mo file into the text domain $domain.
525
 *
526
 * If the text domain already exists, the translations will be merged. If both
527
 * sets have the same string, the translation from the original value will be taken.
528
 *
529
 * On success, the .mo file will be placed in the $l10n global by $domain
530
 * and will be a MO object.
531
 *
532
 * @since 1.5.0
533
 *
534
 * @global array $l10n          An array of all currently loaded text domains.
535
 * @global array $l10n_unloaded An array of all text domains that have been unloaded again.
536
 *
537
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
538
 * @param string $mofile Path to the .mo file.
539
 * @return bool True on success, false on failure.
540
 */
541
function load_textdomain( $domain, $mofile ) {
542
	global $l10n, $l10n_unloaded;
543
544
	$l10n_unloaded = (array) $l10n_unloaded;
545
546
	/**
547
	 * Filters whether to override the .mo file loading.
548
	 *
549
	 * @since 2.9.0
550
	 *
551
	 * @param bool   $override Whether to override the .mo file loading. Default false.
552
	 * @param string $domain   Text domain. Unique identifier for retrieving translated strings.
553
	 * @param string $mofile   Path to the MO file.
554
	 */
555
	$plugin_override = apply_filters( 'override_load_textdomain', false, $domain, $mofile );
556
557
	if ( true == $plugin_override ) {
558
		unset( $l10n_unloaded[ $domain ] );
559
560
		return true;
561
	}
562
563
	/**
564
	 * Fires before the MO translation file is loaded.
565
	 *
566
	 * @since 2.9.0
567
	 *
568
	 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
569
	 * @param string $mofile Path to the .mo file.
570
	 */
571
	do_action( 'load_textdomain', $domain, $mofile );
572
573
	/**
574
	 * Filters MO file path for loading translations for a specific text domain.
575
	 *
576
	 * @since 2.9.0
577
	 *
578
	 * @param string $mofile Path to the MO file.
579
	 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
580
	 */
581
	$mofile = apply_filters( 'load_textdomain_mofile', $mofile, $domain );
582
583
	if ( !is_readable( $mofile ) ) return false;
584
585
	$mo = new MO();
586
	if ( !$mo->import_from_file( $mofile ) ) return false;
587
588
	if ( isset( $l10n[$domain] ) )
589
		$mo->merge_with( $l10n[$domain] );
590
591
	unset( $l10n_unloaded[ $domain ] );
592
593
	$l10n[$domain] = &$mo;
594
595
	return true;
596
}
597
598
/**
599
 * Unload translations for a text domain.
600
 *
601
 * @since 3.0.0
602
 *
603
 * @global array $l10n          An array of all currently loaded text domains.
604
 * @global array $l10n_unloaded An array of all text domains that have been unloaded again.
605
 *
606
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
607
 * @return bool Whether textdomain was unloaded.
608
 */
609
function unload_textdomain( $domain ) {
610
	global $l10n, $l10n_unloaded;
611
612
	$l10n_unloaded = (array) $l10n_unloaded;
613
614
	/**
615
	 * Filters whether to override the text domain unloading.
616
	 *
617
	 * @since 3.0.0
618
	 *
619
	 * @param bool   $override Whether to override the text domain unloading. Default false.
620
	 * @param string $domain   Text domain. Unique identifier for retrieving translated strings.
621
	 */
622
	$plugin_override = apply_filters( 'override_unload_textdomain', false, $domain );
623
624
	if ( $plugin_override ) {
625
		$l10n_unloaded[ $domain ] = true;
626
627
		return true;
628
	}
629
630
	/**
631
	 * Fires before the text domain is unloaded.
632
	 *
633
	 * @since 3.0.0
634
	 *
635
	 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
636
	 */
637
	do_action( 'unload_textdomain', $domain );
638
639
	if ( isset( $l10n[$domain] ) ) {
640
		unset( $l10n[$domain] );
641
642
		$l10n_unloaded[ $domain ] = true;
643
644
		return true;
645
	}
646
647
	return false;
648
}
649
650
/**
651
 * Load default translated strings based on locale.
652
 *
653
 * Loads the .mo file in WP_LANG_DIR constant path from WordPress root.
654
 * The translated (.mo) file is named based on the locale.
655
 *
656
 * @see load_textdomain()
657
 *
658
 * @since 1.5.0
659
 *
660
 * @param string $locale Optional. Locale to load. Default is the value of get_locale().
661
 * @return bool Whether the textdomain was loaded.
662
 */
663
function load_default_textdomain( $locale = null ) {
664
	if ( null === $locale ) {
665
		$locale = is_admin() ? get_user_locale() : get_locale();
666
	}
667
668
	// Unload previously loaded strings so we can switch translations.
669
	unload_textdomain( 'default' );
670
671
	$return = load_textdomain( 'default', WP_LANG_DIR . "/$locale.mo" );
672
673
	if ( ( is_multisite() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) ) && ! file_exists(  WP_LANG_DIR . "/admin-$locale.mo" ) ) {
674
		load_textdomain( 'default', WP_LANG_DIR . "/ms-$locale.mo" );
675
		return $return;
676
	}
677
678
	if ( is_admin() || wp_installing() || ( defined( 'WP_REPAIRING' ) && WP_REPAIRING ) ) {
679
		load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo" );
680
	}
681
682
	if ( is_network_admin() || ( defined( 'WP_INSTALLING_NETWORK' ) && WP_INSTALLING_NETWORK ) )
683
		load_textdomain( 'default', WP_LANG_DIR . "/admin-network-$locale.mo" );
684
685
	return $return;
686
}
687
688
/**
689
 * Loads a plugin's translated strings.
690
 *
691
 * If the path is not given then it will be the root of the plugin directory.
692
 *
693
 * The .mo file should be named based on the text domain with a dash, and then the locale exactly.
694
 *
695
 * @since 1.5.0
696
 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
697
 *
698
 * @param string $domain          Unique identifier for retrieving translated strings
699
 * @param string $deprecated      Optional. Use the $plugin_rel_path parameter instead. Default false.
700
 * @param string $plugin_rel_path Optional. Relative path to WP_PLUGIN_DIR where the .mo file resides.
701
 *                                Default false.
702
 * @return bool True when textdomain is successfully loaded, false otherwise.
703
 */
704
function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) {
705
	/**
706
	 * Filters a plugin's locale.
707
	 *
708
	 * @since 3.0.0
709
	 *
710
	 * @param string $locale The plugin's current locale.
711
	 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
712
	 */
713
	$locale = apply_filters( 'plugin_locale', is_admin() ? get_user_locale() : get_locale(), $domain );
714
715
	$mofile = $domain . '-' . $locale . '.mo';
716
717
	// Try to load from the languages directory first.
718
	if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) {
719
		return true;
720
	}
721
722
	if ( false !== $plugin_rel_path ) {
723
		$path = WP_PLUGIN_DIR . '/' . trim( $plugin_rel_path, '/' );
724
	} elseif ( false !== $deprecated ) {
725
		_deprecated_argument( __FUNCTION__, '2.7.0' );
726
		$path = ABSPATH . trim( $deprecated, '/' );
727
	} else {
728
		$path = WP_PLUGIN_DIR;
729
	}
730
731
	return load_textdomain( $domain, $path . '/' . $mofile );
732
}
733
734
/**
735
 * Load the translated strings for a plugin residing in the mu-plugins directory.
736
 *
737
 * @since 3.0.0
738
 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
739
 *
740
 * @param string $domain             Text domain. Unique identifier for retrieving translated strings.
741
 * @param string $mu_plugin_rel_path Optional. Relative to `WPMU_PLUGIN_DIR` directory in which the .mo
742
 *                                   file resides. Default empty string.
743
 * @return bool True when textdomain is successfully loaded, false otherwise.
744
 */
745 View Code Duplication
function load_muplugin_textdomain( $domain, $mu_plugin_rel_path = '' ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
746
	/** This filter is documented in wp-includes/l10n.php */
747
	$locale = apply_filters( 'plugin_locale', is_admin() ? get_user_locale() : get_locale(), $domain );
748
749
	$mofile = $domain . '-' . $locale . '.mo';
750
751
	// Try to load from the languages directory first.
752
	if ( load_textdomain( $domain, WP_LANG_DIR . '/plugins/' . $mofile ) ) {
753
		return true;
754
	}
755
756
	$path = WPMU_PLUGIN_DIR . '/' . ltrim( $mu_plugin_rel_path, '/' );
757
758
	return load_textdomain( $domain, $path . '/' . $mofile );
759
}
760
761
/**
762
 * Load the theme's translated strings.
763
 *
764
 * If the current locale exists as a .mo file in the theme's root directory, it
765
 * will be included in the translated strings by the $domain.
766
 *
767
 * The .mo files must be named based on the locale exactly.
768
 *
769
 * @since 1.5.0
770
 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first.
771
 *
772
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
773
 * @param string $path   Optional. Path to the directory containing the .mo file.
774
 *                       Default false.
775
 * @return bool True when textdomain is successfully loaded, false otherwise.
776
 */
777 View Code Duplication
function load_theme_textdomain( $domain, $path = false ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
778
	/**
779
	 * Filters a theme's locale.
780
	 *
781
	 * @since 3.0.0
782
	 *
783
	 * @param string $locale The theme's current locale.
784
	 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
785
	 */
786
	$locale = apply_filters( 'theme_locale', is_admin() ? get_user_locale() : get_locale(), $domain );
787
788
	$mofile = $domain . '-' . $locale . '.mo';
789
790
	// Try to load from the languages directory first.
791
	if ( load_textdomain( $domain, WP_LANG_DIR . '/themes/' . $mofile ) ) {
792
		return true;
793
	}
794
795
	if ( ! $path ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
796
		$path = get_template_directory();
797
	}
798
799
	return load_textdomain( $domain, $path . '/' . $locale . '.mo' );
800
}
801
802
/**
803
 * Load the child themes translated strings.
804
 *
805
 * If the current locale exists as a .mo file in the child themes
806
 * root directory, it will be included in the translated strings by the $domain.
807
 *
808
 * The .mo files must be named based on the locale exactly.
809
 *
810
 * @since 2.9.0
811
 *
812
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
813
 * @param string $path   Optional. Path to the directory containing the .mo file.
814
 *                       Default false.
815
 * @return bool True when the theme textdomain is successfully loaded, false otherwise.
816
 */
817
function load_child_theme_textdomain( $domain, $path = false ) {
818
	if ( ! $path )
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
819
		$path = get_stylesheet_directory();
820
	return load_theme_textdomain( $domain, $path );
821
}
822
823
/**
824
 * Loads plugin and theme textdomains just-in-time.
825
 *
826
 * When a textdomain is encountered for the first time, we try to load
827
 * the translation file from `wp-content/languages`, removing the need
828
 * to call load_plugin_texdomain() or load_theme_texdomain().
829
 *
830
 * @since 4.6.0
831
 * @access private
832
 *
833
 * @see get_translations_for_domain()
834
 * @global array $l10n_unloaded An array of all text domains that have been unloaded again.
835
 *
836
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
837
 * @return bool True when the textdomain is successfully loaded, false otherwise.
838
 */
839
function _load_textdomain_just_in_time( $domain ) {
840
	global $l10n_unloaded;
841
842
	$l10n_unloaded = (array) $l10n_unloaded;
843
844
	// Short-circuit if domain is 'default' which is reserved for core.
845
	if ( 'default' === $domain || isset( $l10n_unloaded[ $domain ] ) ) {
846
		return false;
847
	}
848
849
	$translation_path = _get_path_to_translation( $domain );
850
	if ( false === $translation_path ) {
851
		return false;
852
	}
853
854
	return load_textdomain( $domain, $translation_path );
855
}
856
857
/**
858
 * Gets the path to a translation file for loading a textdomain just in time.
859
 *
860
 * Caches the retrieved results internally.
861
 *
862
 * @since 4.7.0
863
 * @access private
864
 *
865
 * @see _load_textdomain_just_in_time()
866
 *
867
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
868
 * @param bool   $reset  Whether to reset the internal cache. Used by the switch to locale functionality.
869
 * @return string|false The path to the translation file or false if no translation file was found.
870
 */
871
function _get_path_to_translation( $domain, $reset = false ) {
872
	static $available_translations = array();
873
874
	if ( true === $reset ) {
875
		$available_translations = array();
876
	}
877
878
	if ( ! isset( $available_translations[ $domain ] ) ) {
879
		$available_translations[ $domain ] = _get_path_to_translation_from_lang_dir( $domain );
880
	}
881
882
	return $available_translations[ $domain ];
883
}
884
885
/**
886
 * Gets the path to a translation file in the languages directory for the current locale.
887
 *
888
 * Holds a cached list of available .mo files to improve performance.
889
 *
890
 * @since 4.7.0
891
 * @access private
892
 *
893
 * @see _get_path_to_translation()
894
 *
895
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
896
 * @return string|false The path to the translation file or false if no translation file was found.
897
 */
898
function _get_path_to_translation_from_lang_dir( $domain ) {
899
	static $cached_mofiles = null;
900
901
	if ( null === $cached_mofiles ) {
902
		$cached_mofiles = array();
903
904
		$locations = array(
905
			WP_LANG_DIR . '/plugins',
906
			WP_LANG_DIR . '/themes',
907
		);
908
909
		foreach ( $locations as $location ) {
910
			$mofiles = glob( $location . '/*.mo' );
911
			if ( $mofiles ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mofiles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
912
				$cached_mofiles = array_merge( $cached_mofiles, $mofiles );
913
			}
914
		}
915
	}
916
917
	$locale = is_admin() ? get_user_locale() : get_locale();
918
	$mofile = "{$domain}-{$locale}.mo";
919
920
	$path = WP_LANG_DIR . '/plugins/' . $mofile;
921
	if ( in_array( $path, $cached_mofiles ) ) {
922
		return $path;
923
	}
924
925
	$path = WP_LANG_DIR . '/themes/' . $mofile;
926
	if ( in_array( $path, $cached_mofiles ) ) {
927
		return $path;
928
	}
929
930
	return false;
931
}
932
933
/**
934
 * Return the Translations instance for a text domain.
935
 *
936
 * If there isn't one, returns empty Translations instance.
937
 *
938
 * @since 2.8.0
939
 *
940
 * @global array $l10n
941
 *
942
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
943
 * @return Translations|NOOP_Translations A Translations instance.
944
 */
945
function get_translations_for_domain( $domain ) {
946
	global $l10n;
947
	if ( isset( $l10n[ $domain ] ) || ( _load_textdomain_just_in_time( $domain ) && isset( $l10n[ $domain ] ) ) ) {
948
		return $l10n[ $domain ];
949
	}
950
951
	static $noop_translations = null;
952
	if ( null === $noop_translations ) {
953
		$noop_translations = new NOOP_Translations;
954
	}
955
956
	return $noop_translations;
957
}
958
959
/**
960
 * Whether there are translations for the text domain.
961
 *
962
 * @since 3.0.0
963
 *
964
 * @global array $l10n
965
 *
966
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
967
 * @return bool Whether there are translations.
968
 */
969
function is_textdomain_loaded( $domain ) {
970
	global $l10n;
971
	return isset( $l10n[ $domain ] );
972
}
973
974
/**
975
 * Translates role name.
976
 *
977
 * Since the role names are in the database and not in the source there
978
 * are dummy gettext calls to get them into the POT file and this function
979
 * properly translates them back.
980
 *
981
 * The before_last_bar() call is needed, because older installs keep the roles
982
 * using the old context format: 'Role name|User role' and just skipping the
983
 * content after the last bar is easier than fixing them in the DB. New installs
984
 * won't suffer from that problem.
985
 *
986
 * @since 2.8.0
987
 *
988
 * @param string $name The role name.
989
 * @return string Translated role name on success, original name on failure.
990
 */
991
function translate_user_role( $name ) {
992
	return translate_with_gettext_context( before_last_bar($name), 'User role' );
993
}
994
995
/**
996
 * Get all available languages based on the presence of *.mo files in a given directory.
997
 *
998
 * The default directory is WP_LANG_DIR.
999
 *
1000
 * @since 3.0.0
1001
 * @since 4.7.0 The results are now filterable with the {@see 'get_available_languages'} filter.
1002
 *
1003
 * @param string $dir A directory to search for language files.
1004
 *                    Default WP_LANG_DIR.
1005
 * @return array An array of language codes or an empty array if no languages are present. Language codes are formed by stripping the .mo extension from the language file names.
1006
 */
1007
function get_available_languages( $dir = null ) {
1008
	$languages = array();
1009
1010
	$lang_files = glob( ( is_null( $dir ) ? WP_LANG_DIR : $dir ) . '/*.mo' );
1011
	if ( $lang_files ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang_files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1012
		foreach ( $lang_files as $lang_file ) {
1013
			$lang_file = basename( $lang_file, '.mo' );
1014
			if ( 0 !== strpos( $lang_file, 'continents-cities' ) && 0 !== strpos( $lang_file, 'ms-' ) &&
1015
				0 !== strpos( $lang_file, 'admin-' ) ) {
1016
				$languages[] = $lang_file;
1017
			}
1018
		}
1019
	}
1020
1021
	/**
1022
	 * Filters the list of available language codes.
1023
	 *
1024
	 * @since 4.7.0
1025
	 *
1026
	 * @param array  $languages An array of available language codes.
1027
	 * @param string $dir       The directory where the language files were found.
1028
	 */
1029
	return apply_filters( 'get_available_languages', $languages, $dir );
1030
}
1031
1032
/**
1033
 * Get installed translations.
1034
 *
1035
 * Looks in the wp-content/languages directory for translations of
1036
 * plugins or themes.
1037
 *
1038
 * @since 3.7.0
1039
 *
1040
 * @param string $type What to search for. Accepts 'plugins', 'themes', 'core'.
1041
 * @return array Array of language data.
1042
 */
1043
function wp_get_installed_translations( $type ) {
1044
	if ( $type !== 'themes' && $type !== 'plugins' && $type !== 'core' )
1045
		return array();
1046
1047
	$dir = 'core' === $type ? '' : "/$type";
1048
1049
	if ( ! is_dir( WP_LANG_DIR ) )
1050
		return array();
1051
1052
	if ( $dir && ! is_dir( WP_LANG_DIR . $dir ) )
1053
		return array();
1054
1055
	$files = scandir( WP_LANG_DIR . $dir );
1056
	if ( ! $files )
0 ignored issues
show
Bug Best Practice introduced by
The expression $files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1057
		return array();
1058
1059
	$language_data = array();
1060
1061
	foreach ( $files as $file ) {
1062
		if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "$dir/$file" ) ) {
1063
			continue;
1064
		}
1065
		if ( substr( $file, -3 ) !== '.po' ) {
1066
			continue;
1067
		}
1068
		if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
1069
			continue;
1070
		}
1071
		if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) )  {
1072
			continue;
1073
		}
1074
1075
		list( , $textdomain, $language ) = $match;
1076
		if ( '' === $textdomain ) {
1077
			$textdomain = 'default';
1078
		}
1079
		$language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "$dir/$file" );
1080
	}
1081
	return $language_data;
1082
}
1083
1084
/**
1085
 * Extract headers from a PO file.
1086
 *
1087
 * @since 3.7.0
1088
 *
1089
 * @param string $po_file Path to PO file.
1090
 * @return array PO file headers.
1091
 */
1092
function wp_get_pomo_file_data( $po_file ) {
1093
	$headers = get_file_data( $po_file, array(
1094
		'POT-Creation-Date'  => '"POT-Creation-Date',
1095
		'PO-Revision-Date'   => '"PO-Revision-Date',
1096
		'Project-Id-Version' => '"Project-Id-Version',
1097
		'X-Generator'        => '"X-Generator',
1098
	) );
1099
	foreach ( $headers as $header => $value ) {
1100
		// Remove possible contextual '\n' and closing double quote.
1101
		$headers[ $header ] = preg_replace( '~(\\\n)?"$~', '', $value );
1102
	}
1103
	return $headers;
1104
}
1105
1106
/**
1107
 * Language selector.
1108
 *
1109
 * @since 4.0.0
1110
 * @since 4.3.0 Introduced the `echo` argument.
1111
 * @since 4.7.0 Introduced the `show_option_site_default` argument.
1112
 *
1113
 * @see get_available_languages()
1114
 * @see wp_get_available_translations()
1115
 *
1116
 * @param string|array $args {
1117
 *     Optional. Array or string of arguments for outputting the language selector.
1118
 *
1119
 *     @type string   $id                           ID attribute of the select element. Default empty.
1120
 *     @type string   $name                         Name attribute of the select element. Default empty.
1121
 *     @type array    $languages                    List of installed languages, contain only the locales.
1122
 *                                                  Default empty array.
1123
 *     @type array    $translations                 List of available translations. Default result of
1124
 *                                                  wp_get_available_translations().
1125
 *     @type string   $selected                     Language which should be selected. Default empty.
1126
 *     @type bool|int $echo                         Whether to echo the generated markup. Accepts 0, 1, or their
1127
 *                                                  boolean equivalents. Default 1.
1128
 *     @type bool     $show_available_translations  Whether to show available translations. Default true.
1129
 *     @type bool     $show_option_site_default     Whether to show an option to fall back to the site's locale. Default false.
1130
 * }
1131
 * @return string HTML content
1132
 */
1133
function wp_dropdown_languages( $args = array() ) {
1134
1135
	$args = wp_parse_args( $args, array(
1136
		'id'           => '',
1137
		'name'         => '',
1138
		'languages'    => array(),
1139
		'translations' => array(),
1140
		'selected'     => '',
1141
		'echo'         => 1,
1142
		'show_available_translations' => true,
1143
		'show_option_site_default'    => false,
1144
	) );
1145
1146
	// English (United States) uses an empty string for the value attribute.
1147
	if ( 'en_US' === $args['selected'] ) {
1148
		$args['selected'] = '';
1149
	}
1150
1151
	$translations = $args['translations'];
1152
	if ( empty( $translations ) ) {
1153
		require_once( ABSPATH . 'wp-admin/includes/translation-install.php' );
1154
		$translations = wp_get_available_translations();
1155
	}
1156
1157
	/*
1158
	 * $args['languages'] should only contain the locales. Find the locale in
1159
	 * $translations to get the native name. Fall back to locale.
1160
	 */
1161
	$languages = array();
1162
	foreach ( $args['languages'] as $locale ) {
1163
		if ( isset( $translations[ $locale ] ) ) {
1164
			$translation = $translations[ $locale ];
1165
			$languages[] = array(
1166
				'language'    => $translation['language'],
1167
				'native_name' => $translation['native_name'],
1168
				'lang'        => current( $translation['iso'] ),
1169
			);
1170
1171
			// Remove installed language from available translations.
1172
			unset( $translations[ $locale ] );
1173
		} else {
1174
			$languages[] = array(
1175
				'language'    => $locale,
1176
				'native_name' => $locale,
1177
				'lang'        => '',
1178
			);
1179
		}
1180
	}
1181
1182
	$translations_available = ( ! empty( $translations ) && $args['show_available_translations'] );
1183
1184
	$output = sprintf( '<select name="%s" id="%s">', esc_attr( $args['name'] ), esc_attr( $args['id'] ) );
1185
1186
	// Holds the HTML markup.
1187
	$structure = array();
1188
1189
	// List installed languages.
1190
	if ( $translations_available ) {
1191
		$structure[] = '<optgroup label="' . esc_attr_x( 'Installed', 'translations' ) . '">';
1192
	}
1193
1194
	if ( $args['show_option_site_default'] ) {
1195
		$structure[] = sprintf(
1196
			'<option value="site-default" data-installed="1"%s>%s</option>',
1197
			selected( 'site-default', $args['selected'], false ),
1198
			_x( 'Site Default', 'default site language' )
1199
		);
1200
	}
1201
1202
	$structure[] = sprintf(
1203
		'<option value="" lang="en" data-installed="1"%s>English (United States)</option>',
1204
		selected( '', $args['selected'], false )
1205
	);
1206
1207 View Code Duplication
	foreach ( $languages as $language ) {
1208
		$structure[] = sprintf(
1209
			'<option value="%s" lang="%s"%s data-installed="1">%s</option>',
1210
			esc_attr( $language['language'] ),
1211
			esc_attr( $language['lang'] ),
1212
			selected( $language['language'], $args['selected'], false ),
1213
			esc_html( $language['native_name'] )
1214
		);
1215
	}
1216
	if ( $translations_available ) {
1217
		$structure[] = '</optgroup>';
1218
	}
1219
1220
	// List available translations.
1221
	if ( $translations_available ) {
1222
		$structure[] = '<optgroup label="' . esc_attr_x( 'Available', 'translations' ) . '">';
1223 View Code Duplication
		foreach ( $translations as $translation ) {
1224
			$structure[] = sprintf(
1225
				'<option value="%s" lang="%s"%s>%s</option>',
1226
				esc_attr( $translation['language'] ),
1227
				esc_attr( current( $translation['iso'] ) ),
1228
				selected( $translation['language'], $args['selected'], false ),
1229
				esc_html( $translation['native_name'] )
1230
			);
1231
		}
1232
		$structure[] = '</optgroup>';
1233
	}
1234
1235
	$output .= join( "\n", $structure );
1236
1237
	$output .= '</select>';
1238
1239
	if ( $args['echo'] ) {
1240
		echo $output;
1241
	}
1242
1243
	return $output;
1244
}
1245
1246
/**
1247
 * Checks if current locale is RTL.
1248
 *
1249
 * @since 3.0.0
1250
 *
1251
 * @global WP_Locale $wp_locale
1252
 *
1253
 * @return bool Whether locale is RTL.
1254
 */
1255
function is_rtl() {
1256
	global $wp_locale;
1257
	if ( ! ( $wp_locale instanceof WP_Locale ) ) {
1258
		return false;
1259
	}
1260
	return $wp_locale->is_rtl();
1261
}
1262
1263
/**
1264
 * Switches the translations according to the given locale.
1265
 *
1266
 * @since 4.7.0
1267
 *
1268
 * @global WP_Locale_Switcher $wp_locale_switcher
1269
 *
1270
 * @param string $locale The locale.
1271
 * @return bool True on success, false on failure.
1272
 */
1273
function switch_to_locale( $locale ) {
1274
	/* @var WP_Locale_Switcher $wp_locale_switcher */
1275
	global $wp_locale_switcher;
1276
1277
	return $wp_locale_switcher->switch_to_locale( $locale );
1278
}
1279
1280
/**
1281
 * Restores the translations according to the previous locale.
1282
 *
1283
 * @since 4.7.0
1284
 *
1285
 * @global WP_Locale_Switcher $wp_locale_switcher
1286
 *
1287
 * @return string|false Locale on success, false on error.
1288
 */
1289
function restore_previous_locale() {
1290
	/* @var WP_Locale_Switcher $wp_locale_switcher */
1291
	global $wp_locale_switcher;
1292
1293
	return $wp_locale_switcher->restore_previous_locale();
1294
}
1295
1296
/**
1297
 * Restores the translations according to the original locale.
1298
 *
1299
 * @since 4.7.0
1300
 *
1301
 * @global WP_Locale_Switcher $wp_locale_switcher
1302
 *
1303
 * @return string|false Locale on success, false on error.
1304
 */
1305
function restore_current_locale() {
1306
	/* @var WP_Locale_Switcher $wp_locale_switcher */
1307
	global $wp_locale_switcher;
1308
1309
	return $wp_locale_switcher->restore_current_locale();
1310
}
1311
1312
/**
1313
 * Whether switch_to_locale() is in effect.
1314
 *
1315
 * @since 4.7.0
1316
 *
1317
 * @global WP_Locale_Switcher $wp_locale_switcher
1318
 *
1319
 * @return bool True if the locale has been switched, false otherwise.
1320
 */
1321
function is_locale_switched() {
1322
	/* @var WP_Locale_Switcher $wp_locale_switcher */
1323
	global $wp_locale_switcher;
1324
1325
	return $wp_locale_switcher->is_switched();
1326
}
1327