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