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