Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.98% covered (warning)
85.98%
141 / 164
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryLanguageinfo
86.50% covered (warning)
86.50%
141 / 163
60.00% covered (warning)
60.00%
3 / 5
29.93
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
93.22% covered (success)
93.22%
110 / 118
0.00% covered (danger)
0.00%
0 / 1
24.18
 getCacheMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedParams
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Api;
8
9use MediaWiki\Language\LanguageCode;
10use MediaWiki\Language\LanguageConverterFactory;
11use MediaWiki\Language\LanguageFactory;
12use MediaWiki\Language\LanguageFallback;
13use MediaWiki\Language\LanguageFallbackMode;
14use MediaWiki\Language\LanguageNameUtils;
15use MediaWiki\Message\Message;
16use Wikimedia\Message\ListType;
17use Wikimedia\ParamValidator\ParamValidator;
18use Wikimedia\Timestamp\ConvertibleTimestamp;
19
20/**
21 * API module to enumerate language information.
22 *
23 * @ingroup API
24 */
25class ApiQueryLanguageinfo extends ApiQueryBase {
26
27    /**
28     * The maximum time for {@link execute()};
29     * if execution takes longer than this, apply continuation.
30     *
31     * If the localization cache is used, this time is not expected to ever be
32     * exceeded; on the other hand, if it is not used, a typical request will
33     * not yield more than a handful of languages before the time is exceeded
34     * and continuation is applied, if one of the expensive props is requested.
35     */
36    private const MAX_EXECUTE_SECONDS = 3;
37
38    public function __construct(
39        ApiQuery $queryModule,
40        string $moduleName,
41        private readonly LanguageFactory $languageFactory,
42        private readonly LanguageNameUtils $languageNameUtils,
43        private readonly LanguageFallback $languageFallback,
44        private readonly LanguageConverterFactory $languageConverterFactory,
45    ) {
46        parent::__construct( $queryModule, $moduleName, 'li' );
47    }
48
49    public function execute() {
50        // ConvertibleTimestamp::time() used so we can fake the current time in tests
51        $endTime = ConvertibleTimestamp::time() + self::MAX_EXECUTE_SECONDS;
52
53        $props = array_fill_keys( $this->getParameter( 'prop' ), true );
54        $includeCode = isset( $props['code'] );
55        $includeBcp47 = isset( $props['bcp47'] );
56        $includeDir = isset( $props['dir'] );
57        $includeAutonym = isset( $props['autonym'] );
58        $includeName = isset( $props['name'] );
59        $includeVariantnames = isset( $props['variantnames'] );
60        $includeFallbacks = isset( $props['fallbacks'] );
61        $includeVariants = isset( $props['variants'] );
62        $includeDigitTransforms = isset( $props['digittransforms'] );
63        $includeDigitGroupingPattern = isset( $props['digitgroupingpattern'] );
64        $includeMinimumGroupingDigits = isset( $props['minimumgroupingdigits'] );
65        $includeNamespaceNames = isset( $props['namespacenames'] );
66        $includeNamespaceAliases = isset( $props['namespacealiases'] );
67
68        $targetLanguageCode = $this->getLanguage()->getCode();
69        $include = LanguageNameUtils::ALL;
70
71        $availableLanguageCodes = array_keys( $this->languageNameUtils->getLanguageNames(
72            // MediaWiki and extensions may return different sets of language codes
73            // when asked for language names in different languages;
74            // asking for English language names is most likely to give us the full set,
75            // even though we may not need those at all
76            'en',
77            $include
78        ) );
79        $selectedLanguageCodes = $this->getParameter( 'code' );
80        if ( $selectedLanguageCodes === [ '*' ] ) {
81            $languageCodes = $availableLanguageCodes;
82        } else {
83            $languageCodes = array_values( array_intersect(
84                $availableLanguageCodes,
85                $selectedLanguageCodes
86            ) );
87            $unrecognizedCodes = array_values( array_diff(
88                $selectedLanguageCodes,
89                $availableLanguageCodes
90            ) );
91            if ( $unrecognizedCodes !== [] ) {
92                $this->addWarning( [
93                    'apiwarn-unrecognizedvalues',
94                    $this->encodeParamName( 'code' ),
95                    Message::listParam( $unrecognizedCodes, ListType::COMMA ),
96                    count( $unrecognizedCodes ),
97                ] );
98            }
99        }
100        // order of $languageCodes is guaranteed by LanguageNameUtils::getLanguageNames()
101        // and preserved by array_values() + array_intersect()
102
103        $continue = $this->getParameter( 'continue' ) ?? reset( $languageCodes );
104
105        $result = $this->getResult();
106        $rootPath = [
107            $this->getQuery()->getModuleName(),
108            $this->getModuleName(),
109        ];
110        $result->addArrayType( $rootPath, 'assoc' );
111
112        foreach ( $languageCodes as $languageCode ) {
113            if ( $languageCode < $continue ) {
114                continue;
115            }
116
117            $now = ConvertibleTimestamp::time();
118            if ( $now >= $endTime ) {
119                $this->setContinueEnumParameter( 'continue', $languageCode );
120                break;
121            }
122
123            $info = [];
124            ApiResult::setArrayType( $info, 'assoc' );
125
126            if ( $includeCode ) {
127                $info['code'] = $languageCode;
128            }
129
130            if ( $includeBcp47 ) {
131                $bcp47 = LanguageCode::bcp47( $languageCode );
132                $info['bcp47'] = $bcp47;
133            }
134
135            if ( $includeDir ) {
136                $dir = $this->languageFactory->getLanguage( $languageCode )->getDir();
137                $info['dir'] = $dir;
138            }
139
140            if ( $includeAutonym ) {
141                $autonym = $this->languageNameUtils->getLanguageName(
142                    $languageCode,
143                    LanguageNameUtils::AUTONYMS,
144                    $include
145                );
146                $info['autonym'] = $autonym;
147            }
148
149            if ( $includeName ) {
150                $name = $this->languageNameUtils->getLanguageName(
151                    $languageCode,
152                    $targetLanguageCode,
153                    $include
154                );
155                $info['name'] = $name;
156            }
157
158            if ( $includeFallbacks ) {
159                $fallbacks = $this->languageFallback->getAll(
160                    $languageCode,
161                    // allow users to distinguish between implicit and explicit 'en' fallbacks
162                    LanguageFallbackMode::STRICT
163                );
164                ApiResult::setIndexedTagName( $fallbacks, 'fb' );
165                $info['fallbacks'] = $fallbacks;
166            }
167
168            if ( $includeVariants || $includeVariantnames ) {
169                $language = $this->languageFactory->getLanguage( $languageCode );
170                $converter = $this->languageConverterFactory->getLanguageConverter( $language );
171                $variants = $converter->getVariants();
172
173                if ( $includeVariants ) {
174                    $info['variants'] = $variants;
175                    ApiResult::setIndexedTagName( $info['variants'], 'var' );
176                }
177                if ( $includeVariantnames ) {
178                    $info['variantnames'] = [];
179                    foreach ( $variants as $variantCode ) {
180                        $info['variantnames'][$variantCode] = $language->getVariantname( $variantCode );
181                    }
182                }
183            }
184
185            if ( $includeDigitTransforms ) {
186                $language = $this->languageFactory->getLanguage( $languageCode );
187                $digittransforms = $language->digitTransformTable();
188                if ( !$digittransforms ) {
189                    $digittransforms = [];
190                }
191                ApiResult::setIndexedTagName( $digittransforms, 'dig' );
192                $info['digittransforms'] = $digittransforms;
193            }
194
195            if ( $includeDigitGroupingPattern ) {
196                $language = $this->languageFactory->getLanguage( $languageCode );
197                $info['digitgroupingpattern'] = $language->digitGroupingPattern() ?? '#,##0.###';
198            }
199
200            if ( $includeMinimumGroupingDigits ) {
201                $language = $this->languageFactory->getLanguage( $languageCode );
202                $info['minimumgroupingdigits'] = $language->minimumGroupingDigits();
203            }
204
205            if ( $includeNamespaceNames ) {
206                $language = $this->languageFactory->getLanguage( $languageCode );
207                $info['namespacenames'] = $language->getNamespaces();
208            }
209
210            if ( $includeNamespaceAliases ) {
211                $language = $this->languageFactory->getLanguage( $languageCode );
212                $info['namespacealiases'] = $language->getNamespaceAliases();
213            }
214
215            $fit = $result->addValue( $rootPath, $languageCode, $info );
216            if ( !$fit ) {
217                $this->setContinueEnumParameter( 'continue', $languageCode );
218                break;
219            }
220        }
221    }
222
223    /** @inheritDoc */
224    public function getCacheMode( $params ) {
225        return 'public';
226    }
227
228    /** @inheritDoc */
229    public function getAllowedParams() {
230        return [
231            'prop' => [
232                ParamValidator::PARAM_DEFAULT => 'code',
233                ParamValidator::PARAM_ISMULTI => true,
234                ParamValidator::PARAM_TYPE => [
235                    'code',
236                    'bcp47',
237                    'dir',
238                    'autonym',
239                    'name',
240                    'variantnames',
241                    'fallbacks',
242                    'variants',
243                    'digittransforms',
244                    'digitgroupingpattern',
245                    'minimumgroupingdigits',
246                    'namespacenames',
247                    'namespacealiases',
248                ],
249                self::PARAM_HELP_MSG_PER_VALUE => [],
250            ],
251            'code' => [
252                ParamValidator::PARAM_DEFAULT => '*',
253                ParamValidator::PARAM_ISMULTI => true,
254            ],
255            'continue' => [
256                self::PARAM_HELP_MSG => 'api-help-param-continue',
257            ],
258        ];
259    }
260
261    /** @inheritDoc */
262    protected function getExamplesMessages() {
263        $pathUrl = 'action=' . $this->getQuery()->getModuleName() .
264            '&meta=' . $this->getModuleName();
265        $pathMsg = $this->getModulePath();
266        $prefix = $this->getModulePrefix();
267
268        return [
269            "$pathUrl"
270                => "apihelp-$pathMsg-example-simple",
271            "$pathUrl&{$prefix}prop=autonym|name&uselang=de"
272                => "apihelp-$pathMsg-example-autonym-name-de",
273            "$pathUrl&{$prefix}prop=fallbacks|variants&{$prefix}code=oc"
274                => "apihelp-$pathMsg-example-fallbacks-variants-oc",
275            "$pathUrl&{$prefix}prop=bcp47|dir"
276                => "apihelp-$pathMsg-example-bcp47-dir",
277        ];
278    }
279
280}
281
282/** @deprecated class alias since 1.43 */
283class_alias( ApiQueryLanguageinfo::class, 'ApiQueryLanguageinfo' );