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