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 $data['fallback'] = [];
303 foreach ( $this->contentLanguage->getFallbackLanguages() as $code ) {
304 $data['fallback'][] = [ 'code' => $code ];
305 }
306 ApiResult::setIndexedTagName( $data['fallback'], 'lang' );
307
308 if ( $contLangConverter->hasVariants() ) {
309 $data['variants'] = [];
310 foreach ( $contLangConverter->getVariants() as $code ) {
311 $data['variants'][] = [
312 'code' => $code,
313 'name' => $this->contentLanguage->getVariantname( $code ),
314 ];
315 }
316 ApiResult::setIndexedTagName( $data['variants'], 'lang' );
317 }
318
319 $data['rtl'] = $this->contentLanguage->isRTL();
320 $data['fallback8bitEncoding'] = $this->contentLanguage->fallback8bitEncoding();
321
322 $data['readonly'] = $this->readOnlyMode->isReadOnly();
323 if ( $data['readonly'] ) {
324 $data['readonlyreason'] = $this->readOnlyMode->getReason();
325 }
326 $data['writeapi'] = true; // Deprecated since MW 1.32
327
328 $data['maxarticlesize'] = $config->get( MainConfigNames::MaxArticleSize ) * 1024;
329
330 $data['timezone'] = $config->get( MainConfigNames::Localtimezone );
331 $data['timeoffset'] = (int)( $config->get( MainConfigNames::LocalTZoffset ) );
332 $data['articlepath'] = $config->get( MainConfigNames::ArticlePath );
333 $data['scriptpath'] = $config->get( MainConfigNames::ScriptPath );
334 $data['script'] = $config->get( MainConfigNames::Script );
335 $data['variantarticlepath'] = $config->get( MainConfigNames::VariantArticlePath );
336 $data[ApiResult::META_BC_BOOLS][] = 'variantarticlepath';
337 $data['server'] = $config->get( MainConfigNames::Server );
338 $data['servername'] = $config->get( MainConfigNames::ServerName );
339 $data['wikiid'] = WikiMap::getCurrentWikiId();
340 $data['time'] = wfTimestamp( TS_ISO_8601, time() );
341
342 $data['misermode'] = (bool)$config->get( MainConfigNames::MiserMode );
343
344 $data['uploadsenabled'] = UploadBase::isEnabled();
345 $data['maxuploadsize'] = UploadBase::getMaxUploadSize();
346 $data['minuploadchunksize'] = ApiUpload::getMinUploadChunkSize( $config );
347
348 $data['galleryoptions'] = $config->get( MainConfigNames::GalleryOptions );
349
350 $data['thumblimits'] = $config->get( MainConfigNames::ThumbLimits );
351 ApiResult::setArrayType( $data['thumblimits'], 'BCassoc' );
352 ApiResult::setIndexedTagName( $data['thumblimits'], 'limit' );
353 $data['imagelimits'] = [];
354 foreach ( $config->get( MainConfigNames::ImageLimits ) as $k => $limit ) {
355 $data['imagelimits'][$k] = [ 'width' => $limit[0], 'height' => $limit[1] ];
356 }
357 ApiResult::setArrayType( $data['imagelimits'], 'BCassoc' );
358 ApiResult::setIndexedTagName( $data['imagelimits'], 'limit' );
359
360 $favicon = $config->get( MainConfigNames::Favicon );
361 if ( $favicon ) {
362 // Expand any local path to full URL to improve API usability (T77093).
363 $data['favicon'] = (string)$this->urlUtils->expand( $favicon );
364 }
365
366 $data['centralidlookupprovider'] = $config->get( MainConfigNames::CentralIdLookupProvider );
367 $providerIds = array_keys( $config->get( MainConfigNames::CentralIdLookupProviders ) );
368 $data['allcentralidlookupproviders'] = $providerIds;
369
370 $data['interwikimagic'] = (bool)$config->get( MainConfigNames::InterwikiMagic );
371 $data['magiclinks'] = $config->get( MainConfigNames::EnableMagicLinks );
372
373 $data['categorycollation'] = $config->get( MainConfigNames::CategoryCollation );
374
375 $data['nofollowlinks'] = $config->get( MainConfigNames::NoFollowLinks );
376 $data['nofollownsexceptions'] = $config->get( MainConfigNames::NoFollowNsExceptions );
377 $data['nofollowdomainexceptions'] = $config->get( MainConfigNames::NoFollowDomainExceptions );
378 $data['externallinktarget'] = $config->get( MainConfigNames::ExternalLinkTarget );
379
380 $this->getHookRunner()->onAPIQuerySiteInfoGeneralInfo( $this, $data );
381
382 return $this->getResult()->addValue( 'query', $property, $data );
383 }
384
385 protected function appendNamespaces( $property ) {
386 $nsProtection = $this->getConfig()->get( MainConfigNames::NamespaceProtection );
387
388 $data = [ ApiResult::META_TYPE => 'assoc' ];
389 foreach ( $this->contentLanguage->getFormattedNamespaces() as $ns => $title ) {
390 $data[$ns] = [
391 'id' => (int)$ns,
392 'case' => $this->namespaceInfo->isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
393 ];
394 ApiResult::setContentValue( $data[$ns], 'name', $title );
395 $canonical = $this->namespaceInfo->getCanonicalName( $ns );
396
397 $data[$ns]['subpages'] = $this->namespaceInfo->hasSubpages( $ns );
398
399 if ( $canonical ) {
400 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
401 }
402
403 $data[$ns]['content'] = $this->namespaceInfo->isContent( $ns );
404 $data[$ns]['nonincludable'] = $this->namespaceInfo->isNonincludable( $ns );
405
406 $specificNs = $nsProtection[$ns] ?? '';
407 if ( is_array( $specificNs ) ) {
408 $specificNs = implode( "|", array_filter( $specificNs ) );
409 }
410 if ( $specificNs !== '' ) {
411 $data[$ns]['namespaceprotection'] = $specificNs;
412 }
413
414 $contentmodel = $this->namespaceInfo->getNamespaceContentModel( $ns );
415 if ( $contentmodel ) {
416 $data[$ns]['defaultcontentmodel'] = $contentmodel;
417 }
418 }
419
420 ApiResult::setArrayType( $data, 'assoc' );
421 ApiResult::setIndexedTagName( $data, 'ns' );
422
423 return $this->getResult()->addValue( 'query', $property, $data );
424 }
425
426 protected function appendNamespaceAliases( $property ) {
427 $aliases = $this->contentLanguage->getNamespaceAliases();
428 $namespaces = $this->contentLanguage->getNamespaces();
429 $data = [];
430 foreach ( $aliases as $title => $ns ) {
431 if ( $namespaces[$ns] == $title ) {
432 // Don't list duplicates
433 continue;
434 }
435 $item = [ 'id' => (int)$ns ];
436 ApiResult::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
437 $data[] = $item;
438 }
439
440 sort( $data );
441
442 ApiResult::setIndexedTagName( $data, 'ns' );
443
444 return $this->getResult()->addValue( 'query', $property, $data );
445 }
446
447 protected function appendSpecialPageAliases( $property ) {
448 $data = [];
449 $aliases = $this->contentLanguage->getSpecialPageAliases();
450 foreach ( $this->specialPageFactory->getNames() as $specialpage ) {
451 if ( isset( $aliases[$specialpage] ) ) {
452 $arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ];
453 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
454 $data[] = $arr;
455 }
456 }
457 ApiResult::setIndexedTagName( $data, 'specialpage' );
458
459 return $this->getResult()->addValue( 'query', $property, $data );
460 }
461
462 protected function appendMagicWords( $property ) {
463 $data = [];
464 foreach ( $this->contentLanguage->getMagicWords() as $name => $aliases ) {
465 $caseSensitive = (bool)array_shift( $aliases );
466 $arr = [
467 'name' => $name,
468 'aliases' => $aliases,
469 'case-sensitive' => $caseSensitive,
470 ];
471 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
472 $data[] = $arr;
473 }
474 ApiResult::setIndexedTagName( $data, 'magicword' );
475
476 return $this->getResult()->addValue( 'query', $property, $data );
477 }
478
479 protected function appendInterwikiMap( $property, $filter ) {
480 $local = $filter ? $filter === 'local' : null;
481
482 $params = $this->extractRequestParams();
483 $langCode = $params['inlanguagecode'] ?? '';
484 $interwikiMagic = $this->getConfig()->get( MainConfigNames::InterwikiMagic );
485
486 if ( $interwikiMagic ) {
487 $langNames = $this->languageNameUtils->getLanguageNames( $langCode );
488 }
489
490 $extraLangPrefixes = $this->getConfig()->get( MainConfigNames::ExtraInterlanguageLinkPrefixes );
491 $extraLangCodeMap = $this->getConfig()->get( MainConfigNames::InterlanguageLinkCodeMap );
492 $localInterwikis = $this->getConfig()->get( MainConfigNames::LocalInterwikis );
493 $data = [];
494
495 foreach ( $this->interwikiLookup->getAllPrefixes( $local ) as $row ) {
496 $prefix = $row['iw_prefix'];
497 $val = [];
498 $val['prefix'] = $prefix;
499 if ( $row['iw_local'] ?? false ) {
500 $val['local'] = true;
501 }
502 if ( $row['iw_trans'] ?? false ) {
503 $val['trans'] = true;
504 }
505
506 if ( $interwikiMagic && isset( $langNames[$prefix] ) ) {
507 $val['language'] = $langNames[$prefix];
508 $standard = LanguageCode::replaceDeprecatedCodes( $prefix );
509 if ( $standard !== $prefix ) {
510 # Note that even if this code is deprecated, it should
511 # only be remapped if extralanglink (set below) is false.
512 $val['deprecated'] = $standard;
513 }
514 $val['bcp47'] = LanguageCode::bcp47( $standard );
515 }
516 if ( in_array( $prefix, $localInterwikis ) ) {
517 $val['localinterwiki'] = true;
518 }
519 if ( $interwikiMagic && in_array( $prefix, $extraLangPrefixes ) ) {
520 $val['extralanglink'] = true;
521 $val['code'] = $extraLangCodeMap[$prefix] ?? $prefix;
522 $val['bcp47'] = LanguageCode::bcp47( $val['code'] );
523
524 $linktext = $this->msg( "interlanguage-link-$prefix" );
525 if ( !$linktext->isDisabled() ) {
526 $val['linktext'] = $linktext->text();
527 }
528
529 $sitename = $this->msg( "interlanguage-link-sitename-$prefix" );
530 if ( !$sitename->isDisabled() ) {
531 $val['sitename'] = $sitename->text();
532 }
533 }
534
535 $val['url'] = (string)$this->urlUtils->expand( $row['iw_url'], PROTO_CURRENT );
536 $val['protorel'] = str_starts_with( $row['iw_url'], '//' );
537 if ( ( $row['iw_wikiid'] ?? '' ) !== '' ) {
538 $val['wikiid'] = $row['iw_wikiid'];
539 }
540 if ( ( $row['iw_api'] ?? '' ) !== '' ) {
541 $val['api'] = $row['iw_api'];
542 }
543
544 $data[] = $val;
545 }
546
547 ApiResult::setIndexedTagName( $data, 'iw' );
548
549 return $this->getResult()->addValue( 'query', $property, $data );
550 }
551
552 protected function appendDbReplLagInfo( $property, $includeAll ) {
553 $data = [];
554 $showHostnames = $this->getConfig()->get( MainConfigNames::ShowHostnames );
555 if ( $includeAll ) {
556 if ( !$showHostnames ) {
557 $this->dieWithError( 'apierror-siteinfo-includealldenied', 'includeAllDenied' );
558 }
559
560 foreach ( $this->loadBalancer->getLagTimes() as $i => $lag ) {
561 $data[] = [
562 'host' => $this->loadBalancer->getServerName( $i ),
563 'lag' => $lag
564 ];
565 }
566 } else {
567 [ , $lag, $index ] = $this->loadBalancer->getMaxLag();
568 $data[] = [
569 'host' => $showHostnames ? $this->loadBalancer->getServerName( $index ) : '',
570 'lag' => $lag
571 ];
572 }
573
574 ApiResult::setIndexedTagName( $data, 'db' );
575
576 return $this->getResult()->addValue( 'query', $property, $data );
577 }
578
579 protected function appendStatistics( $property ) {
580 $data = [
581 'pages' => SiteStats::pages(),
582 'articles' => SiteStats::articles(),
583 'edits' => SiteStats::edits(),
584 'images' => SiteStats::images(),
585 'users' => SiteStats::users(),
586 'activeusers' => SiteStats::activeUsers(),
587 'admins' => SiteStats::numberingroup( 'sysop' ),
588 'jobs' => SiteStats::jobs(),
589 ];
590
591 $this->getHookRunner()->onAPIQuerySiteInfoStatisticsInfo( $data );
592
593 return $this->getResult()->addValue( 'query', $property, $data );
594 }
595
596 protected function appendUserGroups( $property, $numberInGroup ) {
597 $config = $this->getConfig();
598
599 $data = [];
600 $result = $this->getResult();
601 $allGroups = array_values( $this->userGroupManager->listAllGroups() );
602 foreach ( $config->get( MainConfigNames::GroupPermissions ) as $group => $permissions ) {
603 $arr = [
604 'name' => $group,
605 'rights' => array_keys( $permissions, true ),
606 ];
607
608 if ( $numberInGroup ) {
609 $autopromote = $config->get( MainConfigNames::Autopromote );
610
611 if ( $group == 'user' ) {
612 $arr['number'] = SiteStats::users();
613 // '*' and autopromote groups have no size
614 } elseif ( $group !== '*' && !isset( $autopromote[$group] ) ) {
615 $arr['number'] = SiteStats::numberingroup( $group );
616 }
617 }
618
619 $groupArr = [
620 'add' => $config->get( MainConfigNames::AddGroups ),
621 'remove' => $config->get( MainConfigNames::RemoveGroups ),
622 'add-self' => $config->get( MainConfigNames::GroupsAddToSelf ),
623 'remove-self' => $config->get( MainConfigNames::GroupsRemoveFromSelf )
624 ];
625
626 foreach ( $groupArr as $type => $rights ) {
627 if ( isset( $rights[$group] ) ) {
628 if ( $rights[$group] === true ) {
629 $groups = $allGroups;
630 } else {
631 $groups = array_intersect( $rights[$group], $allGroups );
632 }
633 if ( $groups ) {
634 $arr[$type] = $groups;
635 ApiResult::setArrayType( $arr[$type], 'BCarray' );
636 ApiResult::setIndexedTagName( $arr[$type], 'group' );
637 }
638 }
639 }
640
641 ApiResult::setIndexedTagName( $arr['rights'], 'permission' );
642 $data[] = $arr;
643 }
644
645 ApiResult::setIndexedTagName( $data, 'group' );
646
647 return $result->addValue( 'query', $property, $data );
648 }
649
650 protected function appendAutoCreateTempUser( $property ) {
651 $config = $this->getConfig()->get( MainConfigNames::AutoCreateTempUser );
652
653 $data = [ 'enabled' => false ];
654 if ( $config['enabled'] ?? false ) {
655 $data['enabled'] = true;
656 $data['actions'] = $config['actions'];
657 $data['genPattern'] = $config['genPattern'];
658 $data['matchPattern'] = $config['matchPattern'] ?? $data['genPattern'];
659 $data['serialProvider'] = $config['serialProvider'];
660 $data['serialMapping'] = $config['serialMapping'];
661 }
662 if ( isset( $config['reservedPattern'] ) ) {
663 $data['reservedPattern'] = $config['reservedPattern'];
664 }
665
666 return $this->getResult()->addValue( 'query', $property, $data );
667 }
668
669 protected function appendFileExtensions( $property ) {
670 $data = [];
671 foreach (
672 array_unique( $this->getConfig()->get( MainConfigNames::FileExtensions ) ) as $ext
673 ) {
674 $data[] = [ 'ext' => $ext ];
675 }
676 ApiResult::setIndexedTagName( $data, 'fe' );
677
678 return $this->getResult()->addValue( 'query', $property, $data );
679 }
680
681 protected function appendInstalledClientLibraries( $property ) {
682 $data = [];
683 foreach ( SpecialVersion::parseForeignResources() as $name => $info ) {
684 $data[] = [
685 // Can't use $name as it is version suffixed (as multiple versions
686 // of a library may exist, provided by different skins/extensions)
687 'name' => $info['name'],
688 'version' => $info['version'],
689 ];
690 }
691 ApiResult::setIndexedTagName( $data, 'library' );
692 return $this->getResult()->addValue( 'query', $property, $data );
693 }
694
695 protected function appendInstalledLibraries( $property ) {
696 $path = MW_INSTALL_PATH . '/vendor/composer/installed.json';
697 if ( !file_exists( $path ) ) {
698 return true;
699 }
700
701 $data = [];
702 $installed = new ComposerInstalled( $path );
703 foreach ( $installed->getInstalledDependencies() as $name => $info ) {
704 if ( str_starts_with( $info['type'], 'mediawiki-' ) ) {
705 // Skip any extensions or skins since they'll be listed
706 // in their proper section
707 continue;
708 }
709 $data[] = [
710 'name' => $name,
711 'version' => $info['version'],
712 ];
713 }
714 ApiResult::setIndexedTagName( $data, 'library' );
715
716 return $this->getResult()->addValue( 'query', $property, $data );
717 }
718
719 protected function appendExtensions( $property ) {
720 $data = [];
721 $credits = SpecialVersion::getCredits(
722 ExtensionRegistry::getInstance(),
723 $this->getConfig()
724 );
725 foreach ( $credits as $type => $extensions ) {
726 foreach ( $extensions as $ext ) {
727 $ret = [ 'type' => $type ];
728 if ( isset( $ext['name'] ) ) {
729 $ret['name'] = $ext['name'];
730 }
731 if ( isset( $ext['namemsg'] ) ) {
732 $ret['namemsg'] = $ext['namemsg'];
733 }
734 if ( isset( $ext['description'] ) ) {
735 $ret['description'] = $ext['description'];
736 }
737 if ( isset( $ext['descriptionmsg'] ) ) {
738 // Can be a string or [ key, param1, param2, ... ]
739 if ( is_array( $ext['descriptionmsg'] ) ) {
740 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
741 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
742 ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
743 } else {
744 $ret['descriptionmsg'] = $ext['descriptionmsg'];
745 }
746 }
747 if ( isset( $ext['author'] ) ) {
748 $ret['author'] = is_array( $ext['author'] ) ?
749 implode( ', ', $ext['author'] ) : $ext['author'];
750 }
751 if ( isset( $ext['url'] ) ) {
752 $ret['url'] = $ext['url'];
753 }
754 if ( isset( $ext['version'] ) ) {
755 $ret['version'] = $ext['version'];
756 }
757 if ( isset( $ext['path'] ) ) {
758 $extensionPath = dirname( $ext['path'] );
759 $gitInfo = new GitInfo( $extensionPath );
760 $vcsVersion = $gitInfo->getHeadSHA1();
761 if ( $vcsVersion !== false ) {
762 $ret['vcs-system'] = 'git';
763 $ret['vcs-version'] = $vcsVersion;
764 $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
765 $vcsDate = $gitInfo->getHeadCommitDate();
766 if ( $vcsDate !== false ) {
767 $ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $vcsDate );
768 }
769 }
770
771 if ( ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
772 $ret['license-name'] = $ext['license-name'] ?? '';
773 $ret['license'] = SpecialPage::getTitleFor(
774 'Version',
775 "License/{$ext['name']}"
776 )->getLinkURL();
777 }
778
779 if ( ExtensionInfo::getAuthorsFileName( $extensionPath ) ) {
780 $ret['credits'] = SpecialPage::getTitleFor(
781 'Version',
782 "Credits/{$ext['name']}"
783 )->getLinkURL();
784 }
785 }
786 $data[] = $ret;
787 }
788 }
789
790 ApiResult::setIndexedTagName( $data, 'ext' );
791
792 return $this->getResult()->addValue( 'query', $property, $data );
793 }
794
795 protected function appendRightsInfo( $property ) {
796 $config = $this->getConfig();
797 $title = Title::newFromText( $config->get( MainConfigNames::RightsPage ) );
798 if ( $title ) {
799 $url = $this->urlUtils->expand( $title->getLinkURL(), PROTO_CURRENT );
800 } else {
801 $url = $config->get( MainConfigNames::RightsUrl );
802 }
803 $text = $config->get( MainConfigNames::RightsText ) ?? '';
804 if ( $text === '' && $title ) {
805 $text = $title->getPrefixedText();
806 }
807
808 $data = [
809 'url' => (string)$url,
810 'text' => (string)$text,
811 ];
812
813 return $this->getResult()->addValue( 'query', $property, $data );
814 }
815
816 protected function appendRestrictions( $property ) {
817 $config = $this->getConfig();
818 $data = [
819 'types' => $config->get( MainConfigNames::RestrictionTypes ),
820 'levels' => $config->get( MainConfigNames::RestrictionLevels ),
821 'cascadinglevels' => $config->get( MainConfigNames::CascadingRestrictionLevels ),
822 'semiprotectedlevels' => $config->get( MainConfigNames::SemiprotectedRestrictionLevels ),
823 ];
824
825 ApiResult::setArrayType( $data['types'], 'BCarray' );
826 ApiResult::setArrayType( $data['levels'], 'BCarray' );
827 ApiResult::setArrayType( $data['cascadinglevels'], 'BCarray' );
828 ApiResult::setArrayType( $data['semiprotectedlevels'], 'BCarray' );
829
830 ApiResult::setIndexedTagName( $data['types'], 'type' );
831 ApiResult::setIndexedTagName( $data['levels'], 'level' );
832 ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
833 ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
834
835 return $this->getResult()->addValue( 'query', $property, $data );
836 }
837
838 public function appendLanguages( $property ) {
839 $params = $this->extractRequestParams();
840 $langCode = $params['inlanguagecode'] ?? '';
841 $langNames = $this->languageNameUtils->getLanguageNames( $langCode );
842
843 $data = [];
844
845 foreach ( $langNames as $code => $name ) {
846 $lang = [
847 'code' => $code,
848 'bcp47' => LanguageCode::bcp47( $code ),
849 ];
850 ApiResult::setContentValue( $lang, 'name', $name );
851 $data[] = $lang;
852 }
853 ApiResult::setIndexedTagName( $data, 'lang' );
854
855 return $this->getResult()->addValue( 'query', $property, $data );
856 }
857
858 // Export information about which page languages will trigger
859 // language conversion. (T153341)
860 public function appendLanguageVariants( $property ) {
861 $langNames = $this->languageConverterFactory->isConversionDisabled() ? [] :
862 LanguageConverter::$languagesWithVariants;
863 sort( $langNames );
864
865 $data = [];
866 foreach ( $langNames as $langCode ) {
867 $lang = $this->languageFactory->getLanguage( $langCode );
868 $langConverter = $this->languageConverterFactory->getLanguageConverter( $lang );
869 if ( !$langConverter->hasVariants() ) {
870 // Only languages which have variants should be listed
871 continue;
872 }
873 $data[$langCode] = [];
874 ApiResult::setIndexedTagName( $data[$langCode], 'variant' );
875 ApiResult::setArrayType( $data[$langCode], 'kvp', 'code' );
876
877 $variants = $langConverter->getVariants();
878 sort( $variants );
879 foreach ( $variants as $v ) {
880 $data[$langCode][$v] = [
881 'fallbacks' => (array)$langConverter->getVariantFallbacks( $v ),
882 ];
883 ApiResult::setIndexedTagName(
884 $data[$langCode][$v]['fallbacks'], 'variant'
885 );
886 }
887 }
888 ApiResult::setIndexedTagName( $data, 'lang' );
889 ApiResult::setArrayType( $data, 'kvp', 'code' );
890
891 return $this->getResult()->addValue( 'query', $property, $data );
892 }
893
894 public function appendSkins( $property ) {
895 $data = [];
896 $allowed = $this->skinFactory->getAllowedSkins();
897 $default = Skin::normalizeKey( 'default' );
898
899 foreach ( $this->skinFactory->getInstalledSkins() as $name => $displayName ) {
900 $msg = $this->msg( "skinname-{$name}" );
901 $code = $this->getParameter( 'inlanguagecode' );
902 if ( $code && $this->languageNameUtils->isValidCode( $code ) ) {
903 $msg->inLanguage( $code );
904 } else {
905 $msg->inContentLanguage();
906 }
907 if ( $msg->exists() ) {
908 $displayName = $msg->text();
909 }
910 $skin = [ 'code' => $name ];
911 ApiResult::setContentValue( $skin, 'name', $displayName );
912 if ( !isset( $allowed[$name] ) ) {
913 $skin['unusable'] = true;
914 }
915 if ( $name === $default ) {
916 $skin['default'] = true;
917 }
918 $data[] = $skin;
919 }
920 ApiResult::setIndexedTagName( $data, 'skin' );
921
922 return $this->getResult()->addValue( 'query', $property, $data );
923 }
924
925 public function appendExtensionTags( $property ) {
926 $tags = array_map(
927 static function ( $item ) {
928 return "<$item>";
929 },
930 $this->parserFactory->getMainInstance()->getTags()
931 );
932 ApiResult::setArrayType( $tags, 'BCarray' );
933 ApiResult::setIndexedTagName( $tags, 't' );
934
935 return $this->getResult()->addValue( 'query', $property, $tags );
936 }
937
938 public function appendFunctionHooks( $property ) {
939 $hooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
940 ApiResult::setArrayType( $hooks, 'BCarray' );
941 ApiResult::setIndexedTagName( $hooks, 'h' );
942
943 return $this->getResult()->addValue( 'query', $property, $hooks );
944 }
945
946 public function appendVariables( $property ) {
947 $variables = $this->magicWordFactory->getVariableIDs();
948 ApiResult::setArrayType( $variables, 'BCarray' );
949 ApiResult::setIndexedTagName( $variables, 'v' );
950
951 return $this->getResult()->addValue( 'query', $property, $variables );
952 }
953
954 public function appendProtocols( $property ) {
955 // Make a copy of the global so we don't try to set the _element key of it - T47130
956 $protocols = array_values( $this->getConfig()->get( MainConfigNames::UrlProtocols ) );
957 ApiResult::setArrayType( $protocols, 'BCarray' );
958 ApiResult::setIndexedTagName( $protocols, 'p' );
959
960 return $this->getResult()->addValue( 'query', $property, $protocols );
961 }
962
963 public function appendDefaultOptions( $property ) {
964 $options = $this->userOptionsLookup->getDefaultOptions( null );
965 $options[ApiResult::META_BC_BOOLS] = array_keys( $options );
966 return $this->getResult()->addValue( 'query', $property, $options );
967 }
968
969 public function appendUploadDialog( $property ) {
970 $config = $this->getConfig()->get( MainConfigNames::UploadDialog );
971 return $this->getResult()->addValue( 'query', $property, $config );
972 }
973
974 private function getAutoPromoteConds() {
975 $allowedConditions = [];
976 foreach ( get_defined_constants() as $constantName => $constantValue ) {
977 if ( strpos( $constantName, 'APCOND_' ) !== false ) {
978 $allowedConditions[$constantName] = $constantValue;
979 }
980 }
981 return $allowedConditions;
982 }
983
984 private function processAutoPromote( $input, $allowedConditions ) {
985 $data = [];
986 foreach ( $input as $groupName => $conditions ) {
987 $row = $this->recAutopromote( $conditions, $allowedConditions );
988 if ( !isset( $row[0] ) || is_string( $row ) ) {
989 $row = [ $row ];
990 }
991 $data[$groupName] = $row;
992 }
993 return $data;
994 }
995
996 private function appendAutoPromote( $property ) {
997 return $this->getResult()->addValue(
998 'query',
999 $property,
1000 $this->processAutoPromote(
1001 $this->getConfig()->get( 'Autopromote' ),
1002 $this->getAutoPromoteConds()
1003 )
1004 );
1005 }
1006
1007 private function appendAutoPromoteOnce( $property ) {
1008 $allowedConditions = $this->getAutoPromoteConds();
1009 $data = [];
1010 foreach ( $this->getConfig()->get( 'AutopromoteOnce' ) as $key => $value ) {
1011 $data[$key] = $this->processAutoPromote( $value, $allowedConditions );
1012 }
1013 return $this->getResult()->addValue( 'query', $property, $data );
1014 }
1015
1021 private function recAutopromote( $cond, $allowedConditions ) {
1022 $config = [];
1023 // First, checks if $cond is an array
1024 if ( is_array( $cond ) ) {
1025 // Checks if $cond[0] is a valid operand
1026 if ( in_array( $cond[0], UserGroupManager::VALID_OPS, true ) ) {
1027 $config['operand'] = $cond[0];
1028 // Traversal checks conditions
1029 foreach ( array_slice( $cond, 1 ) as $value ) {
1030 $config[] = $this->recAutopromote( $value, $allowedConditions );
1031 }
1032 } elseif ( is_string( $cond[0] ) ) {
1033 // Returns $cond directly, if $cond[0] is a string
1034 $config = $cond;
1035 } else {
1036 // When $cond is equal to an APCOND_ constant value
1037 $params = array_slice( $cond, 1 );
1038 if ( $params === [ null ] ) {
1039 // Special casing for these conditions and their default of null,
1040 // to replace their values with $wgAutoConfirmCount/$wgAutoConfirmAge as appropriate
1041 if ( $cond[0] === APCOND_EDITCOUNT ) {
1042 $params = [ $this->getConfig()->get( MainConfigNames::AutoConfirmCount ) ];
1043 } elseif ( $cond[0] === APCOND_AGE ) {
1044 $params = [ $this->getConfig()->get( MainConfigNames::AutoConfirmAge ) ];
1045 }
1046 }
1047 $config = [
1048 'condname' => array_search( $cond[0], $allowedConditions ),
1049 'params' => $params
1050 ];
1051 ApiResult::setIndexedTagName( $config, 'params' );
1052 }
1053 } elseif ( is_string( $cond ) ) {
1054 $config = $cond;
1055 } else {
1056 // When $cond is equal to an APCOND_ constant value
1057 $config = [
1058 'condname' => array_search( $cond, $allowedConditions ),
1059 'params' => []
1060 ];
1061 ApiResult::setIndexedTagName( $config, 'params' );
1062 }
1063
1064 return $config;
1065 }
1066
1067 public function appendSubscribedHooks( $property ) {
1068 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1069 $hookNames = $hookContainer->getHookNames();
1070 sort( $hookNames );
1071
1072 $data = [];
1073 foreach ( $hookNames as $name ) {
1074 $arr = [
1075 'name' => $name,
1076 'subscribers' => $hookContainer->getHandlerDescriptions( $name ),
1077 ];
1078
1079 ApiResult::setArrayType( $arr['subscribers'], 'array' );
1080 ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
1081 $data[] = $arr;
1082 }
1083
1084 ApiResult::setIndexedTagName( $data, 'hook' );
1085
1086 return $this->getResult()->addValue( 'query', $property, $data );
1087 }
1088
1089 public function getCacheMode( $params ) {
1090 // Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
1091 if ( $this->getConfig()->get( MainConfigNames::ExtraInterlanguageLinkPrefixes ) &&
1092 in_array( 'interwikimap', $params['prop'] ?? [] )
1093 ) {
1094 return 'anon-public-user-private';
1095 }
1096
1097 return 'public';
1098 }
1099
1100 public function getAllowedParams() {
1101 return [
1102 'prop' => [
1103 ParamValidator::PARAM_DEFAULT => 'general',
1104 ParamValidator::PARAM_ISMULTI => true,
1105 ParamValidator::PARAM_TYPE => [
1106 'general',
1107 'namespaces',
1108 'namespacealiases',
1109 'specialpagealiases',
1110 'magicwords',
1111 'interwikimap',
1112 'dbrepllag',
1113 'statistics',
1114 'usergroups',
1115 'autocreatetempuser',
1116 'clientlibraries',
1117 'libraries',
1118 'extensions',
1119 'fileextensions',
1120 'rightsinfo',
1121 'restrictions',
1122 'languages',
1123 'languagevariants',
1124 'skins',
1125 'extensiontags',
1126 'functionhooks',
1127 'showhooks',
1128 'variables',
1129 'protocols',
1130 'defaultoptions',
1131 'uploaddialog',
1132 'autopromote',
1133 'autopromoteonce',
1134 ],
1136 ],
1137 'filteriw' => [
1138 ParamValidator::PARAM_TYPE => [
1139 'local',
1140 '!local',
1141 ]
1142 ],
1143 'showalldb' => false,
1144 'numberingroup' => false,
1145 'inlanguagecode' => null,
1146 ];
1147 }
1148
1149 protected function getExamplesMessages() {
1150 return [
1151 'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
1152 => 'apihelp-query+siteinfo-example-simple',
1153 'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
1154 => 'apihelp-query+siteinfo-example-interwiki',
1155 'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
1156 => 'apihelp-query+siteinfo-example-replag',
1157 ];
1158 }
1159
1160 public function getHelpUrls() {
1161 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Siteinfo';
1162 }
1163}
getDB()
const APCOND_AGE
Definition Defines.php:177
const PROTO_CURRENT
Definition Defines.php:207
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:204
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:1542
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:942
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1786
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:680
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:820
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:765
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:222
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.