Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.45% covered (warning)
83.45%
116 / 139
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryLanguageinfo
84.06% covered (warning)
84.06%
116 / 138
60.00% covered (warning)
60.00%
3 / 5
23.96
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 execute
91.49% covered (success)
91.49%
86 / 94
0.00% covered (danger)
0.00%
0 / 1
18.20
 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%
24 / 24
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    private LanguageFactory $languageFactory;
39    private LanguageNameUtils $languageNameUtils;
40    private LanguageFallback $languageFallback;
41    private LanguageConverterFactory $languageConverterFactory;
42
43    public function __construct(
44        ApiQuery $queryModule,
45        string $moduleName,
46        LanguageFactory $languageFactory,
47        LanguageNameUtils $languageNameUtils,
48        LanguageFallback $languageFallback,
49        LanguageConverterFactory $languageConverterFactory
50    ) {
51        parent::__construct( $queryModule, $moduleName, 'li' );
52        $this->languageFactory = $languageFactory;
53        $this->languageNameUtils = $languageNameUtils;
54        $this->languageFallback = $languageFallback;
55        $this->languageConverterFactory = $languageConverterFactory;
56    }
57
58    public function execute() {
59        // ConvertibleTimestamp::time() used so we can fake the current time in tests
60        $endTime = ConvertibleTimestamp::time() + self::MAX_EXECUTE_SECONDS;
61
62        $props = array_fill_keys( $this->getParameter( 'prop' ), true );
63        $includeCode = isset( $props['code'] );
64        $includeBcp47 = isset( $props['bcp47'] );
65        $includeDir = isset( $props['dir'] );
66        $includeAutonym = isset( $props['autonym'] );
67        $includeName = isset( $props['name'] );
68        $includeVariantnames = isset( $props['variantnames'] );
69        $includeFallbacks = isset( $props['fallbacks'] );
70        $includeVariants = isset( $props['variants'] );
71
72        $targetLanguageCode = $this->getLanguage()->getCode();
73        $include = LanguageNameUtils::ALL;
74
75        $availableLanguageCodes = array_keys( $this->languageNameUtils->getLanguageNames(
76            // MediaWiki and extensions may return different sets of language codes
77            // when asked for language names in different languages;
78            // asking for English language names is most likely to give us the full set,
79            // even though we may not need those at all
80            'en',
81            $include
82        ) );
83        $selectedLanguageCodes = $this->getParameter( 'code' );
84        if ( $selectedLanguageCodes === [ '*' ] ) {
85            $languageCodes = $availableLanguageCodes;
86        } else {
87            $languageCodes = array_values( array_intersect(
88                $availableLanguageCodes,
89                $selectedLanguageCodes
90            ) );
91            $unrecognizedCodes = array_values( array_diff(
92                $selectedLanguageCodes,
93                $availableLanguageCodes
94            ) );
95            if ( $unrecognizedCodes !== [] ) {
96                $this->addWarning( [
97                    'apiwarn-unrecognizedvalues',
98                    $this->encodeParamName( 'code' ),
99                    Message::listParam( $unrecognizedCodes, ListType::COMMA ),
100                    count( $unrecognizedCodes ),
101                ] );
102            }
103        }
104        // order of $languageCodes is guaranteed by LanguageNameUtils::getLanguageNames()
105        // and preserved by array_values() + array_intersect()
106
107        $continue = $this->getParameter( 'continue' ) ?? reset( $languageCodes );
108
109        $result = $this->getResult();
110        $rootPath = [
111            $this->getQuery()->getModuleName(),
112            $this->getModuleName(),
113        ];
114        $result->addArrayType( $rootPath, 'assoc' );
115
116        foreach ( $languageCodes as $languageCode ) {
117            if ( $languageCode < $continue ) {
118                continue;
119            }
120
121            $now = ConvertibleTimestamp::time();
122            if ( $now >= $endTime ) {
123                $this->setContinueEnumParameter( 'continue', $languageCode );
124                break;
125            }
126
127            $info = [];
128            ApiResult::setArrayType( $info, 'assoc' );
129
130            if ( $includeCode ) {
131                $info['code'] = $languageCode;
132            }
133
134            if ( $includeBcp47 ) {
135                $bcp47 = LanguageCode::bcp47( $languageCode );
136                $info['bcp47'] = $bcp47;
137            }
138
139            if ( $includeDir ) {
140                $dir = $this->languageFactory->getLanguage( $languageCode )->getDir();
141                $info['dir'] = $dir;
142            }
143
144            if ( $includeAutonym ) {
145                $autonym = $this->languageNameUtils->getLanguageName(
146                    $languageCode,
147                    LanguageNameUtils::AUTONYMS,
148                    $include
149                );
150                $info['autonym'] = $autonym;
151            }
152
153            if ( $includeName ) {
154                $name = $this->languageNameUtils->getLanguageName(
155                    $languageCode,
156                    $targetLanguageCode,
157                    $include
158                );
159                $info['name'] = $name;
160            }
161
162            if ( $includeFallbacks ) {
163                $fallbacks = $this->languageFallback->getAll(
164                    $languageCode,
165                    // allow users to distinguish between implicit and explicit 'en' fallbacks
166                    LanguageFallbackMode::STRICT
167                );
168                ApiResult::setIndexedTagName( $fallbacks, 'fb' );
169                $info['fallbacks'] = $fallbacks;
170            }
171
172            if ( $includeVariants || $includeVariantnames ) {
173                $language = $this->languageFactory->getLanguage( $languageCode );
174                $converter = $this->languageConverterFactory->getLanguageConverter( $language );
175                $variants = $converter->getVariants();
176
177                if ( $includeVariants ) {
178                    $info['variants'] = $variants;
179                    ApiResult::setIndexedTagName( $info['variants'], 'var' );
180                }
181                if ( $includeVariantnames ) {
182                    $info['variantnames'] = [];
183                    foreach ( $variants as $variantCode ) {
184                        $info['variantnames'][$variantCode] = $language->getVariantname( $variantCode );
185                    }
186                }
187            }
188
189            $fit = $result->addValue( $rootPath, $languageCode, $info );
190            if ( !$fit ) {
191                $this->setContinueEnumParameter( 'continue', $languageCode );
192                break;
193            }
194        }
195    }
196
197    /** @inheritDoc */
198    public function getCacheMode( $params ) {
199        return 'public';
200    }
201
202    /** @inheritDoc */
203    public function getAllowedParams() {
204        return [
205            'prop' => [
206                ParamValidator::PARAM_DEFAULT => 'code',
207                ParamValidator::PARAM_ISMULTI => true,
208                ParamValidator::PARAM_TYPE => [
209                    'code',
210                    'bcp47',
211                    'dir',
212                    'autonym',
213                    'name',
214                    'variantnames',
215                    'fallbacks',
216                    'variants',
217                ],
218                self::PARAM_HELP_MSG_PER_VALUE => [],
219            ],
220            'code' => [
221                ParamValidator::PARAM_DEFAULT => '*',
222                ParamValidator::PARAM_ISMULTI => true,
223            ],
224            'continue' => [
225                self::PARAM_HELP_MSG => 'api-help-param-continue',
226            ],
227        ];
228    }
229
230    /** @inheritDoc */
231    protected function getExamplesMessages() {
232        $pathUrl = 'action=' . $this->getQuery()->getModuleName() .
233            '&meta=' . $this->getModuleName();
234        $pathMsg = $this->getModulePath();
235        $prefix = $this->getModulePrefix();
236
237        return [
238            "$pathUrl"
239                => "apihelp-$pathMsg-example-simple",
240            "$pathUrl&{$prefix}prop=autonym|name&uselang=de"
241                => "apihelp-$pathMsg-example-autonym-name-de",
242            "$pathUrl&{$prefix}prop=fallbacks|variants&{$prefix}code=oc"
243                => "apihelp-$pathMsg-example-fallbacks-variants-oc",
244            "$pathUrl&{$prefix}prop=bcp47|dir"
245                => "apihelp-$pathMsg-example-bcp47-dir",
246        ];
247    }
248
249}
250
251/** @deprecated class alias since 1.43 */
252class_alias( ApiQueryLanguageinfo::class, 'ApiQueryLanguageinfo' );