Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.74% covered (success)
93.74%
614 / 655
70.73% covered (warning)
70.73%
29 / 41
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQuerySiteinfo
93.88% covered (success)
93.88%
614 / 654
70.73% covered (warning)
70.73%
29 / 41
149.81
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
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
1 / 1
3
 appendGeneralInfo
100.00% covered (success)
100.00%
106 / 106
100.00% covered (success)
100.00%
1 / 1
13
 appendNamespaces
96.00% covered (success)
96.00%
24 / 25
0.00% covered (danger)
0.00%
0 / 1
7
 appendNamespaceAliases
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 appendSpecialPageAliases
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 appendMagicWords
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 appendInterwikiMap
97.78% covered (success)
97.78%
44 / 45
0.00% covered (danger)
0.00%
0 / 1
16
 appendDbReplLagInfo
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
5
 appendStatistics
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 appendUserGroups
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
8
 appendAutoCreateTempUser
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 appendFileExtensions
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 appendInstalledClientLibraries
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 appendInstalledLibraries
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
3.00
 appendExtensions
88.46% covered (warning)
88.46%
46 / 52
0.00% covered (danger)
0.00%
0 / 1
17.44
 appendRightsInfo
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 appendRestrictions
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 appendLanguages
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 appendLanguageVariants
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
5
 appendSkins
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
7
 appendExtensionTags
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 appendFunctionHooks
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 appendVariables
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 appendDoubleUnderscores
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 appendProtocols
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 appendDefaultOptions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 appendUploadDialog
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getAutoPromoteConds
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 processAutoPromote
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 appendAutoPromote
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 appendAutoPromoteOnce
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 appendCopyUploadDomains
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 appendCrossSiteAJAXdomains
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 appendSBOM
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 recAutopromote
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
9
 appendSubscribedHooks
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 getCacheMode
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getAllowedParams
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Api;
10
11use MediaWiki\HookContainer\HookContainer;
12use MediaWiki\Interwiki\InterwikiLookup;
13use MediaWiki\Language\Language;
14use MediaWiki\Language\LanguageCode;
15use MediaWiki\Language\LanguageConverter;
16use MediaWiki\Language\LanguageConverterFactory;
17use MediaWiki\Language\LanguageFactory;
18use MediaWiki\Language\LanguageNameUtils;
19use MediaWiki\MainConfigNames;
20use MediaWiki\Parser\MagicWordFactory;
21use MediaWiki\Parser\ParserFactory;
22use MediaWiki\Permissions\GroupPermissionsLookup;
23use MediaWiki\Registration\ExtensionRegistry;
24use MediaWiki\ResourceLoader\SkinModule;
25use MediaWiki\SiteStats\SiteStats;
26use MediaWiki\Skin\Skin;
27use MediaWiki\Skin\SkinFactory;
28use MediaWiki\SpecialPage\SpecialPage;
29use MediaWiki\SpecialPage\SpecialPageFactory;
30use MediaWiki\Specials\SpecialVersion;
31use MediaWiki\Title\NamespaceInfo;
32use MediaWiki\Title\Title;
33use MediaWiki\Upload\UploadBase;
34use MediaWiki\Upload\UploadFromUrl;
35use MediaWiki\User\Options\UserOptionsLookup;
36use MediaWiki\User\TempUser\TempUserConfig;
37use MediaWiki\User\UserGroupManager;
38use MediaWiki\User\UserIdentityUtils;
39use MediaWiki\User\UserRequirementsConditionChecker;
40use MediaWiki\Utils\ExtensionInfo;
41use MediaWiki\Utils\GitInfo;
42use MediaWiki\Utils\SBOMGenerator;
43use MediaWiki\Utils\UrlUtils;
44use MediaWiki\WikiMap\WikiMap;
45use Wikimedia\ParamValidator\ParamValidator;
46use Wikimedia\Rdbms\ILoadBalancer;
47use Wikimedia\Rdbms\ReadOnlyMode;
48use Wikimedia\Timestamp\TimestampFormat as TS;
49
50/**
51 * A query action to return meta information about the wiki site.
52 *
53 * @ingroup API
54 */
55class ApiQuerySiteinfo extends ApiQueryBase {
56
57    public function __construct(
58        ApiQuery $query,
59        string $moduleName,
60        private readonly UserOptionsLookup $userOptionsLookup,
61        private readonly UserGroupManager $userGroupManager,
62        private readonly HookContainer $hookContainer,
63        private readonly LanguageConverterFactory $languageConverterFactory,
64        private readonly LanguageFactory $languageFactory,
65        private readonly LanguageNameUtils $languageNameUtils,
66        private readonly Language $contentLanguage,
67        private readonly NamespaceInfo $namespaceInfo,
68        private readonly InterwikiLookup $interwikiLookup,
69        private readonly ParserFactory $parserFactory,
70        private readonly MagicWordFactory $magicWordFactory,
71        private readonly SpecialPageFactory $specialPageFactory,
72        private readonly SkinFactory $skinFactory,
73        private readonly ILoadBalancer $loadBalancer,
74        private readonly ReadOnlyMode $readOnlyMode,
75        private readonly UrlUtils $urlUtils,
76        private readonly TempUserConfig $tempUserConfig,
77        private readonly GroupPermissionsLookup $groupPermissionsLookup,
78        private readonly SBOMGenerator $sbomGenerator,
79        private readonly UserIdentityUtils $userIdentityUtils,
80    ) {
81        parent::__construct( $query, $moduleName, 'si' );
82    }
83
84    public function execute() {
85        $params = $this->extractRequestParams();
86        $done = [];
87        foreach ( $params['prop'] as $p ) {
88            $fit = match ( $p ) {
89                'general' => $this->appendGeneralInfo( $p ),
90                'namespaces' => $this->appendNamespaces( $p ),
91                'namespacealiases' => $this->appendNamespaceAliases( $p ),
92                'specialpagealiases' => $this->appendSpecialPageAliases( $p ),
93                'magicwords' => $this->appendMagicWords( $p ),
94                'interwikimap' => $this->appendInterwikiMap( $p, $params['filteriw'] ),
95                'dbrepllag' => $this->appendDbReplLagInfo( $p, $params['showalldb'] ),
96                'statistics' => $this->appendStatistics( $p ),
97                'usergroups' => $this->appendUserGroups( $p, $params['numberingroup'] ),
98                'autocreatetempuser' => $this->appendAutoCreateTempUser( $p ),
99                'clientlibraries' => $this->appendInstalledClientLibraries( $p ),
100                'libraries' => $this->appendInstalledLibraries( $p ),
101                'extensions' => $this->appendExtensions( $p ),
102                'fileextensions' => $this->appendFileExtensions( $p ),
103                'rightsinfo' => $this->appendRightsInfo( $p ),
104                'restrictions' => $this->appendRestrictions( $p ),
105                'languages' => $this->appendLanguages( $p ),
106                'languagevariants' => $this->appendLanguageVariants( $p ),
107                'skins' => $this->appendSkins( $p ),
108                'extensiontags' => $this->appendExtensionTags( $p ),
109                'functionhooks' => $this->appendFunctionHooks( $p ),
110                'showhooks' => $this->appendSubscribedHooks( $p ),
111                'variables' => $this->appendVariables( $p ),
112                'doubleunderscores' => $this->appendDoubleUnderscores( $p ),
113                'protocols' => $this->appendProtocols( $p ),
114                'defaultoptions' => $this->appendDefaultOptions( $p ),
115                'uploaddialog' => $this->appendUploadDialog( $p ),
116                'autopromote' => $this->appendAutoPromote( $p ),
117                'autopromoteonce' => $this->appendAutoPromoteOnce( $p ),
118                'copyuploaddomains' => $this->appendCopyUploadDomains( $p ),
119                'crosssiteajaxdomains' => $this->appendCrossSiteAJAXdomains( $p ),
120                'sbom' => $this->appendSBOM( $p ),
121                // @phan-suppress-next-line PhanUseReturnValueOfNever
122                default => ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" ) // @codeCoverageIgnore
123            };
124            if ( !$fit ) {
125                // Abuse siprop as a query-continue parameter
126                // and set it to all unprocessed props
127                $this->setContinueEnumParameter( 'prop', implode( '|',
128                    array_diff( $params['prop'], $done ) ) );
129                break;
130            }
131            $done[] = $p;
132        }
133    }
134
135    protected function appendGeneralInfo( string $property ): bool {
136        $config = $this->getConfig();
137        $mainPage = Title::newMainPage();
138        $logo = SkinModule::getAvailableLogos( $config, $this->getLanguage()->getCode() );
139
140        $data = [
141            'mainpage' => $mainPage->getPrefixedText(),
142            'base' => (string)$this->urlUtils->expand( $mainPage->getFullURL(), PROTO_CURRENT ),
143            'sitename' => $config->get( MainConfigNames::Sitename ),
144            'mainpageisdomainroot' => (bool)$config->get( MainConfigNames::MainPageIsDomainRoot ),
145
146            // A logo can either be a relative or an absolute path
147            // make sure we always return an absolute path
148            'logo' => (string)$this->urlUtils->expand( $logo['1x'], PROTO_RELATIVE ),
149
150            'generator' => 'MediaWiki ' . MW_VERSION,
151
152            'phpversion' => PHP_VERSION,
153            'phpsapi' => PHP_SAPI,
154            'dbtype' => $config->get( MainConfigNames::DBtype ),
155            'dbversion' => $this->getDB()->getServerVersion(),
156        ];
157
158        $allowFrom = [ '' ];
159        $allowException = true;
160        if ( !$config->get( MainConfigNames::AllowExternalImages ) ) {
161            $data['imagewhitelistenabled'] =
162                (bool)$config->get( MainConfigNames::EnableImageWhitelist );
163            $allowFrom = $config->get( MainConfigNames::AllowExternalImagesFrom );
164            $allowException = (bool)$allowFrom;
165        }
166        if ( $allowException ) {
167            $data['externalimages'] = (array)$allowFrom;
168            ApiResult::setIndexedTagName( $data['externalimages'], 'prefix' );
169        }
170
171        $data['langconversion'] = !$this->languageConverterFactory->isConversionDisabled();
172        $data['linkconversion'] = !$this->languageConverterFactory->isLinkConversionDisabled();
173        // For backwards compatibility (soft deprecated since MW 1.36)
174        $data['titleconversion'] = $data['linkconversion'];
175
176        $contLangConverter = $this->languageConverterFactory->getLanguageConverter( $this->contentLanguage );
177        if ( $this->contentLanguage->linkPrefixExtension() ) {
178            $linkPrefixCharset = $this->contentLanguage->linkPrefixCharset();
179            $data['linkprefixcharset'] = $linkPrefixCharset;
180            // For backwards compatibility
181            $data['linkprefix'] = "/^((?>.*[^$linkPrefixCharset]|))(.+)$/sDu";
182        } else {
183            $data['linkprefixcharset'] = '';
184            $data['linkprefix'] = '';
185        }
186
187        $data['linktrail'] = $this->contentLanguage->linkTrail() ?: '';
188
189        $data['legaltitlechars'] = Title::legalChars();
190        $data['invalidusernamechars'] = $config->get( MainConfigNames::InvalidUsernameCharacters );
191        $data['allunicodefixes'] = (bool)$config->get( MainConfigNames::AllUnicodeFixes );
192
193        $git = GitInfo::repo()->getHeadSHA1();
194        if ( $git ) {
195            $data['git-hash'] = $git;
196            $data['git-branch'] = GitInfo::repo()->getCurrentBranch();
197        }
198
199        // 'case-insensitive' option is reserved for future
200        $data['case'] =
201            $config->get( MainConfigNames::CapitalLinks ) ? 'first-letter' : 'case-sensitive';
202        $data['lang'] = $config->get( MainConfigNames::LanguageCode );
203
204        $data['fallback'] = [];
205        foreach ( $this->contentLanguage->getFallbackLanguages() as $code ) {
206            $data['fallback'][] = [ 'code' => $code ];
207        }
208        ApiResult::setIndexedTagName( $data['fallback'], 'lang' );
209
210        if ( $contLangConverter->hasVariants() ) {
211            $data['variants'] = [];
212            foreach ( $contLangConverter->getVariants() as $code ) {
213                $data['variants'][] = [
214                    'code' => $code,
215                    'name' => $this->contentLanguage->getVariantname( $code ),
216                ];
217            }
218            ApiResult::setIndexedTagName( $data['variants'], 'lang' );
219        }
220
221        $data['rtl'] = $this->contentLanguage->isRTL();
222        $data['fallback8bitEncoding'] = $this->contentLanguage->fallback8bitEncoding();
223
224        $data['readonly'] = $this->readOnlyMode->isReadOnly();
225        if ( $data['readonly'] ) {
226            $data['readonlyreason'] = $this->readOnlyMode->getReason();
227        }
228        $data['writeapi'] = true; // Deprecated since MW 1.32
229
230        $data['maxarticlesize'] = $config->get( MainConfigNames::MaxArticleSize ) * 1024;
231
232        $data['timezone'] = $config->get( MainConfigNames::Localtimezone );
233        $data['timeoffset'] = (int)( $config->get( MainConfigNames::LocalTZoffset ) );
234        $data['articlepath'] = $config->get( MainConfigNames::ArticlePath );
235        $data['scriptpath'] = $config->get( MainConfigNames::ScriptPath );
236        $data['script'] = $config->get( MainConfigNames::Script );
237        $data['variantarticlepath'] = $config->get( MainConfigNames::VariantArticlePath );
238        $data[ApiResult::META_BC_BOOLS][] = 'variantarticlepath';
239        $data['server'] = $config->get( MainConfigNames::Server );
240        $data['servername'] = $config->get( MainConfigNames::ServerName );
241        $data['wikiid'] = WikiMap::getCurrentWikiId();
242        $data['time'] = wfTimestamp( TS::ISO_8601, time() );
243
244        $data['misermode'] = (bool)$config->get( MainConfigNames::MiserMode );
245
246        $data['uploadsenabled'] = UploadBase::isEnabled();
247        $data['maxuploadsize'] = UploadBase::getMaxUploadSize();
248        $data['minuploadchunksize'] = ApiUpload::getMinUploadChunkSize( $config );
249
250        $data['galleryoptions'] = $config->get( MainConfigNames::GalleryOptions );
251
252        /* T427066: return thumblimits as a compact array, even if it is
253         * actually sparse; the thumbsize defaults are adjusted to match in
254         * ::appendDefaultOptions() below. */
255        $data['thumblimits'] = array_values(
256            $config->get( MainConfigNames::ThumbLimits )
257        );
258        ApiResult::setArrayType( $data['thumblimits'], 'BCassoc' );
259        ApiResult::setIndexedTagName( $data['thumblimits'], 'limit' );
260        $data['imagelimits'] = [];
261        foreach ( $config->get( MainConfigNames::ImageLimits ) as $k => $limit ) {
262            $data['imagelimits'][$k] = [ 'width' => $limit[0], 'height' => $limit[1] ];
263        }
264        ApiResult::setArrayType( $data['imagelimits'], 'BCassoc' );
265        ApiResult::setIndexedTagName( $data['imagelimits'], 'limit' );
266
267        $favicon = $config->get( MainConfigNames::Favicon );
268        if ( $favicon ) {
269            // Expand any local path to full URL to improve API usability (T77093).
270            $data['favicon'] = (string)$this->urlUtils->expand( $favicon );
271        }
272
273        $data['centralidlookupprovider'] = $config->get( MainConfigNames::CentralIdLookupProvider );
274        $providerIds = array_keys( $config->get( MainConfigNames::CentralIdLookupProviders ) );
275        $data['allcentralidlookupproviders'] = $providerIds;
276
277        $data['interwikimagic'] = (bool)$config->get( MainConfigNames::InterwikiMagic );
278        $data['magiclinks'] = $config->get( MainConfigNames::EnableMagicLinks );
279
280        $data['categorycollation'] = $config->get( MainConfigNames::CategoryCollation );
281
282        $data['nofollowlinks'] = $config->get( MainConfigNames::NoFollowLinks );
283        $data['nofollownsexceptions'] = $config->get( MainConfigNames::NoFollowNsExceptions );
284        $data['nofollowdomainexceptions'] = $config->get( MainConfigNames::NoFollowDomainExceptions );
285        $data['externallinktarget'] = $config->get( MainConfigNames::ExternalLinkTarget );
286
287        $this->getHookRunner()->onAPIQuerySiteInfoGeneralInfo( $this, $data );
288
289        return $this->getResult()->addValue( 'query', $property, $data );
290    }
291
292    protected function appendNamespaces( string $property ): bool {
293        $nsProtection = $this->getConfig()->get( MainConfigNames::NamespaceProtection );
294
295        $data = [ ApiResult::META_TYPE => 'assoc' ];
296        foreach ( $this->contentLanguage->getFormattedNamespaces() as $ns => $title ) {
297            $data[$ns] = [
298                'id' => (int)$ns,
299                'case' => $this->namespaceInfo->isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
300            ];
301            ApiResult::setContentValue( $data[$ns], 'name', $title );
302            $canonical = $this->namespaceInfo->getCanonicalName( $ns );
303
304            $data[$ns]['subpages'] = $this->namespaceInfo->hasSubpages( $ns );
305
306            if ( $canonical ) {
307                $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
308            }
309
310            $data[$ns]['content'] = $this->namespaceInfo->isContent( $ns );
311            $data[$ns]['nonincludable'] = $this->namespaceInfo->isNonincludable( $ns );
312
313            $specificNs = $nsProtection[$ns] ?? '';
314            if ( is_array( $specificNs ) ) {
315                $specificNs = implode( "|", array_filter( $specificNs ) );
316            }
317            if ( $specificNs !== '' ) {
318                $data[$ns]['namespaceprotection'] = $specificNs;
319            }
320
321            $contentmodel = $this->namespaceInfo->getNamespaceContentModel( $ns );
322            if ( $contentmodel ) {
323                $data[$ns]['defaultcontentmodel'] = $contentmodel;
324            }
325        }
326
327        ApiResult::setArrayType( $data, 'assoc' );
328        ApiResult::setIndexedTagName( $data, 'ns' );
329
330        return $this->getResult()->addValue( 'query', $property, $data );
331    }
332
333    protected function appendNamespaceAliases( string $property ): bool {
334        $aliases = $this->contentLanguage->getNamespaceAliases();
335        $namespaces = $this->contentLanguage->getNamespaces();
336        $data = [];
337        foreach ( $aliases as $title => $ns ) {
338            if ( $namespaces[$ns] == $title ) {
339                // Don't list duplicates
340                continue;
341            }
342            $item = [ 'id' => (int)$ns ];
343            ApiResult::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
344            $data[] = $item;
345        }
346
347        sort( $data );
348
349        ApiResult::setIndexedTagName( $data, 'ns' );
350
351        return $this->getResult()->addValue( 'query', $property, $data );
352    }
353
354    protected function appendSpecialPageAliases( string $property ): bool {
355        $data = [];
356        $aliases = $this->contentLanguage->getSpecialPageAliases();
357        foreach ( $this->specialPageFactory->getNames() as $specialpage ) {
358            if ( isset( $aliases[$specialpage] ) ) {
359                $arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ];
360                ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
361                $data[] = $arr;
362            }
363        }
364        ApiResult::setIndexedTagName( $data, 'specialpage' );
365
366        return $this->getResult()->addValue( 'query', $property, $data );
367    }
368
369    protected function appendMagicWords( string $property ): bool {
370        $data = [];
371        foreach ( $this->contentLanguage->getMagicWords() as $name => $aliases ) {
372            $caseSensitive = (bool)array_shift( $aliases );
373            $arr = [
374                'name' => $name,
375                'aliases' => $aliases,
376                'case-sensitive' => $caseSensitive,
377            ];
378            ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
379            $data[] = $arr;
380        }
381        ApiResult::setIndexedTagName( $data, 'magicword' );
382
383        return $this->getResult()->addValue( 'query', $property, $data );
384    }
385
386    protected function appendInterwikiMap( string $property, ?string $filter ): bool {
387        $local = $filter ? $filter === 'local' : null;
388
389        $params = $this->extractRequestParams();
390        $langCode = $params['inlanguagecode'] ?? '';
391        $interwikiMagic = $this->getConfig()->get( MainConfigNames::InterwikiMagic );
392
393        if ( $interwikiMagic ) {
394            $langNames = $this->languageNameUtils->getLanguageNames( $langCode );
395        }
396
397        $extraLangPrefixes = $this->getConfig()->get( MainConfigNames::ExtraInterlanguageLinkPrefixes );
398        $extraLangCodeMap = $this->getConfig()->get( MainConfigNames::InterlanguageLinkCodeMap );
399        $localInterwikis = $this->getConfig()->get( MainConfigNames::LocalInterwikis );
400        $data = [];
401
402        foreach ( $this->interwikiLookup->getAllPrefixes( $local ) as $row ) {
403            $prefix = $row['iw_prefix'];
404            $val = [];
405            $val['prefix'] = $prefix;
406            if ( $row['iw_local'] ?? false ) {
407                $val['local'] = true;
408            }
409            if ( $row['iw_trans'] ?? false ) {
410                $val['trans'] = true;
411            }
412
413            if ( $interwikiMagic && isset( $langNames[$prefix] ) ) {
414                $val['language'] = $langNames[$prefix];
415                $standard = LanguageCode::replaceDeprecatedCodes( $prefix );
416                if ( $standard !== $prefix ) {
417                    # Note that even if this code is deprecated, it should
418                    # only be remapped if extralanglink (set below) is false.
419                    $val['deprecated'] = $standard;
420                }
421                $val['bcp47'] = LanguageCode::bcp47( $standard );
422            }
423            if ( in_array( $prefix, $localInterwikis ) ) {
424                $val['localinterwiki'] = true;
425            }
426            if ( $interwikiMagic && in_array( $prefix, $extraLangPrefixes ) ) {
427                $val['extralanglink'] = true;
428                $val['code'] = $extraLangCodeMap[$prefix] ?? $prefix;
429                $val['bcp47'] = LanguageCode::bcp47( $val['code'] );
430
431                $linktext = $this->msg( "interlanguage-link-$prefix" );
432                if ( !$linktext->isDisabled() ) {
433                    $val['linktext'] = $linktext->text();
434                }
435
436                $sitename = $this->msg( "interlanguage-link-sitename-$prefix" );
437                if ( !$sitename->isDisabled() ) {
438                    $val['sitename'] = $sitename->text();
439                }
440            }
441
442            $val['url'] = (string)$this->urlUtils->expand( $row['iw_url'], PROTO_CURRENT );
443            $val['protorel'] = str_starts_with( $row['iw_url'], '//' );
444            if ( ( $row['iw_wikiid'] ?? '' ) !== '' ) {
445                $val['wikiid'] = $row['iw_wikiid'];
446            }
447            if ( ( $row['iw_api'] ?? '' ) !== '' ) {
448                $val['api'] = $row['iw_api'];
449            }
450
451            $data[] = $val;
452        }
453
454        ApiResult::setIndexedTagName( $data, 'iw' );
455
456        return $this->getResult()->addValue( 'query', $property, $data );
457    }
458
459    protected function appendDbReplLagInfo( string $property, bool $includeAll ): bool {
460        $data = [];
461        $showHostnames = $this->getConfig()->get( MainConfigNames::ShowHostnames );
462        if ( $includeAll ) {
463            if ( !$showHostnames ) {
464                $this->dieWithError( 'apierror-siteinfo-includealldenied', 'includeAllDenied' );
465            }
466
467            foreach ( $this->loadBalancer->getLagTimes() as $i => $lag ) {
468                $data[] = [
469                    'host' => $this->loadBalancer->getServerName( $i ),
470                    'lag' => $lag
471                ];
472            }
473        } else {
474            [ , $lag, $index ] = $this->loadBalancer->getMaxLag();
475            $data[] = [
476                'host' => $showHostnames ? $this->loadBalancer->getServerName( $index ) : '',
477                'lag' => $lag
478            ];
479        }
480
481        ApiResult::setIndexedTagName( $data, 'db' );
482
483        return $this->getResult()->addValue( 'query', $property, $data );
484    }
485
486    protected function appendStatistics( string $property ): bool {
487        $data = [
488            'pages' => SiteStats::pages(),
489            'articles' => SiteStats::articles(),
490            'edits' => SiteStats::edits(),
491            'images' => SiteStats::images(),
492            'users' => SiteStats::users(),
493            'activeusers' => SiteStats::activeUsers(),
494            'admins' => SiteStats::numberingroup( 'sysop' ),
495            'jobs' => SiteStats::jobs(),
496        ];
497
498        $this->getHookRunner()->onAPIQuerySiteInfoStatisticsInfo( $data );
499
500        return $this->getResult()->addValue( 'query', $property, $data );
501    }
502
503    protected function appendUserGroups( string $property, bool $numberInGroup ): bool {
504        $config = $this->getConfig();
505
506        $data = [];
507        $result = $this->getResult();
508        $allGroups = $this->userGroupManager->listAllGroups();
509        $allImplicitGroups = $this->userGroupManager->listAllImplicitGroups();
510        foreach ( array_merge( $allImplicitGroups, $allGroups ) as $group ) {
511            $arr = [
512                'name' => $group,
513                'rights' => $this->groupPermissionsLookup->getGrantedPermissions( $group ),
514                // TODO: Also expose the list of revoked permissions somehow.
515            ];
516
517            if ( $numberInGroup ) {
518                $autopromote = $config->get( MainConfigNames::Autopromote );
519
520                if ( $group == 'user' ) {
521                    $arr['number'] = SiteStats::users();
522                // '*' and autopromote groups have no size
523                } elseif ( $group !== '*' && !isset( $autopromote[$group] ) ) {
524                    $arr['number'] = SiteStats::numberingroup( $group );
525                }
526            }
527
528            $groupArr = $this->userGroupManager->getGroupsChangeableByGroup( $group );
529
530            foreach ( $groupArr as $type => $groups ) {
531                $groups = array_values( array_intersect( $groups, $allGroups ) );
532                if ( $groups ) {
533                    $arr[$type] = $groups;
534                    ApiResult::setArrayType( $arr[$type], 'BCarray' );
535                    ApiResult::setIndexedTagName( $arr[$type], 'group' );
536                }
537            }
538
539            ApiResult::setIndexedTagName( $arr['rights'], 'permission' );
540            $data[] = $arr;
541        }
542
543        ApiResult::setIndexedTagName( $data, 'group' );
544
545        return $result->addValue( 'query', $property, $data );
546    }
547
548    protected function appendAutoCreateTempUser( string $property ): bool {
549        $data = [ 'enabled' => $this->tempUserConfig->isEnabled() ];
550        if ( $this->tempUserConfig->isKnown() ) {
551            $data['matchPatterns'] = $this->tempUserConfig->getMatchPatterns();
552        }
553        return $this->getResult()->addValue( 'query', $property, $data );
554    }
555
556    protected function appendFileExtensions( string $property ): bool {
557        $data = [];
558        foreach (
559            array_unique( $this->getConfig()->get( MainConfigNames::FileExtensions ) ) as $ext
560        ) {
561            $data[] = [ 'ext' => $ext ];
562        }
563        ApiResult::setIndexedTagName( $data, 'fe' );
564
565        return $this->getResult()->addValue( 'query', $property, $data );
566    }
567
568    protected function appendInstalledClientLibraries( string $property ): bool {
569        $data = [];
570        foreach ( SpecialVersion::parseForeignResources() as $name => $info ) {
571            $data[] = [
572                // Can't use $name as it is version suffixed (as multiple versions
573                // of a library may exist, provided by different skins/extensions)
574                'name' => $info['name'],
575                'version' => $info['version'],
576            ];
577        }
578        ApiResult::setIndexedTagName( $data, 'library' );
579        return $this->getResult()->addValue( 'query', $property, $data );
580    }
581
582    protected function appendInstalledLibraries( string $property ): bool {
583        $credits = SpecialVersion::getCredits(
584            ExtensionRegistry::getInstance(),
585            $this->getConfig()
586        );
587        $data = [];
588        foreach ( SpecialVersion::parseComposerInstalled( $credits ) as $name => $info ) {
589            if ( str_starts_with( $info['type'], 'mediawiki-' ) ) {
590                // Skip any extensions or skins since they'll be listed
591                // in their proper section
592                continue;
593            }
594            $data[] = [
595                'name' => $name,
596                'version' => $info['version'],
597            ];
598        }
599        ApiResult::setIndexedTagName( $data, 'library' );
600
601        return $this->getResult()->addValue( 'query', $property, $data );
602    }
603
604    protected function appendExtensions( string $property ): bool {
605        $data = [];
606        $credits = SpecialVersion::getCredits(
607            ExtensionRegistry::getInstance(),
608            $this->getConfig()
609        );
610        foreach ( $credits as $type => $extensions ) {
611            foreach ( $extensions as $ext ) {
612                $ret = [ 'type' => $type ];
613                if ( isset( $ext['name'] ) ) {
614                    $ret['name'] = $ext['name'];
615                }
616                if ( isset( $ext['namemsg'] ) ) {
617                    $ret['namemsg'] = $ext['namemsg'];
618                }
619                if ( isset( $ext['description'] ) ) {
620                    $ret['description'] = $ext['description'];
621                }
622                if ( isset( $ext['descriptionmsg'] ) ) {
623                    // Can be a string or [ key, param1, param2, ... ]
624                    if ( is_array( $ext['descriptionmsg'] ) ) {
625                        $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
626                        $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
627                        ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
628                    } else {
629                        $ret['descriptionmsg'] = $ext['descriptionmsg'];
630                    }
631                }
632                if ( isset( $ext['author'] ) ) {
633                    $ret['author'] = is_array( $ext['author'] ) ?
634                        implode( ', ', $ext['author'] ) : $ext['author'];
635                }
636                if ( isset( $ext['url'] ) ) {
637                    $ret['url'] = $ext['url'];
638                }
639                if ( isset( $ext['version'] ) ) {
640                    $ret['version'] = $ext['version'];
641                }
642                if ( isset( $ext['path'] ) ) {
643                    $extensionPath = dirname( $ext['path'] );
644                    $gitInfo = new GitInfo( $extensionPath );
645                    $vcsVersion = $gitInfo->getHeadSHA1();
646                    if ( $vcsVersion !== false ) {
647                        $ret['vcs-system'] = 'git';
648                        $ret['vcs-version'] = $vcsVersion;
649                        $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
650                        $vcsDate = $gitInfo->getHeadCommitDate();
651                        if ( $vcsDate !== false ) {
652                            $ret['vcs-date'] = wfTimestamp( TS::ISO_8601, $vcsDate );
653                        }
654                    }
655
656                    if ( ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
657                        $ret['license-name'] = $ext['license-name'] ?? '';
658                        $ret['license'] = SpecialPage::getTitleFor(
659                            'Version',
660                            "License/{$ext['name']}"
661                        )->getLinkURL();
662                    }
663
664                    if ( ExtensionInfo::getAuthorsFileName( $extensionPath ) ) {
665                        $ret['credits'] = SpecialPage::getTitleFor(
666                            'Version',
667                            "Credits/{$ext['name']}"
668                        )->getLinkURL();
669                    }
670                }
671                $data[] = $ret;
672            }
673        }
674
675        ApiResult::setIndexedTagName( $data, 'ext' );
676
677        return $this->getResult()->addValue( 'query', $property, $data );
678    }
679
680    protected function appendRightsInfo( string $property ): bool {
681        $config = $this->getConfig();
682        $title = Title::newFromText( $config->get( MainConfigNames::RightsPage ) );
683        if ( $title ) {
684            $url = $this->urlUtils->expand( $title->getLinkURL(), PROTO_CURRENT );
685        } else {
686            $url = $config->get( MainConfigNames::RightsUrl );
687        }
688        $text = $config->get( MainConfigNames::RightsText ) ?? '';
689        if ( $text === '' && $title ) {
690            $text = $title->getPrefixedText();
691        }
692
693        $data = [
694            'url' => (string)$url,
695            'text' => (string)$text,
696        ];
697
698        return $this->getResult()->addValue( 'query', $property, $data );
699    }
700
701    protected function appendRestrictions( string $property ): bool {
702        $config = $this->getConfig();
703        $data = [
704            'types' => $config->get( MainConfigNames::RestrictionTypes ),
705            'levels' => $config->get( MainConfigNames::RestrictionLevels ),
706            'cascadinglevels' => $config->get( MainConfigNames::CascadingRestrictionLevels ),
707            'semiprotectedlevels' => $config->get( MainConfigNames::SemiprotectedRestrictionLevels ),
708        ];
709
710        ApiResult::setArrayType( $data['types'], 'BCarray' );
711        ApiResult::setArrayType( $data['levels'], 'BCarray' );
712        ApiResult::setArrayType( $data['cascadinglevels'], 'BCarray' );
713        ApiResult::setArrayType( $data['semiprotectedlevels'], 'BCarray' );
714
715        ApiResult::setIndexedTagName( $data['types'], 'type' );
716        ApiResult::setIndexedTagName( $data['levels'], 'level' );
717        ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
718        ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
719
720        return $this->getResult()->addValue( 'query', $property, $data );
721    }
722
723    public function appendLanguages( string $property ): bool {
724        $params = $this->extractRequestParams();
725        $langCode = $params['inlanguagecode'] ?? '';
726        $langNames = $this->languageNameUtils->getLanguageNames( $langCode );
727
728        $data = [];
729
730        foreach ( $langNames as $code => $name ) {
731            $lang = [
732                'code' => $code,
733                'bcp47' => LanguageCode::bcp47( $code ),
734            ];
735            ApiResult::setContentValue( $lang, 'name', $name );
736            $data[] = $lang;
737        }
738        ApiResult::setIndexedTagName( $data, 'lang' );
739
740        return $this->getResult()->addValue( 'query', $property, $data );
741    }
742
743    // Export information about which page languages will trigger
744    // language conversion. (T153341)
745    public function appendLanguageVariants( string $property ): bool {
746        $langNames = $this->languageConverterFactory->isConversionDisabled() ? [] :
747            LanguageConverter::$languagesWithVariants;
748        sort( $langNames );
749
750        $data = [];
751        foreach ( $langNames as $langCode ) {
752            $lang = $this->languageFactory->getLanguage( $langCode );
753            $langConverter = $this->languageConverterFactory->getLanguageConverter( $lang );
754            if ( !$langConverter->hasVariants() ) {
755                // Only languages which have variants should be listed
756                continue;
757            }
758            $data[$langCode] = [];
759            ApiResult::setIndexedTagName( $data[$langCode], 'variant' );
760            ApiResult::setArrayType( $data[$langCode], 'kvp', 'code' );
761
762            $variants = $langConverter->getVariants();
763            sort( $variants );
764            foreach ( $variants as $v ) {
765                $data[$langCode][$v] = [
766                    'fallbacks' => (array)$langConverter->getVariantFallbacks( $v ),
767                ];
768                ApiResult::setIndexedTagName(
769                    $data[$langCode][$v]['fallbacks'], 'variant'
770                );
771            }
772        }
773        ApiResult::setIndexedTagName( $data, 'lang' );
774        ApiResult::setArrayType( $data, 'kvp', 'code' );
775
776        return $this->getResult()->addValue( 'query', $property, $data );
777    }
778
779    public function appendSkins( string $property ): bool {
780        $data = [];
781        $allowed = $this->skinFactory->getAllowedSkins();
782        $default = Skin::normalizeKey( 'default' );
783
784        foreach ( $this->skinFactory->getInstalledSkins() as $name => $displayName ) {
785            $msg = $this->msg( "skinname-{$name}" );
786            $code = $this->getParameter( 'inlanguagecode' );
787            if ( $code && $this->languageNameUtils->isValidCode( $code ) ) {
788                $msg->inLanguage( $code );
789            } else {
790                $msg->inContentLanguage();
791            }
792            if ( $msg->exists() ) {
793                $displayName = $msg->text();
794            }
795            $skin = [ 'code' => $name ];
796            ApiResult::setContentValue( $skin, 'name', $displayName );
797            if ( !isset( $allowed[$name] ) ) {
798                $skin['unusable'] = true;
799            }
800            if ( $name === $default ) {
801                $skin['default'] = true;
802            }
803            $data[] = $skin;
804        }
805        ApiResult::setIndexedTagName( $data, 'skin' );
806
807        return $this->getResult()->addValue( 'query', $property, $data );
808    }
809
810    public function appendExtensionTags( string $property ): bool {
811        $tags = array_map(
812            static fn ( $item ) => "<$item>",
813            $this->parserFactory->getMainInstance()->getTags()
814        );
815        ApiResult::setArrayType( $tags, 'BCarray' );
816        ApiResult::setIndexedTagName( $tags, 't' );
817
818        return $this->getResult()->addValue( 'query', $property, $tags );
819    }
820
821    public function appendFunctionHooks( string $property ): bool {
822        $hooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
823        ApiResult::setArrayType( $hooks, 'BCarray' );
824        ApiResult::setIndexedTagName( $hooks, 'h' );
825
826        return $this->getResult()->addValue( 'query', $property, $hooks );
827    }
828
829    public function appendVariables( string $property ): bool {
830        $variables = $this->magicWordFactory->getVariableIDs();
831        ApiResult::setArrayType( $variables, 'BCarray' );
832        ApiResult::setIndexedTagName( $variables, 'v' );
833
834        return $this->getResult()->addValue( 'query', $property, $variables );
835    }
836
837    public function appendDoubleUnderscores( string $property ): bool {
838        $ids = $this->magicWordFactory->getDoubleUnderscoreArray()->getNames();
839        ApiResult::setArrayType( $ids, 'BCarray' );
840        ApiResult::setIndexedTagName( $ids, 'v' );
841
842        return $this->getResult()->addValue( 'query', $property, $ids );
843    }
844
845    public function appendProtocols( string $property ): bool {
846        // Make a copy of the global so we don't try to set the _element key of it - T47130
847        $protocols = array_values( $this->getConfig()->get( MainConfigNames::UrlProtocols ) );
848        ApiResult::setArrayType( $protocols, 'BCarray' );
849        ApiResult::setIndexedTagName( $protocols, 'p' );
850
851        return $this->getResult()->addValue( 'query', $property, $protocols );
852    }
853
854    public function appendDefaultOptions( string $property ): bool {
855        $options = $this->userOptionsLookup->getDefaultOptions( null );
856        $options[ApiResult::META_BC_BOOLS] = array_keys( $options );
857        // Adjust thumbsize option, since for API purposes we're returning
858        // thumblimits as a non-sparse array. (T427066)
859        $options['thumbsize'] = array_search(
860            $options['thumbsize'],
861            array_keys( $this->getConfig()->get( MainConfigNames::ThumbLimits ) ),
862            true
863        );
864        return $this->getResult()->addValue( 'query', $property, $options );
865    }
866
867    public function appendUploadDialog( string $property ): bool {
868        $config = $this->getConfig()->get( MainConfigNames::UploadDialog );
869        return $this->getResult()->addValue( 'query', $property, $config );
870    }
871
872    private function getAutoPromoteConds(): array {
873        $allowedConditions = [];
874        foreach ( get_defined_constants() as $constantName => $constantValue ) {
875            if ( str_contains( $constantName, 'APCOND_' ) ) {
876                $allowedConditions[$constantName] = $constantValue;
877            }
878        }
879        return $allowedConditions;
880    }
881
882    private function processAutoPromote( array $input, array $allowedConditions ): array {
883        $data = [];
884        foreach ( $input as $groupName => $conditions ) {
885            $row = $this->recAutopromote( $conditions, $allowedConditions );
886            if ( !isset( $row[0] ) || is_string( $row ) ) {
887                $row = [ $row ];
888            }
889            $data[$groupName] = $row;
890        }
891        return $data;
892    }
893
894    private function appendAutoPromote( string $property ): bool {
895        return $this->getResult()->addValue(
896            'query',
897            $property,
898            $this->processAutoPromote(
899                $this->getConfig()->get( MainConfigNames::Autopromote ),
900                $this->getAutoPromoteConds()
901            )
902        );
903    }
904
905    private function appendAutoPromoteOnce( string $property ): bool {
906        $allowedConditions = $this->getAutoPromoteConds();
907        $data = [];
908        foreach ( $this->getConfig()->get( MainConfigNames::AutopromoteOnce ) as $key => $value ) {
909            $data[$key] = $this->processAutoPromote( $value, $allowedConditions );
910        }
911        return $this->getResult()->addValue( 'query', $property, $data );
912    }
913
914    private function appendCopyUploadDomains( string $property ): bool {
915        $allowedHosts = UploadFromUrl::getAllowedHosts();
916        ApiResult::setIndexedTagName( $allowedHosts, 'domain' );
917        return $this->getResult()->addValue(
918            'query',
919            $property,
920            $allowedHosts
921        );
922    }
923
924    private function appendCrossSiteAJAXdomains( string $property ): bool {
925        $allowedDomains = $this->getConfig()->get( MainConfigNames::CrossSiteAJAXdomains );
926        ApiResult::setArrayType( $allowedDomains, 'array' );
927        ApiResult::setIndexedTagName( $allowedDomains, 'domain' );
928        return $this->getResult()->addValue( 'query', $property, $allowedDomains );
929    }
930
931    protected function appendSBOM( string $property ): bool {
932        if ( !$this->userIdentityUtils->isNamed( $this->getUser() ) ) {
933            $this->dieWithError( 'apierror-mustbeloggedin-sbom' );
934        }
935        $data = $this->sbomGenerator->generateCdxSBOM( $this->getContext() );
936        return $this->getResult()->addValue( 'query', $property, $data );
937    }
938
939    /**
940     * @param array|int|string $cond
941     * @param array $allowedConditions
942     * @return array|string
943     */
944    private function recAutopromote( $cond, $allowedConditions ) {
945        $config = [];
946        // First, checks if $cond is an array
947        if ( is_array( $cond ) ) {
948            // Checks if $cond[0] is a valid operand
949            if ( in_array( $cond[0], UserRequirementsConditionChecker::VALID_OPS, true ) ) {
950                $config['operand'] = $cond[0];
951                // Traversal checks conditions
952                foreach ( array_slice( $cond, 1 ) as $value ) {
953                    $config[] = $this->recAutopromote( $value, $allowedConditions );
954                }
955            } elseif ( is_string( $cond[0] ) ) {
956                // Returns $cond directly, if $cond[0] is a string
957                $config = $cond;
958            } else {
959                // When $cond is equal to an APCOND_ constant value
960                $params = array_slice( $cond, 1 );
961                if ( $params === [ null ] ) {
962                    // Special casing for these conditions and their default of null,
963                    // to replace their values with $wgAutoConfirmCount/$wgAutoConfirmAge as appropriate
964                    if ( $cond[0] === APCOND_EDITCOUNT ) {
965                        $params = [ $this->getConfig()->get( MainConfigNames::AutoConfirmCount ) ];
966                    } elseif ( $cond[0] === APCOND_AGE ) {
967                        $params = [ $this->getConfig()->get( MainConfigNames::AutoConfirmAge ) ];
968                    }
969                }
970                $config = [
971                    'condname' => array_search( $cond[0], $allowedConditions ),
972                    'params' => $params
973                ];
974                ApiResult::setIndexedTagName( $config, 'params' );
975            }
976        } elseif ( is_string( $cond ) ) {
977            $config = $cond;
978        } else {
979            // When $cond is equal to an APCOND_ constant value
980            $config = [
981                'condname' => array_search( $cond, $allowedConditions ),
982                'params' => []
983            ];
984            ApiResult::setIndexedTagName( $config, 'params' );
985        }
986
987        return $config;
988    }
989
990    public function appendSubscribedHooks( string $property ): bool {
991        $hookNames = $this->hookContainer->getHookNames();
992        sort( $hookNames );
993
994        $data = [];
995        foreach ( $hookNames as $name ) {
996            $arr = [
997                'name' => $name,
998                'subscribers' => $this->hookContainer->getHandlerDescriptions( $name ),
999            ];
1000
1001            ApiResult::setArrayType( $arr['subscribers'], 'array' );
1002            ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
1003            $data[] = $arr;
1004        }
1005
1006        ApiResult::setIndexedTagName( $data, 'hook' );
1007
1008        return $this->getResult()->addValue( 'query', $property, $data );
1009    }
1010
1011    /** @inheritDoc */
1012    public function getCacheMode( $params ) {
1013        // Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
1014        if ( $this->getConfig()->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) &&
1015            in_array( 'interwikimap', $params['prop'] ?? [] )
1016        ) {
1017            return 'anon-public-user-private';
1018        }
1019
1020        return 'public';
1021    }
1022
1023    /** @inheritDoc */
1024    public function getAllowedParams() {
1025        return [
1026            'prop' => [
1027                ParamValidator::PARAM_DEFAULT => 'general',
1028                ParamValidator::PARAM_ISMULTI => true,
1029                ParamValidator::PARAM_TYPE => [
1030                    'general',
1031                    'namespaces',
1032                    'namespacealiases',
1033                    'specialpagealiases',
1034                    'magicwords',
1035                    'interwikimap',
1036                    'dbrepllag',
1037                    'statistics',
1038                    'usergroups',
1039                    'autocreatetempuser',
1040                    'clientlibraries',
1041                    'libraries',
1042                    'extensions',
1043                    'fileextensions',
1044                    'rightsinfo',
1045                    'restrictions',
1046                    'languages',
1047                    'languagevariants',
1048                    'skins',
1049                    'extensiontags',
1050                    'functionhooks',
1051                    'showhooks',
1052                    'variables',
1053                    'doubleunderscores',
1054                    'protocols',
1055                    'defaultoptions',
1056                    'uploaddialog',
1057                    'autopromote',
1058                    'autopromoteonce',
1059                    'copyuploaddomains',
1060                    'crosssiteajaxdomains',
1061                    'sbom',
1062                ],
1063                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
1064            ],
1065            'filteriw' => [
1066                ParamValidator::PARAM_TYPE => [
1067                    'local',
1068                    '!local',
1069                ]
1070            ],
1071            'showalldb' => false,
1072            'numberingroup' => false,
1073            'inlanguagecode' => null,
1074        ];
1075    }
1076
1077    /** @inheritDoc */
1078    protected function getExamplesMessages() {
1079        return [
1080            'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
1081                => 'apihelp-query+siteinfo-example-simple',
1082            'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
1083                => 'apihelp-query+siteinfo-example-interwiki',
1084            'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
1085                => 'apihelp-query+siteinfo-example-replag',
1086        ];
1087    }
1088
1089    /** @inheritDoc */
1090    public function getHelpUrls() {
1091        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Siteinfo';
1092    }
1093}
1094
1095/** @deprecated class alias since 1.43 */
1096class_alias( ApiQuerySiteinfo::class, 'ApiQuerySiteinfo' );