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