MediaWiki master
ApiQuerySiteinfo.php
Go to the documentation of this file.
1<?php
47
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
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
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/$AutoConfirmAge 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 ],
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}
getDB()
const APCOND_AGE
Definition Defines.php:177
const PROTO_CURRENT
Definition Defines.php:205
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:36
const APCOND_EDITCOUNT
Definition Defines.php:176
const PROTO_RELATIVE
Definition Defines.php:202
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$linkPrefixCharset
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1533
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:933
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1777
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:211
getResult()
Get the result object.
Definition ApiBase.php:671
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:811
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:756
This is a base class for all Query modules.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
A query action to return meta information about the wiki site.
appendLanguageVariants( $property)
appendLanguages( $property)
appendInterwikiMap( $property, $filter)
appendGeneralInfo( $property)
appendRightsInfo( $property)
getExamplesMessages()
Returns usage examples for this module.
appendInstalledLibraries( $property)
appendVariables( $property)
appendAutoCreateTempUser( $property)
appendInstalledClientLibraries( $property)
appendUserGroups( $property, $numberInGroup)
appendFileExtensions( $property)
appendNamespaces( $property)
appendDefaultOptions( $property)
appendMagicWords( $property)
getHelpUrls()
Return links to more detailed help pages about the module.
appendExtensions( $property)
getCacheMode( $params)
Get the cache mode for the data generated by this module.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
appendRestrictions( $property)
__construct(ApiQuery $query, $moduleName, UserOptionsLookup $userOptionsLookup, UserGroupManager $userGroupManager, LanguageConverterFactory $languageConverterFactory, LanguageFactory $languageFactory, LanguageNameUtils $languageNameUtils, Language $contentLanguage, NamespaceInfo $namespaceInfo, InterwikiLookup $interwikiLookup, ParserFactory $parserFactory, MagicWordFactory $magicWordFactory, SpecialPageFactory $specialPageFactory, SkinFactory $skinFactory, ILoadBalancer $loadBalancer, ReadOnlyMode $readOnlyMode, UrlUtils $urlUtils)
appendExtensionTags( $property)
appendUploadDialog( $property)
appendProtocols( $property)
appendStatistics( $property)
appendSpecialPageAliases( $property)
appendDbReplLagInfo( $property, $includeAll)
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
appendSubscribedHooks( $property)
appendFunctionHooks( $property)
appendNamespaceAliases( $property)
This is the main query class.
Definition ApiQuery.php:43
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
static getMinUploadChunkSize(Config $config)
Base class for language-specific code.
Definition Language.php:63
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
An interface for creating language converters.
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
A service that provides utilities to do with language names and codes.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Store information about magic words, and create/cache MagicWord objects.
Module for skin stylesheets.
Static accessor class for site_stats and related things.
Definition SiteStats.php:36
Factory for handling the special page list and generating SpecialPage objects.
Parent class for all special pages.
Version information about MediaWiki (core, extensions, libs), PHP, and the database.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:78
Provides access to user options.
Fetch status information from a local git repository.
Definition GitInfo.php:47
A service to expand, parse, and otherwise manipulate URLs.
Definition UrlUtils.php:16
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:31
Factory class to create Skin objects.
static normalizeKey(string $key)
Normalize a skin preference value to a form that can be loaded.
Definition Skin.php:221
Reads an installed.json file and provides accessors to get what is installed.
Service for formatting and validating API parameters.
Determine whether a site is currently in read-only mode.
Service interface for looking up Interwiki records.
This class is a delegate to ILBFactory for a given database cluster.