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