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 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Api;
22
23use MediaWiki\Language\LanguageCode;
24use MediaWiki\Languages\LanguageConverterFactory;
25use MediaWiki\Languages\LanguageFactory;
26use MediaWiki\Languages\LanguageFallback;
27use MediaWiki\Languages\LanguageNameUtils;
28use MediaWiki\Message\Message;
29use Wikimedia\ParamValidator\ParamValidator;
30use Wikimedia\Timestamp\ConvertibleTimestamp;
31
32/**
33 * API module to enumerate language information.
34 *
35 * @ingroup API
36 */
37class ApiQueryLanguageinfo extends ApiQueryBase {
38
39    /**
40     * The maximum time for {@link execute()};
41     * if execution takes longer than this, apply continuation.
42     *
43     * If the localization cache is used, this time is not expected to ever be
44     * exceeded; on the other hand, if it is not used, a typical request will
45     * not yield more than a handful of languages before the time is exceeded
46     * and continuation is applied, if one of the expensive props is requested.
47     */
48    private const MAX_EXECUTE_SECONDS = 3;
49
50    private LanguageFactory $languageFactory;
51    private LanguageNameUtils $languageNameUtils;
52    private LanguageFallback $languageFallback;
53    private LanguageConverterFactory $languageConverterFactory;
54
55    public function __construct(
56        ApiQuery $queryModule,
57        string $moduleName,
58        LanguageFactory $languageFactory,
59        LanguageNameUtils $languageNameUtils,
60        LanguageFallback $languageFallback,
61        LanguageConverterFactory $languageConverterFactory
62    ) {
63        parent::__construct( $queryModule, $moduleName, 'li' );
64        $this->languageFactory = $languageFactory;
65        $this->languageNameUtils = $languageNameUtils;
66        $this->languageFallback = $languageFallback;
67        $this->languageConverterFactory = $languageConverterFactory;
68    }
69
70    public function execute() {
71        // ConvertibleTimestamp::time() used so we can fake the current time in tests
72        $endTime = ConvertibleTimestamp::time() + self::MAX_EXECUTE_SECONDS;
73
74        $props = array_fill_keys( $this->getParameter( 'prop' ), true );
75        $includeCode = isset( $props['code'] );
76        $includeBcp47 = isset( $props['bcp47'] );
77        $includeDir = isset( $props['dir'] );
78        $includeAutonym = isset( $props['autonym'] );
79        $includeName = isset( $props['name'] );
80        $includeVariantnames = isset( $props['variantnames'] );
81        $includeFallbacks = isset( $props['fallbacks'] );
82        $includeVariants = isset( $props['variants'] );
83
84        $targetLanguageCode = $this->getLanguage()->getCode();
85        $include = LanguageNameUtils::ALL;
86
87        $availableLanguageCodes = array_keys( $this->languageNameUtils->getLanguageNames(
88            // MediaWiki and extensions may return different sets of language codes
89            // when asked for language names in different languages;
90            // asking for English language names is most likely to give us the full set,
91            // even though we may not need those at all
92            'en',
93            $include
94        ) );
95        $selectedLanguageCodes = $this->getParameter( 'code' );
96        if ( $selectedLanguageCodes === [ '*' ] ) {
97            $languageCodes = $availableLanguageCodes;
98        } else {
99            $languageCodes = array_values( array_intersect(
100                $availableLanguageCodes,
101                $selectedLanguageCodes
102            ) );
103            $unrecognizedCodes = array_values( array_diff(
104                $selectedLanguageCodes,
105                $availableLanguageCodes
106            ) );
107            if ( $unrecognizedCodes !== [] ) {
108                $this->addWarning( [
109                    'apiwarn-unrecognizedvalues',
110                    $this->encodeParamName( 'code' ),
111                    Message::listParam( $unrecognizedCodes, 'comma' ),
112                    count( $unrecognizedCodes ),
113                ] );
114            }
115        }
116        // order of $languageCodes is guaranteed by LanguageNameUtils::getLanguageNames()
117        // and preserved by array_values() + array_intersect()
118
119        $continue = $this->getParameter( 'continue' ) ?? reset( $languageCodes );
120
121        $result = $this->getResult();
122        $rootPath = [
123            $this->getQuery()->getModuleName(),
124            $this->getModuleName(),
125        ];
126        $result->addArrayType( $rootPath, 'assoc' );
127
128        foreach ( $languageCodes as $languageCode ) {
129            if ( $languageCode < $continue ) {
130                continue;
131            }
132
133            $now = ConvertibleTimestamp::time();
134            if ( $now >= $endTime ) {
135                $this->setContinueEnumParameter( 'continue', $languageCode );
136                break;
137            }
138
139            $info = [];
140            ApiResult::setArrayType( $info, 'assoc' );
141
142            if ( $includeCode ) {
143                $info['code'] = $languageCode;
144            }
145
146            if ( $includeBcp47 ) {
147                $bcp47 = LanguageCode::bcp47( $languageCode );
148                $info['bcp47'] = $bcp47;
149            }
150
151            if ( $includeDir ) {
152                $dir = $this->languageFactory->getLanguage( $languageCode )->getDir();
153                $info['dir'] = $dir;
154            }
155
156            if ( $includeAutonym ) {
157                $autonym = $this->languageNameUtils->getLanguageName(
158                    $languageCode,
159                    LanguageNameUtils::AUTONYMS,
160                    $include
161                );
162                $info['autonym'] = $autonym;
163            }
164
165            if ( $includeName ) {
166                $name = $this->languageNameUtils->getLanguageName(
167                    $languageCode,
168                    $targetLanguageCode,
169                    $include
170                );
171                $info['name'] = $name;
172            }
173
174            if ( $includeFallbacks ) {
175                $fallbacks = $this->languageFallback->getAll(
176                    $languageCode,
177                    // allow users to distinguish between implicit and explicit 'en' fallbacks
178                    LanguageFallback::STRICT
179                );
180                ApiResult::setIndexedTagName( $fallbacks, 'fb' );
181                $info['fallbacks'] = $fallbacks;
182            }
183
184            if ( $includeVariants || $includeVariantnames ) {
185                $language = $this->languageFactory->getLanguage( $languageCode );
186                $converter = $this->languageConverterFactory->getLanguageConverter( $language );
187                $variants = $converter->getVariants();
188
189                if ( $includeVariants ) {
190                    $info['variants'] = $variants;
191                    ApiResult::setIndexedTagName( $info['variants'], 'var' );
192                }
193                if ( $includeVariantnames ) {
194                    $info['variantnames'] = [];
195                    foreach ( $variants as $variantCode ) {
196                        $info['variantnames'][$variantCode] = $language->getVariantname( $variantCode );
197                    }
198                }
199            }
200
201            $fit = $result->addValue( $rootPath, $languageCode, $info );
202            if ( !$fit ) {
203                $this->setContinueEnumParameter( 'continue', $languageCode );
204                break;
205            }
206        }
207    }
208
209    public function getCacheMode( $params ) {
210        return 'public';
211    }
212
213    public function getAllowedParams() {
214        return [
215            'prop' => [
216                ParamValidator::PARAM_DEFAULT => 'code',
217                ParamValidator::PARAM_ISMULTI => true,
218                ParamValidator::PARAM_TYPE => [
219                    'code',
220                    'bcp47',
221                    'dir',
222                    'autonym',
223                    'name',
224                    'variantnames',
225                    'fallbacks',
226                    'variants',
227                ],
228                self::PARAM_HELP_MSG_PER_VALUE => [],
229            ],
230            'code' => [
231                ParamValidator::PARAM_DEFAULT => '*',
232                ParamValidator::PARAM_ISMULTI => true,
233            ],
234            'continue' => [
235                self::PARAM_HELP_MSG => 'api-help-param-continue',
236            ],
237        ];
238    }
239
240    protected function getExamplesMessages() {
241        $pathUrl = 'action=' . $this->getQuery()->getModuleName() .
242            '&meta=' . $this->getModuleName();
243        $pathMsg = $this->getModulePath();
244        $prefix = $this->getModulePrefix();
245
246        return [
247            "$pathUrl"
248                => "apihelp-$pathMsg-example-simple",
249            "$pathUrl&{$prefix}prop=autonym|name&uselang=de"
250                => "apihelp-$pathMsg-example-autonym-name-de",
251            "$pathUrl&{$prefix}prop=fallbacks|variants&{$prefix}code=oc"
252                => "apihelp-$pathMsg-example-fallbacks-variants-oc",
253            "$pathUrl&{$prefix}prop=bcp47|dir"
254                => "apihelp-$pathMsg-example-bcp47-dir",
255        ];
256    }
257
258}
259
260/** @deprecated class alias since 1.43 */
261class_alias( ApiQueryLanguageinfo::class, 'ApiQueryLanguageinfo' );