3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
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.
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.
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
23 namespace MediaWiki\Api
;
25 use MediaWiki\HookContainer\HookContainer
;
26 use MediaWiki\Interwiki\InterwikiLookup
;
27 use MediaWiki\Language\Language
;
28 use MediaWiki\Language\LanguageCode
;
29 use MediaWiki\Language\LanguageConverter
;
30 use MediaWiki\Languages\LanguageConverterFactory
;
31 use MediaWiki\Languages\LanguageFactory
;
32 use MediaWiki\Languages\LanguageNameUtils
;
33 use MediaWiki\MainConfigNames
;
34 use MediaWiki\Parser\MagicWordFactory
;
35 use MediaWiki\Parser\ParserFactory
;
36 use MediaWiki\Registration\ExtensionRegistry
;
37 use MediaWiki\ResourceLoader\SkinModule
;
38 use MediaWiki\SiteStats\SiteStats
;
39 use MediaWiki\SpecialPage\SpecialPage
;
40 use MediaWiki\SpecialPage\SpecialPageFactory
;
41 use MediaWiki\Specials\SpecialVersion
;
42 use MediaWiki\Title\NamespaceInfo
;
43 use MediaWiki\Title\Title
;
44 use MediaWiki\User\Options\UserOptionsLookup
;
45 use MediaWiki\User\TempUser\TempUserConfig
;
46 use MediaWiki\User\UserGroupManager
;
47 use MediaWiki\Utils\ExtensionInfo
;
48 use MediaWiki\Utils\GitInfo
;
49 use MediaWiki\Utils\UrlUtils
;
50 use MediaWiki\WikiMap\WikiMap
;
54 use Wikimedia\Composer\ComposerInstalled
;
55 use Wikimedia\ParamValidator\ParamValidator
;
56 use Wikimedia\Rdbms\ILoadBalancer
;
57 use Wikimedia\Rdbms\ReadOnlyMode
;
60 * A query action to return meta information about the wiki site.
64 class ApiQuerySiteinfo
extends ApiQueryBase
{
66 private UserOptionsLookup
$userOptionsLookup;
67 private UserGroupManager
$userGroupManager;
68 private HookContainer
$hookContainer;
69 private LanguageConverterFactory
$languageConverterFactory;
70 private LanguageFactory
$languageFactory;
71 private LanguageNameUtils
$languageNameUtils;
72 private Language
$contentLanguage;
73 private NamespaceInfo
$namespaceInfo;
74 private InterwikiLookup
$interwikiLookup;
75 private ParserFactory
$parserFactory;
76 private MagicWordFactory
$magicWordFactory;
77 private SpecialPageFactory
$specialPageFactory;
78 private SkinFactory
$skinFactory;
79 private ILoadBalancer
$loadBalancer;
80 private ReadOnlyMode
$readOnlyMode;
81 private UrlUtils
$urlUtils;
82 private TempUserConfig
$tempUserConfig;
84 public function __construct(
87 UserOptionsLookup
$userOptionsLookup,
88 UserGroupManager
$userGroupManager,
89 HookContainer
$hookContainer,
90 LanguageConverterFactory
$languageConverterFactory,
91 LanguageFactory
$languageFactory,
92 LanguageNameUtils
$languageNameUtils,
93 Language
$contentLanguage,
94 NamespaceInfo
$namespaceInfo,
95 InterwikiLookup
$interwikiLookup,
96 ParserFactory
$parserFactory,
97 MagicWordFactory
$magicWordFactory,
98 SpecialPageFactory
$specialPageFactory,
99 SkinFactory
$skinFactory,
100 ILoadBalancer
$loadBalancer,
101 ReadOnlyMode
$readOnlyMode,
103 TempUserConfig
$tempUserConfig
105 parent
::__construct( $query, $moduleName, 'si' );
106 $this->userOptionsLookup
= $userOptionsLookup;
107 $this->userGroupManager
= $userGroupManager;
108 $this->hookContainer
= $hookContainer;
109 $this->languageConverterFactory
= $languageConverterFactory;
110 $this->languageFactory
= $languageFactory;
111 $this->languageNameUtils
= $languageNameUtils;
112 $this->contentLanguage
= $contentLanguage;
113 $this->namespaceInfo
= $namespaceInfo;
114 $this->interwikiLookup
= $interwikiLookup;
115 $this->parserFactory
= $parserFactory;
116 $this->magicWordFactory
= $magicWordFactory;
117 $this->specialPageFactory
= $specialPageFactory;
118 $this->skinFactory
= $skinFactory;
119 $this->loadBalancer
= $loadBalancer;
120 $this->readOnlyMode
= $readOnlyMode;
121 $this->urlUtils
= $urlUtils;
122 $this->tempUserConfig
= $tempUserConfig;
125 public function execute() {
126 $params = $this->extractRequestParams();
128 foreach ( $params['prop'] as $p ) {
131 $fit = $this->appendGeneralInfo( $p );
134 $fit = $this->appendNamespaces( $p );
136 case 'namespacealiases':
137 $fit = $this->appendNamespaceAliases( $p );
139 case 'specialpagealiases':
140 $fit = $this->appendSpecialPageAliases( $p );
143 $fit = $this->appendMagicWords( $p );
146 $fit = $this->appendInterwikiMap( $p, $params['filteriw'] );
149 $fit = $this->appendDbReplLagInfo( $p, $params['showalldb'] );
152 $fit = $this->appendStatistics( $p );
155 $fit = $this->appendUserGroups( $p, $params['numberingroup'] );
157 case 'autocreatetempuser':
158 $fit = $this->appendAutoCreateTempUser( $p );
160 case 'clientlibraries':
161 $fit = $this->appendInstalledClientLibraries( $p );
164 $fit = $this->appendInstalledLibraries( $p );
167 $fit = $this->appendExtensions( $p );
169 case 'fileextensions':
170 $fit = $this->appendFileExtensions( $p );
173 $fit = $this->appendRightsInfo( $p );
176 $fit = $this->appendRestrictions( $p );
179 $fit = $this->appendLanguages( $p );
181 case 'languagevariants':
182 $fit = $this->appendLanguageVariants( $p );
185 $fit = $this->appendSkins( $p );
187 case 'extensiontags':
188 $fit = $this->appendExtensionTags( $p );
190 case 'functionhooks':
191 $fit = $this->appendFunctionHooks( $p );
194 $fit = $this->appendSubscribedHooks( $p );
197 $fit = $this->appendVariables( $p );
200 $fit = $this->appendProtocols( $p );
202 case 'defaultoptions':
203 $fit = $this->appendDefaultOptions( $p );
206 $fit = $this->appendUploadDialog( $p );
209 $fit = $this->appendAutoPromote( $p );
211 case 'autopromoteonce':
212 $fit = $this->appendAutoPromoteOnce( $p );
215 ApiBase
::dieDebug( __METHOD__
, "Unknown prop=$p" ); // @codeCoverageIgnore
218 // Abuse siprop as a query-continue parameter
219 // and set it to all unprocessed props
220 $this->setContinueEnumParameter( 'prop', implode( '|',
221 array_diff( $params['prop'], $done ) ) );
228 protected function appendGeneralInfo( $property ) {
229 $config = $this->getConfig();
230 $mainPage = Title
::newMainPage();
231 $logo = SkinModule
::getAvailableLogos( $config, $this->getLanguage()->getCode() );
234 'mainpage' => $mainPage->getPrefixedText(),
235 'base' => (string)$this->urlUtils
->expand( $mainPage->getFullURL(), PROTO_CURRENT
),
236 'sitename' => $config->get( MainConfigNames
::Sitename
),
237 'mainpageisdomainroot' => (bool)$config->get( MainConfigNames
::MainPageIsDomainRoot
),
239 // A logo can either be a relative or an absolute path
240 // make sure we always return an absolute path
241 'logo' => (string)$this->urlUtils
->expand( $logo['1x'], PROTO_RELATIVE
),
243 'generator' => 'MediaWiki ' . MW_VERSION
,
245 'phpversion' => PHP_VERSION
,
246 'phpsapi' => PHP_SAPI
,
247 'dbtype' => $config->get( MainConfigNames
::DBtype
),
248 'dbversion' => $this->getDB()->getServerVersion(),
252 $allowException = true;
253 if ( !$config->get( MainConfigNames
::AllowExternalImages
) ) {
254 $data['imagewhitelistenabled'] =
255 (bool)$config->get( MainConfigNames
::EnableImageWhitelist
);
256 $allowFrom = $config->get( MainConfigNames
::AllowExternalImagesFrom
);
257 $allowException = (bool)$allowFrom;
259 if ( $allowException ) {
260 $data['externalimages'] = (array)$allowFrom;
261 ApiResult
::setIndexedTagName( $data['externalimages'], 'prefix' );
264 $data['langconversion'] = !$this->languageConverterFactory
->isConversionDisabled();
265 $data['linkconversion'] = !$this->languageConverterFactory
->isLinkConversionDisabled();
266 // For backwards compatibility (soft deprecated since MW 1.36)
267 $data['titleconversion'] = $data['linkconversion'];
269 $contLangConverter = $this->languageConverterFactory
->getLanguageConverter( $this->contentLanguage
);
270 if ( $this->contentLanguage
->linkPrefixExtension() ) {
271 $linkPrefixCharset = $this->contentLanguage
->linkPrefixCharset();
272 $data['linkprefixcharset'] = $linkPrefixCharset;
273 // For backwards compatibility
274 $data['linkprefix'] = "/^((?>.*[^$linkPrefixCharset]|))(.+)$/sDu";
276 $data['linkprefixcharset'] = '';
277 $data['linkprefix'] = '';
280 $data['linktrail'] = $this->contentLanguage
->linkTrail() ?
: '';
282 $data['legaltitlechars'] = Title
::legalChars();
283 $data['invalidusernamechars'] = $config->get( MainConfigNames
::InvalidUsernameCharacters
);
285 $data['allunicodefixes'] = (bool)$config->get( MainConfigNames
::AllUnicodeFixes
);
286 $data['fixarabicunicode'] = true; // Config removed in 1.35, always true
287 $data['fixmalayalamunicode'] = true; // Config removed in 1.35, always true
289 $git = GitInfo
::repo()->getHeadSHA1();
291 $data['git-hash'] = $git;
292 $data['git-branch'] = GitInfo
::repo()->getCurrentBranch();
295 // 'case-insensitive' option is reserved for future
297 $config->get( MainConfigNames
::CapitalLinks
) ?
'first-letter' : 'case-sensitive';
298 $data['lang'] = $config->get( MainConfigNames
::LanguageCode
);
300 $data['fallback'] = [];
301 foreach ( $this->contentLanguage
->getFallbackLanguages() as $code ) {
302 $data['fallback'][] = [ 'code' => $code ];
304 ApiResult
::setIndexedTagName( $data['fallback'], 'lang' );
306 if ( $contLangConverter->hasVariants() ) {
307 $data['variants'] = [];
308 foreach ( $contLangConverter->getVariants() as $code ) {
309 $data['variants'][] = [
311 'name' => $this->contentLanguage
->getVariantname( $code ),
314 ApiResult
::setIndexedTagName( $data['variants'], 'lang' );
317 $data['rtl'] = $this->contentLanguage
->isRTL();
318 $data['fallback8bitEncoding'] = $this->contentLanguage
->fallback8bitEncoding();
320 $data['readonly'] = $this->readOnlyMode
->isReadOnly();
321 if ( $data['readonly'] ) {
322 $data['readonlyreason'] = $this->readOnlyMode
->getReason();
324 $data['writeapi'] = true; // Deprecated since MW 1.32
326 $data['maxarticlesize'] = $config->get( MainConfigNames
::MaxArticleSize
) * 1024;
328 $data['timezone'] = $config->get( MainConfigNames
::Localtimezone
);
329 $data['timeoffset'] = (int)( $config->get( MainConfigNames
::LocalTZoffset
) );
330 $data['articlepath'] = $config->get( MainConfigNames
::ArticlePath
);
331 $data['scriptpath'] = $config->get( MainConfigNames
::ScriptPath
);
332 $data['script'] = $config->get( MainConfigNames
::Script
);
333 $data['variantarticlepath'] = $config->get( MainConfigNames
::VariantArticlePath
);
334 $data[ApiResult
::META_BC_BOOLS
][] = 'variantarticlepath';
335 $data['server'] = $config->get( MainConfigNames
::Server
);
336 $data['servername'] = $config->get( MainConfigNames
::ServerName
);
337 $data['wikiid'] = WikiMap
::getCurrentWikiId();
338 $data['time'] = wfTimestamp( TS_ISO_8601
, time() );
340 $data['misermode'] = (bool)$config->get( MainConfigNames
::MiserMode
);
342 $data['uploadsenabled'] = UploadBase
::isEnabled();
343 $data['maxuploadsize'] = UploadBase
::getMaxUploadSize();
344 $data['minuploadchunksize'] = ApiUpload
::getMinUploadChunkSize( $config );
346 $data['galleryoptions'] = $config->get( MainConfigNames
::GalleryOptions
);
348 $data['thumblimits'] = $config->get( MainConfigNames
::ThumbLimits
);
349 ApiResult
::setArrayType( $data['thumblimits'], 'BCassoc' );
350 ApiResult
::setIndexedTagName( $data['thumblimits'], 'limit' );
351 $data['imagelimits'] = [];
352 foreach ( $config->get( MainConfigNames
::ImageLimits
) as $k => $limit ) {
353 $data['imagelimits'][$k] = [ 'width' => $limit[0], 'height' => $limit[1] ];
355 ApiResult
::setArrayType( $data['imagelimits'], 'BCassoc' );
356 ApiResult
::setIndexedTagName( $data['imagelimits'], 'limit' );
358 $favicon = $config->get( MainConfigNames
::Favicon
);
360 // Expand any local path to full URL to improve API usability (T77093).
361 $data['favicon'] = (string)$this->urlUtils
->expand( $favicon );
364 $data['centralidlookupprovider'] = $config->get( MainConfigNames
::CentralIdLookupProvider
);
365 $providerIds = array_keys( $config->get( MainConfigNames
::CentralIdLookupProviders
) );
366 $data['allcentralidlookupproviders'] = $providerIds;
368 $data['interwikimagic'] = (bool)$config->get( MainConfigNames
::InterwikiMagic
);
369 $data['magiclinks'] = $config->get( MainConfigNames
::EnableMagicLinks
);
371 $data['categorycollation'] = $config->get( MainConfigNames
::CategoryCollation
);
373 $data['nofollowlinks'] = $config->get( MainConfigNames
::NoFollowLinks
);
374 $data['nofollownsexceptions'] = $config->get( MainConfigNames
::NoFollowNsExceptions
);
375 $data['nofollowdomainexceptions'] = $config->get( MainConfigNames
::NoFollowDomainExceptions
);
376 $data['externallinktarget'] = $config->get( MainConfigNames
::ExternalLinkTarget
);
378 $this->getHookRunner()->onAPIQuerySiteInfoGeneralInfo( $this, $data );
380 return $this->getResult()->addValue( 'query', $property, $data );
383 protected function appendNamespaces( $property ) {
384 $nsProtection = $this->getConfig()->get( MainConfigNames
::NamespaceProtection
);
386 $data = [ ApiResult
::META_TYPE
=> 'assoc' ];
387 foreach ( $this->contentLanguage
->getFormattedNamespaces() as $ns => $title ) {
390 'case' => $this->namespaceInfo
->isCapitalized( $ns ) ?
'first-letter' : 'case-sensitive',
392 ApiResult
::setContentValue( $data[$ns], 'name', $title );
393 $canonical = $this->namespaceInfo
->getCanonicalName( $ns );
395 $data[$ns]['subpages'] = $this->namespaceInfo
->hasSubpages( $ns );
398 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
401 $data[$ns]['content'] = $this->namespaceInfo
->isContent( $ns );
402 $data[$ns]['nonincludable'] = $this->namespaceInfo
->isNonincludable( $ns );
404 $specificNs = $nsProtection[$ns] ??
'';
405 if ( is_array( $specificNs ) ) {
406 $specificNs = implode( "|", array_filter( $specificNs ) );
408 if ( $specificNs !== '' ) {
409 $data[$ns]['namespaceprotection'] = $specificNs;
412 $contentmodel = $this->namespaceInfo
->getNamespaceContentModel( $ns );
413 if ( $contentmodel ) {
414 $data[$ns]['defaultcontentmodel'] = $contentmodel;
418 ApiResult
::setArrayType( $data, 'assoc' );
419 ApiResult
::setIndexedTagName( $data, 'ns' );
421 return $this->getResult()->addValue( 'query', $property, $data );
424 protected function appendNamespaceAliases( $property ) {
425 $aliases = $this->contentLanguage
->getNamespaceAliases();
426 $namespaces = $this->contentLanguage
->getNamespaces();
428 foreach ( $aliases as $title => $ns ) {
429 if ( $namespaces[$ns] == $title ) {
430 // Don't list duplicates
433 $item = [ 'id' => (int)$ns ];
434 ApiResult
::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
440 ApiResult
::setIndexedTagName( $data, 'ns' );
442 return $this->getResult()->addValue( 'query', $property, $data );
445 protected function appendSpecialPageAliases( $property ) {
447 $aliases = $this->contentLanguage
->getSpecialPageAliases();
448 foreach ( $this->specialPageFactory
->getNames() as $specialpage ) {
449 if ( isset( $aliases[$specialpage] ) ) {
450 $arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ];
451 ApiResult
::setIndexedTagName( $arr['aliases'], 'alias' );
455 ApiResult
::setIndexedTagName( $data, 'specialpage' );
457 return $this->getResult()->addValue( 'query', $property, $data );
460 protected function appendMagicWords( $property ) {
462 foreach ( $this->contentLanguage
->getMagicWords() as $name => $aliases ) {
463 $caseSensitive = (bool)array_shift( $aliases );
466 'aliases' => $aliases,
467 'case-sensitive' => $caseSensitive,
469 ApiResult
::setIndexedTagName( $arr['aliases'], 'alias' );
472 ApiResult
::setIndexedTagName( $data, 'magicword' );
474 return $this->getResult()->addValue( 'query', $property, $data );
477 protected function appendInterwikiMap( $property, $filter ) {
478 $local = $filter ?
$filter === 'local' : null;
480 $params = $this->extractRequestParams();
481 $langCode = $params['inlanguagecode'] ??
'';
482 $interwikiMagic = $this->getConfig()->get( MainConfigNames
::InterwikiMagic
);
484 if ( $interwikiMagic ) {
485 $langNames = $this->languageNameUtils
->getLanguageNames( $langCode );
488 $extraLangPrefixes = $this->getConfig()->get( MainConfigNames
::ExtraInterlanguageLinkPrefixes
);
489 $extraLangCodeMap = $this->getConfig()->get( MainConfigNames
::InterlanguageLinkCodeMap
);
490 $localInterwikis = $this->getConfig()->get( MainConfigNames
::LocalInterwikis
);
493 foreach ( $this->interwikiLookup
->getAllPrefixes( $local ) as $row ) {
494 $prefix = $row['iw_prefix'];
496 $val['prefix'] = $prefix;
497 if ( $row['iw_local'] ??
false ) {
498 $val['local'] = true;
500 if ( $row['iw_trans'] ??
false ) {
501 $val['trans'] = true;
504 if ( $interwikiMagic && isset( $langNames[$prefix] ) ) {
505 $val['language'] = $langNames[$prefix];
506 $standard = LanguageCode
::replaceDeprecatedCodes( $prefix );
507 if ( $standard !== $prefix ) {
508 # Note that even if this code is deprecated, it should
509 # only be remapped if extralanglink (set below) is false.
510 $val['deprecated'] = $standard;
512 $val['bcp47'] = LanguageCode
::bcp47( $standard );
514 if ( in_array( $prefix, $localInterwikis ) ) {
515 $val['localinterwiki'] = true;
517 if ( $interwikiMagic && in_array( $prefix, $extraLangPrefixes ) ) {
518 $val['extralanglink'] = true;
519 $val['code'] = $extraLangCodeMap[$prefix] ??
$prefix;
520 $val['bcp47'] = LanguageCode
::bcp47( $val['code'] );
522 $linktext = $this->msg( "interlanguage-link-$prefix" );
523 if ( !$linktext->isDisabled() ) {
524 $val['linktext'] = $linktext->text();
527 $sitename = $this->msg( "interlanguage-link-sitename-$prefix" );
528 if ( !$sitename->isDisabled() ) {
529 $val['sitename'] = $sitename->text();
533 $val['url'] = (string)$this->urlUtils
->expand( $row['iw_url'], PROTO_CURRENT
);
534 $val['protorel'] = str_starts_with( $row['iw_url'], '//' );
535 if ( ( $row['iw_wikiid'] ??
'' ) !== '' ) {
536 $val['wikiid'] = $row['iw_wikiid'];
538 if ( ( $row['iw_api'] ??
'' ) !== '' ) {
539 $val['api'] = $row['iw_api'];
545 ApiResult
::setIndexedTagName( $data, 'iw' );
547 return $this->getResult()->addValue( 'query', $property, $data );
550 protected function appendDbReplLagInfo( $property, $includeAll ) {
552 $showHostnames = $this->getConfig()->get( MainConfigNames
::ShowHostnames
);
554 if ( !$showHostnames ) {
555 $this->dieWithError( 'apierror-siteinfo-includealldenied', 'includeAllDenied' );
558 foreach ( $this->loadBalancer
->getLagTimes() as $i => $lag ) {
560 'host' => $this->loadBalancer
->getServerName( $i ),
565 [ , $lag, $index ] = $this->loadBalancer
->getMaxLag();
567 'host' => $showHostnames ?
$this->loadBalancer
->getServerName( $index ) : '',
572 ApiResult
::setIndexedTagName( $data, 'db' );
574 return $this->getResult()->addValue( 'query', $property, $data );
577 protected function appendStatistics( $property ) {
579 'pages' => SiteStats
::pages(),
580 'articles' => SiteStats
::articles(),
581 'edits' => SiteStats
::edits(),
582 'images' => SiteStats
::images(),
583 'users' => SiteStats
::users(),
584 'activeusers' => SiteStats
::activeUsers(),
585 'admins' => SiteStats
::numberingroup( 'sysop' ),
586 'jobs' => SiteStats
::jobs(),
589 $this->getHookRunner()->onAPIQuerySiteInfoStatisticsInfo( $data );
591 return $this->getResult()->addValue( 'query', $property, $data );
594 protected function appendUserGroups( $property, $numberInGroup ) {
595 $config = $this->getConfig();
598 $result = $this->getResult();
599 $allGroups = array_values( $this->userGroupManager
->listAllGroups() );
600 foreach ( $config->get( MainConfigNames
::GroupPermissions
) as $group => $permissions ) {
603 'rights' => array_keys( $permissions, true ),
606 if ( $numberInGroup ) {
607 $autopromote = $config->get( MainConfigNames
::Autopromote
);
609 if ( $group == 'user' ) {
610 $arr['number'] = SiteStats
::users();
611 // '*' and autopromote groups have no size
612 } elseif ( $group !== '*' && !isset( $autopromote[$group] ) ) {
613 $arr['number'] = SiteStats
::numberingroup( $group );
618 'add' => $config->get( MainConfigNames
::AddGroups
),
619 'remove' => $config->get( MainConfigNames
::RemoveGroups
),
620 'add-self' => $config->get( MainConfigNames
::GroupsAddToSelf
),
621 'remove-self' => $config->get( MainConfigNames
::GroupsRemoveFromSelf
)
624 foreach ( $groupArr as $type => $rights ) {
625 if ( isset( $rights[$group] ) ) {
626 if ( $rights[$group] === true ) {
627 $groups = $allGroups;
629 $groups = array_intersect( $rights[$group], $allGroups );
632 $arr[$type] = $groups;
633 ApiResult
::setArrayType( $arr[$type], 'BCarray' );
634 ApiResult
::setIndexedTagName( $arr[$type], 'group' );
639 ApiResult
::setIndexedTagName( $arr['rights'], 'permission' );
643 ApiResult
::setIndexedTagName( $data, 'group' );
645 return $result->addValue( 'query', $property, $data );
648 protected function appendAutoCreateTempUser( $property ) {
649 $data = [ 'enabled' => $this->tempUserConfig
->isEnabled() ];
650 if ( $this->tempUserConfig
->isKnown() ) {
651 $data['matchPatterns'] = $this->tempUserConfig
->getMatchPatterns();
653 return $this->getResult()->addValue( 'query', $property, $data );
656 protected function appendFileExtensions( $property ) {
659 array_unique( $this->getConfig()->get( MainConfigNames
::FileExtensions
) ) as $ext
661 $data[] = [ 'ext' => $ext ];
663 ApiResult
::setIndexedTagName( $data, 'fe' );
665 return $this->getResult()->addValue( 'query', $property, $data );
668 protected function appendInstalledClientLibraries( $property ) {
670 foreach ( SpecialVersion
::parseForeignResources() as $name => $info ) {
672 // Can't use $name as it is version suffixed (as multiple versions
673 // of a library may exist, provided by different skins/extensions)
674 'name' => $info['name'],
675 'version' => $info['version'],
678 ApiResult
::setIndexedTagName( $data, 'library' );
679 return $this->getResult()->addValue( 'query', $property, $data );
682 protected function appendInstalledLibraries( $property ) {
683 $path = MW_INSTALL_PATH
. '/vendor/composer/installed.json';
684 if ( !file_exists( $path ) ) {
689 $installed = new ComposerInstalled( $path );
690 foreach ( $installed->getInstalledDependencies() as $name => $info ) {
691 if ( str_starts_with( $info['type'], 'mediawiki-' ) ) {
692 // Skip any extensions or skins since they'll be listed
693 // in their proper section
698 'version' => $info['version'],
701 ApiResult
::setIndexedTagName( $data, 'library' );
703 return $this->getResult()->addValue( 'query', $property, $data );
706 protected function appendExtensions( $property ) {
708 $credits = SpecialVersion
::getCredits(
709 ExtensionRegistry
::getInstance(),
712 foreach ( $credits as $type => $extensions ) {
713 foreach ( $extensions as $ext ) {
714 $ret = [ 'type' => $type ];
715 if ( isset( $ext['name'] ) ) {
716 $ret['name'] = $ext['name'];
718 if ( isset( $ext['namemsg'] ) ) {
719 $ret['namemsg'] = $ext['namemsg'];
721 if ( isset( $ext['description'] ) ) {
722 $ret['description'] = $ext['description'];
724 if ( isset( $ext['descriptionmsg'] ) ) {
725 // Can be a string or [ key, param1, param2, ... ]
726 if ( is_array( $ext['descriptionmsg'] ) ) {
727 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
728 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
729 ApiResult
::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
731 $ret['descriptionmsg'] = $ext['descriptionmsg'];
734 if ( isset( $ext['author'] ) ) {
735 $ret['author'] = is_array( $ext['author'] ) ?
736 implode( ', ', $ext['author'] ) : $ext['author'];
738 if ( isset( $ext['url'] ) ) {
739 $ret['url'] = $ext['url'];
741 if ( isset( $ext['version'] ) ) {
742 $ret['version'] = $ext['version'];
744 if ( isset( $ext['path'] ) ) {
745 $extensionPath = dirname( $ext['path'] );
746 $gitInfo = new GitInfo( $extensionPath );
747 $vcsVersion = $gitInfo->getHeadSHA1();
748 if ( $vcsVersion !== false ) {
749 $ret['vcs-system'] = 'git';
750 $ret['vcs-version'] = $vcsVersion;
751 $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
752 $vcsDate = $gitInfo->getHeadCommitDate();
753 if ( $vcsDate !== false ) {
754 $ret['vcs-date'] = wfTimestamp( TS_ISO_8601
, $vcsDate );
758 if ( ExtensionInfo
::getLicenseFileNames( $extensionPath ) ) {
759 $ret['license-name'] = $ext['license-name'] ??
'';
760 $ret['license'] = SpecialPage
::getTitleFor(
762 "License/{$ext['name']}"
766 if ( ExtensionInfo
::getAuthorsFileName( $extensionPath ) ) {
767 $ret['credits'] = SpecialPage
::getTitleFor(
769 "Credits/{$ext['name']}"
777 ApiResult
::setIndexedTagName( $data, 'ext' );
779 return $this->getResult()->addValue( 'query', $property, $data );
782 protected function appendRightsInfo( $property ) {
783 $config = $this->getConfig();
784 $title = Title
::newFromText( $config->get( MainConfigNames
::RightsPage
) );
786 $url = $this->urlUtils
->expand( $title->getLinkURL(), PROTO_CURRENT
);
788 $url = $config->get( MainConfigNames
::RightsUrl
);
790 $text = $config->get( MainConfigNames
::RightsText
) ??
'';
791 if ( $text === '' && $title ) {
792 $text = $title->getPrefixedText();
796 'url' => (string)$url,
797 'text' => (string)$text,
800 return $this->getResult()->addValue( 'query', $property, $data );
803 protected function appendRestrictions( $property ) {
804 $config = $this->getConfig();
806 'types' => $config->get( MainConfigNames
::RestrictionTypes
),
807 'levels' => $config->get( MainConfigNames
::RestrictionLevels
),
808 'cascadinglevels' => $config->get( MainConfigNames
::CascadingRestrictionLevels
),
809 'semiprotectedlevels' => $config->get( MainConfigNames
::SemiprotectedRestrictionLevels
),
812 ApiResult
::setArrayType( $data['types'], 'BCarray' );
813 ApiResult
::setArrayType( $data['levels'], 'BCarray' );
814 ApiResult
::setArrayType( $data['cascadinglevels'], 'BCarray' );
815 ApiResult
::setArrayType( $data['semiprotectedlevels'], 'BCarray' );
817 ApiResult
::setIndexedTagName( $data['types'], 'type' );
818 ApiResult
::setIndexedTagName( $data['levels'], 'level' );
819 ApiResult
::setIndexedTagName( $data['cascadinglevels'], 'level' );
820 ApiResult
::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
822 return $this->getResult()->addValue( 'query', $property, $data );
825 public function appendLanguages( $property ) {
826 $params = $this->extractRequestParams();
827 $langCode = $params['inlanguagecode'] ??
'';
828 $langNames = $this->languageNameUtils
->getLanguageNames( $langCode );
832 foreach ( $langNames as $code => $name ) {
835 'bcp47' => LanguageCode
::bcp47( $code ),
837 ApiResult
::setContentValue( $lang, 'name', $name );
840 ApiResult
::setIndexedTagName( $data, 'lang' );
842 return $this->getResult()->addValue( 'query', $property, $data );
845 // Export information about which page languages will trigger
846 // language conversion. (T153341)
847 public function appendLanguageVariants( $property ) {
848 $langNames = $this->languageConverterFactory
->isConversionDisabled() ?
[] :
849 LanguageConverter
::$languagesWithVariants;
853 foreach ( $langNames as $langCode ) {
854 $lang = $this->languageFactory
->getLanguage( $langCode );
855 $langConverter = $this->languageConverterFactory
->getLanguageConverter( $lang );
856 if ( !$langConverter->hasVariants() ) {
857 // Only languages which have variants should be listed
860 $data[$langCode] = [];
861 ApiResult
::setIndexedTagName( $data[$langCode], 'variant' );
862 ApiResult
::setArrayType( $data[$langCode], 'kvp', 'code' );
864 $variants = $langConverter->getVariants();
866 foreach ( $variants as $v ) {
867 $data[$langCode][$v] = [
868 'fallbacks' => (array)$langConverter->getVariantFallbacks( $v ),
870 ApiResult
::setIndexedTagName(
871 $data[$langCode][$v]['fallbacks'], 'variant'
875 ApiResult
::setIndexedTagName( $data, 'lang' );
876 ApiResult
::setArrayType( $data, 'kvp', 'code' );
878 return $this->getResult()->addValue( 'query', $property, $data );
881 public function appendSkins( $property ) {
883 $allowed = $this->skinFactory
->getAllowedSkins();
884 $default = Skin
::normalizeKey( 'default' );
886 foreach ( $this->skinFactory
->getInstalledSkins() as $name => $displayName ) {
887 $msg = $this->msg( "skinname-{$name}" );
888 $code = $this->getParameter( 'inlanguagecode' );
889 if ( $code && $this->languageNameUtils
->isValidCode( $code ) ) {
890 $msg->inLanguage( $code );
892 $msg->inContentLanguage();
894 if ( $msg->exists() ) {
895 $displayName = $msg->text();
897 $skin = [ 'code' => $name ];
898 ApiResult
::setContentValue( $skin, 'name', $displayName );
899 if ( !isset( $allowed[$name] ) ) {
900 $skin['unusable'] = true;
902 if ( $name === $default ) {
903 $skin['default'] = true;
907 ApiResult
::setIndexedTagName( $data, 'skin' );
909 return $this->getResult()->addValue( 'query', $property, $data );
912 public function appendExtensionTags( $property ) {
914 static function ( $item ) {
917 $this->parserFactory
->getMainInstance()->getTags()
919 ApiResult
::setArrayType( $tags, 'BCarray' );
920 ApiResult
::setIndexedTagName( $tags, 't' );
922 return $this->getResult()->addValue( 'query', $property, $tags );
925 public function appendFunctionHooks( $property ) {
926 $hooks = $this->parserFactory
->getMainInstance()->getFunctionHooks();
927 ApiResult
::setArrayType( $hooks, 'BCarray' );
928 ApiResult
::setIndexedTagName( $hooks, 'h' );
930 return $this->getResult()->addValue( 'query', $property, $hooks );
933 public function appendVariables( $property ) {
934 $variables = $this->magicWordFactory
->getVariableIDs();
935 ApiResult
::setArrayType( $variables, 'BCarray' );
936 ApiResult
::setIndexedTagName( $variables, 'v' );
938 return $this->getResult()->addValue( 'query', $property, $variables );
941 public function appendProtocols( $property ) {
942 // Make a copy of the global so we don't try to set the _element key of it - T47130
943 $protocols = array_values( $this->getConfig()->get( MainConfigNames
::UrlProtocols
) );
944 ApiResult
::setArrayType( $protocols, 'BCarray' );
945 ApiResult
::setIndexedTagName( $protocols, 'p' );
947 return $this->getResult()->addValue( 'query', $property, $protocols );
950 public function appendDefaultOptions( $property ) {
951 $options = $this->userOptionsLookup
->getDefaultOptions( null );
952 $options[ApiResult
::META_BC_BOOLS
] = array_keys( $options );
953 return $this->getResult()->addValue( 'query', $property, $options );
956 public function appendUploadDialog( $property ) {
957 $config = $this->getConfig()->get( MainConfigNames
::UploadDialog
);
958 return $this->getResult()->addValue( 'query', $property, $config );
961 private function getAutoPromoteConds() {
962 $allowedConditions = [];
963 foreach ( get_defined_constants() as $constantName => $constantValue ) {
964 if ( strpos( $constantName, 'APCOND_' ) !== false ) {
965 $allowedConditions[$constantName] = $constantValue;
968 return $allowedConditions;
971 private function processAutoPromote( $input, $allowedConditions ) {
973 foreach ( $input as $groupName => $conditions ) {
974 $row = $this->recAutopromote( $conditions, $allowedConditions );
975 if ( !isset( $row[0] ) ||
is_string( $row ) ) {
978 $data[$groupName] = $row;
983 private function appendAutoPromote( $property ) {
984 return $this->getResult()->addValue(
987 $this->processAutoPromote(
988 $this->getConfig()->get( MainConfigNames
::Autopromote
),
989 $this->getAutoPromoteConds()
994 private function appendAutoPromoteOnce( $property ) {
995 $allowedConditions = $this->getAutoPromoteConds();
997 foreach ( $this->getConfig()->get( MainConfigNames
::AutopromoteOnce
) as $key => $value ) {
998 $data[$key] = $this->processAutoPromote( $value, $allowedConditions );
1000 return $this->getResult()->addValue( 'query', $property, $data );
1004 * @param array|int|string $cond
1005 * @param array $allowedConditions
1006 * @return array|string
1008 private function recAutopromote( $cond, $allowedConditions ) {
1010 // First, checks if $cond is an array
1011 if ( is_array( $cond ) ) {
1012 // Checks if $cond[0] is a valid operand
1013 if ( in_array( $cond[0], UserGroupManager
::VALID_OPS
, true ) ) {
1014 $config['operand'] = $cond[0];
1015 // Traversal checks conditions
1016 foreach ( array_slice( $cond, 1 ) as $value ) {
1017 $config[] = $this->recAutopromote( $value, $allowedConditions );
1019 } elseif ( is_string( $cond[0] ) ) {
1020 // Returns $cond directly, if $cond[0] is a string
1023 // When $cond is equal to an APCOND_ constant value
1024 $params = array_slice( $cond, 1 );
1025 if ( $params === [ null ] ) {
1026 // Special casing for these conditions and their default of null,
1027 // to replace their values with $wgAutoConfirmCount/$wgAutoConfirmAge as appropriate
1028 if ( $cond[0] === APCOND_EDITCOUNT
) {
1029 $params = [ $this->getConfig()->get( MainConfigNames
::AutoConfirmCount
) ];
1030 } elseif ( $cond[0] === APCOND_AGE
) {
1031 $params = [ $this->getConfig()->get( MainConfigNames
::AutoConfirmAge
) ];
1035 'condname' => array_search( $cond[0], $allowedConditions ),
1038 ApiResult
::setIndexedTagName( $config, 'params' );
1040 } elseif ( is_string( $cond ) ) {
1043 // When $cond is equal to an APCOND_ constant value
1045 'condname' => array_search( $cond, $allowedConditions ),
1048 ApiResult
::setIndexedTagName( $config, 'params' );
1054 public function appendSubscribedHooks( $property ) {
1055 $hookNames = $this->hookContainer
->getHookNames();
1059 foreach ( $hookNames as $name ) {
1062 'subscribers' => $this->hookContainer
->getHandlerDescriptions( $name ),
1065 ApiResult
::setArrayType( $arr['subscribers'], 'array' );
1066 ApiResult
::setIndexedTagName( $arr['subscribers'], 's' );
1070 ApiResult
::setIndexedTagName( $data, 'hook' );
1072 return $this->getResult()->addValue( 'query', $property, $data );
1075 public function getCacheMode( $params ) {
1076 // Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
1077 if ( $this->getConfig()->get( MainConfigNames
::ExtraInterlanguageLinkPrefixes
) &&
1078 in_array( 'interwikimap', $params['prop'] ??
[] )
1080 return 'anon-public-user-private';
1086 public function getAllowedParams() {
1089 ParamValidator
::PARAM_DEFAULT
=> 'general',
1090 ParamValidator
::PARAM_ISMULTI
=> true,
1091 ParamValidator
::PARAM_TYPE
=> [
1095 'specialpagealiases',
1101 'autocreatetempuser',
1121 ApiBase
::PARAM_HELP_MSG_PER_VALUE
=> [],
1124 ParamValidator
::PARAM_TYPE
=> [
1129 'showalldb' => false,
1130 'numberingroup' => false,
1131 'inlanguagecode' => null,
1135 protected function getExamplesMessages() {
1137 'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
1138 => 'apihelp-query+siteinfo-example-simple',
1139 'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
1140 => 'apihelp-query+siteinfo-example-interwiki',
1141 'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
1142 => 'apihelp-query+siteinfo-example-replag',
1146 public function getHelpUrls() {
1147 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Siteinfo';
1151 /** @deprecated class alias since 1.43 */
1152 class_alias( ApiQuerySiteinfo
::class, 'ApiQuerySiteinfo' );