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