MediaWiki REL1_34
LanguageNameUtils.php
Go to the documentation of this file.
1<?php
30
32use Hooks;
35use MWException;
36use Wikimedia\Assert\Assert;
37
49 const AUTONYMS = null;
50
54 const ALL = 'all';
55
59 const DEFINED = 'mw';
60
64 const SUPPORTED = 'mwfile';
65
67 private $options;
68
74
79 private $validCodeCache = [];
80
81 public const CONSTRUCTOR_OPTIONS = [
82 'ExtraLanguageNames',
83 'UsePigLatinVariant',
84 ];
85
89 public function __construct( ServiceOptions $options ) {
90 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
91 $this->options = $options;
92 }
93
101 public function isSupportedLanguage( $code ) {
102 if ( !$this->isValidBuiltInCode( $code ) ) {
103 return false;
104 }
105
106 if ( $code === 'qqq' ) {
107 // Special code for internal use, not supported even though there is a qqq.json
108 return false;
109 }
110
111 return is_readable( $this->getMessagesFileName( $code ) ) ||
112 is_readable( $this->getJsonMessagesFileName( $code ) );
113 }
114
123 public function isValidCode( $code ) {
124 Assert::parameterType( 'string', $code, '$code' );
125 if ( !isset( $this->validCodeCache[$code] ) ) {
126 // People think language codes are HTML-safe, so enforce it. Ideally we should only
127 // allow a-zA-Z0-9- but .+ and other chars are often used for {{int:}} hacks. See bugs
128 // T39564, T39587, T38938.
129 $this->validCodeCache[$code] =
130 // Protect against path traversal
131 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) &&
132 !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
133 }
134 return $this->validCodeCache[$code];
135 }
136
144 public function isValidBuiltInCode( $code ) {
145 Assert::parameterType( 'string', $code, '$code' );
146
147 return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
148 }
149
157 public function isKnownLanguageTag( $tag ) {
158 // Quick escape for invalid input to avoid exceptions down the line when code tries to
159 // process tags which are not valid at all.
160 if ( !$this->isValidBuiltInCode( $tag ) ) {
161 return false;
162 }
163
164 if ( isset( Data\Names::$names[$tag] ) || $this->getLanguageName( $tag, $tag ) !== '' ) {
165 return true;
166 }
167
168 return false;
169 }
170
182 public function getLanguageNames( $inLanguage = self::AUTONYMS, $include = self::DEFINED ) {
183 $cacheKey = $inLanguage === self::AUTONYMS ? 'null' : $inLanguage;
184 $cacheKey .= ":$include";
185 if ( !$this->languageNameCache ) {
186 $this->languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
187 }
188
189 $ret = $this->languageNameCache->get( $cacheKey );
190 if ( !$ret ) {
191 $ret = $this->getLanguageNamesUncached( $inLanguage, $include );
192 $this->languageNameCache->set( $cacheKey, $ret );
193 }
194 return $ret;
195 }
196
203 private function getLanguageNamesUncached( $inLanguage, $include ) {
204 // If passed an invalid language code to use, fallback to en
205 if ( $inLanguage !== self::AUTONYMS && !$this->isValidCode( $inLanguage ) ) {
206 $inLanguage = 'en';
207 }
208
209 $names = [];
210
211 if ( $inLanguage !== self::AUTONYMS ) {
212 # TODO: also include for self::AUTONYMS, when this code is more efficient
213 Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
214 }
215
216 $mwNames = $this->options->get( 'ExtraLanguageNames' ) + Data\Names::$names;
217 if ( $this->options->get( 'UsePigLatinVariant' ) ) {
218 // Pig Latin (for variant development)
219 $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
220 }
221
222 foreach ( $mwNames as $mwCode => $mwName ) {
223 # - Prefer own MediaWiki native name when not using the hook
224 # - For other names just add if not added through the hook
225 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
226 $names[$mwCode] = $mwName;
227 }
228 }
229
230 if ( $include === self::ALL ) {
231 ksort( $names );
232 return $names;
233 }
234
235 $returnMw = [];
236 $coreCodes = array_keys( $mwNames );
237 foreach ( $coreCodes as $coreCode ) {
238 $returnMw[$coreCode] = $names[$coreCode];
239 }
240
241 if ( $include === self::SUPPORTED ) {
242 $namesMwFile = [];
243 # We do this using a foreach over the codes instead of a directory loop so that messages
244 # files in extensions will work correctly.
245 foreach ( $returnMw as $code => $value ) {
246 if ( is_readable( $this->getMessagesFileName( $code ) ) ||
247 is_readable( $this->getJsonMessagesFileName( $code ) )
248 ) {
249 $namesMwFile[$code] = $names[$code];
250 }
251 }
252
253 ksort( $namesMwFile );
254 return $namesMwFile;
255 }
256
257 ksort( $returnMw );
258 # self::DEFINED option; default if it's not one of the other two options
259 # (self::ALL/self::SUPPORTED)
260 return $returnMw;
261 }
262
272 public function getLanguageName( $code, $inLanguage = self::AUTONYMS, $include = self::ALL ) {
273 $code = strtolower( $code );
274 $array = $this->getLanguageNames( $inLanguage, $include );
275 return $array[$code] ?? '';
276 }
277
286 public function getFileName( $prefix, $code, $suffix = '.php' ) {
287 if ( !$this->isValidBuiltInCode( $code ) ) {
288 throw new MWException( "Invalid language code \"$code\"" );
289 }
290
291 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
292 }
293
298 public function getMessagesFileName( $code ) {
299 global $IP;
300 $file = $this->getFileName( "$IP/languages/messages/Messages", $code, '.php' );
301 Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
302 return $file;
303 }
304
310 public function getJsonMessagesFileName( $code ) {
311 global $IP;
312
313 if ( !$this->isValidBuiltInCode( $code ) ) {
314 throw new MWException( "Invalid language code \"$code\"" );
315 }
316
317 return "$IP/languages/i18n/$code.json";
318 }
319}
Simple store for keeping values in an associative array for the current process.
Hooks class.
Definition Hooks.php:34
MediaWiki exception.
A codec for MediaWiki page titles.
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
A service that provides utilities to do with language names and codes.
isKnownLanguageTag( $tag)
Returns true if a language code is an IETF tag known to MediaWiki.
getLanguageNamesUncached( $inLanguage, $include)
Uncached helper for getLanguageNames.
isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx....
const DEFINED
Return in getLanguageName(s) only the languages that are defined by MediaWiki.
isValidCode( $code)
Returns true if a language code string is of a valid form, whether or not it exists.
isValidBuiltInCode( $code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
array $validCodeCache
Cache for validity of language codes.
const AUTONYMS
Return autonyms in getLanguageName(s).
HashBagOStuff null $languageNameCache
Cache for language names.
const ALL
Return all known languages in getLanguageName(s).
getLanguageName( $code, $inLanguage=self::AUTONYMS, $include=self::ALL)
getFileName( $prefix, $code, $suffix='.php')
Get the name of a file for a certain language code.
const SUPPORTED
Return in getLanguageName(s) only the languages for which we have at least some localisation.
getLanguageNames( $inLanguage=self::AUTONYMS, $include=self::DEFINED)
Get an array of language names, indexed by code.
$IP
Definition update.php:3
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42