Merge "Remove parameter 'options' from hook 'SkinEditSectionLinks'"
[lhc/web/wiklou.git] / includes / api / ApiQuerySiteinfo.php
1 <?php
2 /**
3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22 use MediaWiki\MediaWikiServices;
23
24 /**
25 * A query action to return meta information about the wiki site.
26 *
27 * @ingroup API
28 */
29 class ApiQuerySiteinfo extends ApiQueryBase {
30
31 public function __construct( ApiQuery $query, $moduleName ) {
32 parent::__construct( $query, $moduleName, 'si' );
33 }
34
35 public function execute() {
36 $params = $this->extractRequestParams();
37 $done = [];
38 $fit = false;
39 foreach ( $params['prop'] as $p ) {
40 switch ( $p ) {
41 case 'general':
42 $fit = $this->appendGeneralInfo( $p );
43 break;
44 case 'namespaces':
45 $fit = $this->appendNamespaces( $p );
46 break;
47 case 'namespacealiases':
48 $fit = $this->appendNamespaceAliases( $p );
49 break;
50 case 'specialpagealiases':
51 $fit = $this->appendSpecialPageAliases( $p );
52 break;
53 case 'magicwords':
54 $fit = $this->appendMagicWords( $p );
55 break;
56 case 'interwikimap':
57 $fit = $this->appendInterwikiMap( $p, $params['filteriw'] );
58 break;
59 case 'dbrepllag':
60 $fit = $this->appendDbReplLagInfo( $p, $params['showalldb'] );
61 break;
62 case 'statistics':
63 $fit = $this->appendStatistics( $p );
64 break;
65 case 'usergroups':
66 $fit = $this->appendUserGroups( $p, $params['numberingroup'] );
67 break;
68 case 'libraries':
69 $fit = $this->appendInstalledLibraries( $p );
70 break;
71 case 'extensions':
72 $fit = $this->appendExtensions( $p );
73 break;
74 case 'fileextensions':
75 $fit = $this->appendFileExtensions( $p );
76 break;
77 case 'rightsinfo':
78 $fit = $this->appendRightsInfo( $p );
79 break;
80 case 'restrictions':
81 $fit = $this->appendRestrictions( $p );
82 break;
83 case 'languages':
84 $fit = $this->appendLanguages( $p );
85 break;
86 case 'languagevariants':
87 $fit = $this->appendLanguageVariants( $p );
88 break;
89 case 'skins':
90 $fit = $this->appendSkins( $p );
91 break;
92 case 'extensiontags':
93 $fit = $this->appendExtensionTags( $p );
94 break;
95 case 'functionhooks':
96 $fit = $this->appendFunctionHooks( $p );
97 break;
98 case 'showhooks':
99 $fit = $this->appendSubscribedHooks( $p );
100 break;
101 case 'variables':
102 $fit = $this->appendVariables( $p );
103 break;
104 case 'protocols':
105 $fit = $this->appendProtocols( $p );
106 break;
107 case 'defaultoptions':
108 $fit = $this->appendDefaultOptions( $p );
109 break;
110 case 'uploaddialog':
111 $fit = $this->appendUploadDialog( $p );
112 break;
113 default:
114 ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" ); // @codeCoverageIgnore
115 }
116 if ( !$fit ) {
117 // Abuse siprop as a query-continue parameter
118 // and set it to all unprocessed props
119 $this->setContinueEnumParameter( 'prop', implode( '|',
120 array_diff( $params['prop'], $done ) ) );
121 break;
122 }
123 $done[] = $p;
124 }
125 }
126
127 protected function appendGeneralInfo( $property ) {
128 $config = $this->getConfig();
129
130 $data = [];
131 $mainPage = Title::newMainPage();
132 $data['mainpage'] = $mainPage->getPrefixedText();
133 $data['base'] = wfExpandUrl( $mainPage->getFullURL(), PROTO_CURRENT );
134 $data['sitename'] = $config->get( 'Sitename' );
135
136 // wgLogo can either be a relative or an absolute path
137 // make sure we always return an absolute path
138 $data['logo'] = wfExpandUrl( $config->get( 'Logo' ), PROTO_RELATIVE );
139
140 $data['generator'] = "MediaWiki {$config->get( 'Version' )}";
141
142 $data['phpversion'] = PHP_VERSION;
143 $data['phpsapi'] = PHP_SAPI;
144 if ( defined( 'HHVM_VERSION' ) ) {
145 $data['hhvmversion'] = HHVM_VERSION; // @codeCoverageIgnore
146 }
147 $data['dbtype'] = $config->get( 'DBtype' );
148 $data['dbversion'] = $this->getDB()->getServerVersion();
149
150 $allowFrom = [ '' ];
151 $allowException = true;
152 if ( !$config->get( 'AllowExternalImages' ) ) {
153 $data['imagewhitelistenabled'] = (bool)$config->get( 'EnableImageWhitelist' );
154 $allowFrom = $config->get( 'AllowExternalImagesFrom' );
155 $allowException = !empty( $allowFrom );
156 }
157 if ( $allowException ) {
158 $data['externalimages'] = (array)$allowFrom;
159 ApiResult::setIndexedTagName( $data['externalimages'], 'prefix' );
160 }
161
162 $data['langconversion'] = !$config->get( 'DisableLangConversion' );
163 $data['titleconversion'] = !$config->get( 'DisableTitleConversion' );
164
165 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
166 if ( $contLang->linkPrefixExtension() ) {
167 $linkPrefixCharset = $contLang->linkPrefixCharset();
168 $data['linkprefixcharset'] = $linkPrefixCharset;
169 // For backwards compatibility
170 $data['linkprefix'] = "/^((?>.*[^$linkPrefixCharset]|))(.+)$/sDu";
171 } else {
172 $data['linkprefixcharset'] = '';
173 $data['linkprefix'] = '';
174 }
175
176 $linktrail = $contLang->linkTrail();
177 $data['linktrail'] = $linktrail ?: '';
178
179 $data['legaltitlechars'] = Title::legalChars();
180 $data['invalidusernamechars'] = $config->get( 'InvalidUsernameCharacters' );
181
182 $data['allunicodefixes'] = (bool)$config->get( 'AllUnicodeFixes' );
183 $data['fixarabicunicode'] = (bool)$config->get( 'FixArabicUnicode' );
184 $data['fixmalayalamunicode'] = (bool)$config->get( 'FixMalayalamUnicode' );
185
186 global $IP;
187 $git = SpecialVersion::getGitHeadSha1( $IP );
188 if ( $git ) {
189 $data['git-hash'] = $git;
190 $data['git-branch'] =
191 SpecialVersion::getGitCurrentBranch( $GLOBALS['IP'] );
192 }
193
194 // 'case-insensitive' option is reserved for future
195 $data['case'] = $config->get( 'CapitalLinks' ) ? 'first-letter' : 'case-sensitive';
196 $data['lang'] = $config->get( 'LanguageCode' );
197
198 $fallbacks = [];
199 foreach ( $contLang->getFallbackLanguages() as $code ) {
200 $fallbacks[] = [ 'code' => $code ];
201 }
202 $data['fallback'] = $fallbacks;
203 ApiResult::setIndexedTagName( $data['fallback'], 'lang' );
204
205 if ( $contLang->hasVariants() ) {
206 $variants = [];
207 foreach ( $contLang->getVariants() as $code ) {
208 $variants[] = [
209 'code' => $code,
210 'name' => $contLang->getVariantname( $code ),
211 ];
212 }
213 $data['variants'] = $variants;
214 ApiResult::setIndexedTagName( $data['variants'], 'lang' );
215 }
216
217 $data['rtl'] = $contLang->isRTL();
218 $data['fallback8bitEncoding'] = $contLang->fallback8bitEncoding();
219
220 $data['readonly'] = wfReadOnly();
221 if ( $data['readonly'] ) {
222 $data['readonlyreason'] = wfReadOnlyReason();
223 }
224 $data['writeapi'] = true; // Deprecated since MW 1.32
225
226 $data['maxarticlesize'] = $config->get( 'MaxArticleSize' ) * 1024;
227
228 $tz = $config->get( 'Localtimezone' );
229 $offset = $config->get( 'LocalTZoffset' );
230 $data['timezone'] = $tz;
231 $data['timeoffset'] = (int)$offset;
232 $data['articlepath'] = $config->get( 'ArticlePath' );
233 $data['scriptpath'] = $config->get( 'ScriptPath' );
234 $data['script'] = $config->get( 'Script' );
235 $data['variantarticlepath'] = $config->get( 'VariantArticlePath' );
236 $data[ApiResult::META_BC_BOOLS][] = 'variantarticlepath';
237 $data['server'] = $config->get( 'Server' );
238 $data['servername'] = $config->get( 'ServerName' );
239 $data['wikiid'] = WikiMap::getWikiIdFromDbDomain( WikiMap::getCurrentWikiDbDomain() );
240 $data['time'] = wfTimestamp( TS_ISO_8601, time() );
241
242 $data['misermode'] = (bool)$config->get( 'MiserMode' );
243
244 $data['uploadsenabled'] = UploadBase::isEnabled();
245 $data['maxuploadsize'] = UploadBase::getMaxUploadSize();
246 $data['minuploadchunksize'] = (int)$config->get( 'MinUploadChunkSize' );
247
248 $data['galleryoptions'] = $config->get( 'GalleryOptions' );
249
250 $data['thumblimits'] = $config->get( 'ThumbLimits' );
251 ApiResult::setArrayType( $data['thumblimits'], 'BCassoc' );
252 ApiResult::setIndexedTagName( $data['thumblimits'], 'limit' );
253 $data['imagelimits'] = [];
254 ApiResult::setArrayType( $data['imagelimits'], 'BCassoc' );
255 ApiResult::setIndexedTagName( $data['imagelimits'], 'limit' );
256 foreach ( $config->get( 'ImageLimits' ) as $k => $limit ) {
257 $data['imagelimits'][$k] = [ 'width' => $limit[0], 'height' => $limit[1] ];
258 }
259
260 $favicon = $config->get( 'Favicon' );
261 if ( !empty( $favicon ) ) {
262 // wgFavicon can either be a relative or an absolute path
263 // make sure we always return an absolute path
264 $data['favicon'] = wfExpandUrl( $favicon, PROTO_RELATIVE );
265 }
266
267 $data['centralidlookupprovider'] = $config->get( 'CentralIdLookupProvider' );
268 $providerIds = array_keys( $config->get( 'CentralIdLookupProviders' ) );
269 $data['allcentralidlookupproviders'] = $providerIds;
270
271 $data['interwikimagic'] = (bool)$config->get( 'InterwikiMagic' );
272 $data['magiclinks'] = $config->get( 'EnableMagicLinks' );
273
274 $data['categorycollation'] = $config->get( 'CategoryCollation' );
275
276 Hooks::run( 'APIQuerySiteInfoGeneralInfo', [ $this, &$data ] );
277
278 return $this->getResult()->addValue( 'query', $property, $data );
279 }
280
281 protected function appendNamespaces( $property ) {
282 $data = [
283 ApiResult::META_TYPE => 'assoc',
284 ];
285 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
286 foreach (
287 MediaWikiServices::getInstance()->getContentLanguage()->getFormattedNamespaces()
288 as $ns => $title
289 ) {
290 $data[$ns] = [
291 'id' => (int)$ns,
292 'case' => $nsInfo->isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
293 ];
294 ApiResult::setContentValue( $data[$ns], 'name', $title );
295 $canonical = $nsInfo->getCanonicalName( $ns );
296
297 $data[$ns]['subpages'] = $nsInfo->hasSubpages( $ns );
298
299 if ( $canonical ) {
300 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
301 }
302
303 $data[$ns]['content'] = $nsInfo->isContent( $ns );
304 $data[$ns]['nonincludable'] = $nsInfo->isNonincludable( $ns );
305
306 $contentmodel = $nsInfo->getNamespaceContentModel( $ns );
307 if ( $contentmodel ) {
308 $data[$ns]['defaultcontentmodel'] = $contentmodel;
309 }
310 }
311
312 ApiResult::setArrayType( $data, 'assoc' );
313 ApiResult::setIndexedTagName( $data, 'ns' );
314
315 return $this->getResult()->addValue( 'query', $property, $data );
316 }
317
318 protected function appendNamespaceAliases( $property ) {
319 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
320 $aliases = array_merge( $this->getConfig()->get( 'NamespaceAliases' ),
321 $contLang->getNamespaceAliases() );
322 $namespaces = $contLang->getNamespaces();
323 $data = [];
324 foreach ( $aliases as $title => $ns ) {
325 if ( $namespaces[$ns] == $title ) {
326 // Don't list duplicates
327 continue;
328 }
329 $item = [
330 'id' => (int)$ns
331 ];
332 ApiResult::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
333 $data[] = $item;
334 }
335
336 sort( $data );
337
338 ApiResult::setIndexedTagName( $data, 'ns' );
339
340 return $this->getResult()->addValue( 'query', $property, $data );
341 }
342
343 protected function appendSpecialPageAliases( $property ) {
344 $data = [];
345 $services = MediaWikiServices::getInstance();
346 $aliases = $services->getContentLanguage()->getSpecialPageAliases();
347 foreach ( $services->getSpecialPageFactory()->getNames() as $specialpage ) {
348 if ( isset( $aliases[$specialpage] ) ) {
349 $arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ];
350 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
351 $data[] = $arr;
352 }
353 }
354 ApiResult::setIndexedTagName( $data, 'specialpage' );
355
356 return $this->getResult()->addValue( 'query', $property, $data );
357 }
358
359 protected function appendMagicWords( $property ) {
360 $data = [];
361 foreach (
362 MediaWikiServices::getInstance()->getContentLanguage()->getMagicWords()
363 as $magicword => $aliases
364 ) {
365 $caseSensitive = array_shift( $aliases );
366 $arr = [ 'name' => $magicword, 'aliases' => $aliases ];
367 $arr['case-sensitive'] = (bool)$caseSensitive;
368 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
369 $data[] = $arr;
370 }
371 ApiResult::setIndexedTagName( $data, 'magicword' );
372
373 return $this->getResult()->addValue( 'query', $property, $data );
374 }
375
376 protected function appendInterwikiMap( $property, $filter ) {
377 if ( $filter === 'local' ) {
378 $local = 1;
379 } elseif ( $filter === '!local' ) {
380 $local = 0;
381 } else {
382 // $filter === null
383 $local = null;
384 }
385
386 $params = $this->extractRequestParams();
387 $langCode = $params['inlanguagecode'] ?? '';
388 $langNames = Language::fetchLanguageNames( $langCode );
389
390 $getPrefixes = MediaWikiServices::getInstance()->getInterwikiLookup()->getAllPrefixes( $local );
391 $extraLangPrefixes = $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' );
392 $localInterwikis = $this->getConfig()->get( 'LocalInterwikis' );
393 $data = [];
394
395 foreach ( $getPrefixes as $row ) {
396 $prefix = $row['iw_prefix'];
397 $val = [];
398 $val['prefix'] = $prefix;
399 if ( isset( $row['iw_local'] ) && $row['iw_local'] == '1' ) {
400 $val['local'] = true;
401 }
402 if ( isset( $row['iw_trans'] ) && $row['iw_trans'] == '1' ) {
403 $val['trans'] = true;
404 }
405
406 if ( isset( $langNames[$prefix] ) ) {
407 $val['language'] = $langNames[$prefix];
408 }
409 if ( in_array( $prefix, $localInterwikis ) ) {
410 $val['localinterwiki'] = true;
411 }
412 if ( in_array( $prefix, $extraLangPrefixes ) ) {
413 $val['extralanglink'] = true;
414
415 $linktext = wfMessage( "interlanguage-link-$prefix" );
416 if ( !$linktext->isDisabled() ) {
417 $val['linktext'] = $linktext->text();
418 }
419
420 $sitename = wfMessage( "interlanguage-link-sitename-$prefix" );
421 if ( !$sitename->isDisabled() ) {
422 $val['sitename'] = $sitename->text();
423 }
424 }
425
426 $val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
427 $val['protorel'] = substr( $row['iw_url'], 0, 2 ) == '//';
428 if ( isset( $row['iw_wikiid'] ) && $row['iw_wikiid'] !== '' ) {
429 $val['wikiid'] = $row['iw_wikiid'];
430 }
431 if ( isset( $row['iw_api'] ) && $row['iw_api'] !== '' ) {
432 $val['api'] = $row['iw_api'];
433 }
434
435 $data[] = $val;
436 }
437
438 ApiResult::setIndexedTagName( $data, 'iw' );
439
440 return $this->getResult()->addValue( 'query', $property, $data );
441 }
442
443 protected function appendDbReplLagInfo( $property, $includeAll ) {
444 $data = [];
445 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
446 $showHostnames = $this->getConfig()->get( 'ShowHostnames' );
447 if ( $includeAll ) {
448 if ( !$showHostnames ) {
449 $this->dieWithError( 'apierror-siteinfo-includealldenied', 'includeAllDenied' );
450 }
451
452 $lags = $lb->getLagTimes();
453 foreach ( $lags as $i => $lag ) {
454 $data[] = [
455 'host' => $lb->getServerName( $i ),
456 'lag' => $lag
457 ];
458 }
459 } else {
460 list( , $lag, $index ) = $lb->getMaxLag();
461 $data[] = [
462 'host' => $showHostnames
463 ? $lb->getServerName( $index )
464 : '',
465 'lag' => $lag
466 ];
467 }
468
469 ApiResult::setIndexedTagName( $data, 'db' );
470
471 return $this->getResult()->addValue( 'query', $property, $data );
472 }
473
474 protected function appendStatistics( $property ) {
475 $data = [];
476 $data['pages'] = (int)SiteStats::pages();
477 $data['articles'] = (int)SiteStats::articles();
478 $data['edits'] = (int)SiteStats::edits();
479 $data['images'] = (int)SiteStats::images();
480 $data['users'] = (int)SiteStats::users();
481 $data['activeusers'] = (int)SiteStats::activeUsers();
482 $data['admins'] = (int)SiteStats::numberingroup( 'sysop' );
483 $data['jobs'] = (int)SiteStats::jobs();
484
485 Hooks::run( 'APIQuerySiteInfoStatisticsInfo', [ &$data ] );
486
487 return $this->getResult()->addValue( 'query', $property, $data );
488 }
489
490 protected function appendUserGroups( $property, $numberInGroup ) {
491 $config = $this->getConfig();
492
493 $data = [];
494 $result = $this->getResult();
495 $allGroups = array_values( User::getAllGroups() );
496 foreach ( $config->get( 'GroupPermissions' ) as $group => $permissions ) {
497 $arr = [
498 'name' => $group,
499 'rights' => array_keys( $permissions, true ),
500 ];
501
502 if ( $numberInGroup ) {
503 $autopromote = $config->get( 'Autopromote' );
504
505 if ( $group == 'user' ) {
506 $arr['number'] = SiteStats::users();
507 // '*' and autopromote groups have no size
508 } elseif ( $group !== '*' && !isset( $autopromote[$group] ) ) {
509 $arr['number'] = SiteStats::numberingroup( $group );
510 }
511 }
512
513 $groupArr = [
514 'add' => $config->get( 'AddGroups' ),
515 'remove' => $config->get( 'RemoveGroups' ),
516 'add-self' => $config->get( 'GroupsAddToSelf' ),
517 'remove-self' => $config->get( 'GroupsRemoveFromSelf' )
518 ];
519
520 foreach ( $groupArr as $type => $rights ) {
521 if ( isset( $rights[$group] ) ) {
522 if ( $rights[$group] === true ) {
523 $groups = $allGroups;
524 } else {
525 $groups = array_intersect( $rights[$group], $allGroups );
526 }
527 if ( $groups ) {
528 $arr[$type] = $groups;
529 ApiResult::setArrayType( $arr[$type], 'BCarray' );
530 ApiResult::setIndexedTagName( $arr[$type], 'group' );
531 }
532 }
533 }
534
535 ApiResult::setIndexedTagName( $arr['rights'], 'permission' );
536 $data[] = $arr;
537 }
538
539 ApiResult::setIndexedTagName( $data, 'group' );
540
541 return $result->addValue( 'query', $property, $data );
542 }
543
544 protected function appendFileExtensions( $property ) {
545 $data = [];
546 foreach ( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) as $ext ) {
547 $data[] = [ 'ext' => $ext ];
548 }
549 ApiResult::setIndexedTagName( $data, 'fe' );
550
551 return $this->getResult()->addValue( 'query', $property, $data );
552 }
553
554 protected function appendInstalledLibraries( $property ) {
555 global $IP;
556 $path = "$IP/vendor/composer/installed.json";
557 if ( !file_exists( $path ) ) {
558 return true;
559 }
560
561 $data = [];
562 $installed = new ComposerInstalled( $path );
563 foreach ( $installed->getInstalledDependencies() as $name => $info ) {
564 if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
565 // Skip any extensions or skins since they'll be listed
566 // in their proper section
567 continue;
568 }
569 $data[] = [
570 'name' => $name,
571 'version' => $info['version'],
572 ];
573 }
574 ApiResult::setIndexedTagName( $data, 'library' );
575
576 return $this->getResult()->addValue( 'query', $property, $data );
577 }
578
579 protected function appendExtensions( $property ) {
580 $data = [];
581 foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $type => $extensions ) {
582 foreach ( $extensions as $ext ) {
583 $ret = [];
584 $ret['type'] = $type;
585 if ( isset( $ext['name'] ) ) {
586 $ret['name'] = $ext['name'];
587 }
588 if ( isset( $ext['namemsg'] ) ) {
589 $ret['namemsg'] = $ext['namemsg'];
590 }
591 if ( isset( $ext['description'] ) ) {
592 $ret['description'] = $ext['description'];
593 }
594 if ( isset( $ext['descriptionmsg'] ) ) {
595 // Can be a string or [ key, param1, param2, ... ]
596 if ( is_array( $ext['descriptionmsg'] ) ) {
597 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
598 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
599 ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
600 } else {
601 $ret['descriptionmsg'] = $ext['descriptionmsg'];
602 }
603 }
604 if ( isset( $ext['author'] ) ) {
605 $ret['author'] = is_array( $ext['author'] ) ?
606 implode( ', ', $ext['author'] ) : $ext['author'];
607 }
608 if ( isset( $ext['url'] ) ) {
609 $ret['url'] = $ext['url'];
610 }
611 if ( isset( $ext['version'] ) ) {
612 $ret['version'] = $ext['version'];
613 }
614 if ( isset( $ext['path'] ) ) {
615 $extensionPath = dirname( $ext['path'] );
616 $gitInfo = new GitInfo( $extensionPath );
617 $vcsVersion = $gitInfo->getHeadSHA1();
618 if ( $vcsVersion !== false ) {
619 $ret['vcs-system'] = 'git';
620 $ret['vcs-version'] = $vcsVersion;
621 $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
622 $vcsDate = $gitInfo->getHeadCommitDate();
623 if ( $vcsDate !== false ) {
624 $ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $vcsDate );
625 }
626 }
627
628 if ( SpecialVersion::getExtLicenseFileName( $extensionPath ) ) {
629 $ret['license-name'] = $ext['license-name'] ?? '';
630 $ret['license'] = SpecialPage::getTitleFor(
631 'Version',
632 "License/{$ext['name']}"
633 )->getLinkURL();
634 }
635
636 if ( SpecialVersion::getExtAuthorsFileName( $extensionPath ) ) {
637 $ret['credits'] = SpecialPage::getTitleFor(
638 'Version',
639 "Credits/{$ext['name']}"
640 )->getLinkURL();
641 }
642 }
643 $data[] = $ret;
644 }
645 }
646
647 ApiResult::setIndexedTagName( $data, 'ext' );
648
649 return $this->getResult()->addValue( 'query', $property, $data );
650 }
651
652 protected function appendRightsInfo( $property ) {
653 $config = $this->getConfig();
654 $rightsPage = $config->get( 'RightsPage' );
655 if ( is_string( $rightsPage ) ) {
656 $title = Title::newFromText( $rightsPage );
657 $url = wfExpandUrl( $title, PROTO_CURRENT );
658 } else {
659 $title = false;
660 $url = $config->get( 'RightsUrl' );
661 }
662 $text = $config->get( 'RightsText' );
663 if ( $title && !strlen( $text ) ) {
664 $text = $title->getPrefixedText();
665 }
666
667 $data = [
668 'url' => (string)$url,
669 'text' => (string)$text,
670 ];
671
672 return $this->getResult()->addValue( 'query', $property, $data );
673 }
674
675 protected function appendRestrictions( $property ) {
676 $config = $this->getConfig();
677 $data = [
678 'types' => $config->get( 'RestrictionTypes' ),
679 'levels' => $config->get( 'RestrictionLevels' ),
680 'cascadinglevels' => $config->get( 'CascadingRestrictionLevels' ),
681 'semiprotectedlevels' => $config->get( 'SemiprotectedRestrictionLevels' ),
682 ];
683
684 ApiResult::setArrayType( $data['types'], 'BCarray' );
685 ApiResult::setArrayType( $data['levels'], 'BCarray' );
686 ApiResult::setArrayType( $data['cascadinglevels'], 'BCarray' );
687 ApiResult::setArrayType( $data['semiprotectedlevels'], 'BCarray' );
688
689 ApiResult::setIndexedTagName( $data['types'], 'type' );
690 ApiResult::setIndexedTagName( $data['levels'], 'level' );
691 ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
692 ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
693
694 return $this->getResult()->addValue( 'query', $property, $data );
695 }
696
697 public function appendLanguages( $property ) {
698 $params = $this->extractRequestParams();
699 $langCode = $params['inlanguagecode'] ?? '';
700 $langNames = Language::fetchLanguageNames( $langCode );
701
702 $data = [];
703
704 foreach ( $langNames as $code => $name ) {
705 $lang = [
706 'code' => $code,
707 'bcp47' => LanguageCode::bcp47( $code ),
708 ];
709 ApiResult::setContentValue( $lang, 'name', $name );
710 $data[] = $lang;
711 }
712 ApiResult::setIndexedTagName( $data, 'lang' );
713
714 return $this->getResult()->addValue( 'query', $property, $data );
715 }
716
717 // Export information about which page languages will trigger
718 // language conversion. (T153341)
719 public function appendLanguageVariants( $property ) {
720 $langNames = LanguageConverter::$languagesWithVariants;
721 if ( $this->getConfig()->get( 'DisableLangConversion' ) ) {
722 // Ensure result is empty if language conversion is disabled.
723 $langNames = [];
724 }
725 sort( $langNames );
726
727 $data = [];
728 foreach ( $langNames as $langCode ) {
729 $lang = Language::factory( $langCode );
730 if ( $lang->getConverter() instanceof FakeConverter ) {
731 // Only languages which do not return instances of
732 // FakeConverter implement language conversion.
733 continue;
734 }
735 $data[$langCode] = [];
736 ApiResult::setIndexedTagName( $data[$langCode], 'variant' );
737 ApiResult::setArrayType( $data[$langCode], 'kvp', 'code' );
738
739 $variants = $lang->getVariants();
740 sort( $variants );
741 foreach ( $variants as $v ) {
742 $fallbacks = $lang->getConverter()->getVariantFallbacks( $v );
743 if ( !is_array( $fallbacks ) ) {
744 $fallbacks = [ $fallbacks ];
745 }
746 $data[$langCode][$v] = [
747 'fallbacks' => $fallbacks,
748 ];
749 ApiResult::setIndexedTagName(
750 $data[$langCode][$v]['fallbacks'], 'variant'
751 );
752 }
753 }
754 ApiResult::setIndexedTagName( $data, 'lang' );
755 ApiResult::setArrayType( $data, 'kvp', 'code' );
756
757 return $this->getResult()->addValue( 'query', $property, $data );
758 }
759
760 public function appendSkins( $property ) {
761 $data = [];
762 $allowed = Skin::getAllowedSkins();
763 $default = Skin::normalizeKey( 'default' );
764 foreach ( Skin::getSkinNames() as $name => $displayName ) {
765 $msg = $this->msg( "skinname-{$name}" );
766 $code = $this->getParameter( 'inlanguagecode' );
767 if ( $code && Language::isValidCode( $code ) ) {
768 $msg->inLanguage( $code );
769 } else {
770 $msg->inContentLanguage();
771 }
772 if ( $msg->exists() ) {
773 $displayName = $msg->text();
774 }
775 $skin = [ 'code' => $name ];
776 ApiResult::setContentValue( $skin, 'name', $displayName );
777 if ( !isset( $allowed[$name] ) ) {
778 $skin['unusable'] = true;
779 }
780 if ( $name === $default ) {
781 $skin['default'] = true;
782 }
783 $data[] = $skin;
784 }
785 ApiResult::setIndexedTagName( $data, 'skin' );
786
787 return $this->getResult()->addValue( 'query', $property, $data );
788 }
789
790 public function appendExtensionTags( $property ) {
791 $tags = array_map(
792 function ( $item ) {
793 return "<$item>";
794 },
795 MediaWikiServices::getInstance()->getParser()->getTags()
796 );
797 ApiResult::setArrayType( $tags, 'BCarray' );
798 ApiResult::setIndexedTagName( $tags, 't' );
799
800 return $this->getResult()->addValue( 'query', $property, $tags );
801 }
802
803 public function appendFunctionHooks( $property ) {
804 $hooks = MediaWikiServices::getInstance()->getParser()->getFunctionHooks();
805 ApiResult::setArrayType( $hooks, 'BCarray' );
806 ApiResult::setIndexedTagName( $hooks, 'h' );
807
808 return $this->getResult()->addValue( 'query', $property, $hooks );
809 }
810
811 public function appendVariables( $property ) {
812 $variables = MediaWikiServices::getInstance()->getMagicWordFactory()->getVariableIDs();
813 ApiResult::setArrayType( $variables, 'BCarray' );
814 ApiResult::setIndexedTagName( $variables, 'v' );
815
816 return $this->getResult()->addValue( 'query', $property, $variables );
817 }
818
819 public function appendProtocols( $property ) {
820 // Make a copy of the global so we don't try to set the _element key of it - T47130
821 $protocols = array_values( $this->getConfig()->get( 'UrlProtocols' ) );
822 ApiResult::setArrayType( $protocols, 'BCarray' );
823 ApiResult::setIndexedTagName( $protocols, 'p' );
824
825 return $this->getResult()->addValue( 'query', $property, $protocols );
826 }
827
828 public function appendDefaultOptions( $property ) {
829 $options = User::getDefaultOptions();
830 $options[ApiResult::META_BC_BOOLS] = array_keys( $options );
831 return $this->getResult()->addValue( 'query', $property, $options );
832 }
833
834 public function appendUploadDialog( $property ) {
835 $config = $this->getConfig()->get( 'UploadDialog' );
836 return $this->getResult()->addValue( 'query', $property, $config );
837 }
838
839 public function appendSubscribedHooks( $property ) {
840 $hooks = $this->getConfig()->get( 'Hooks' );
841 $myWgHooks = $hooks;
842 ksort( $myWgHooks );
843
844 $data = [];
845 foreach ( $myWgHooks as $name => $subscribers ) {
846 $arr = [
847 'name' => $name,
848 'subscribers' => array_map( [ SpecialVersion::class, 'arrayToString' ], $subscribers ),
849 ];
850
851 ApiResult::setArrayType( $arr['subscribers'], 'array' );
852 ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
853 $data[] = $arr;
854 }
855
856 ApiResult::setIndexedTagName( $data, 'hook' );
857
858 return $this->getResult()->addValue( 'query', $property, $data );
859 }
860
861 public function getCacheMode( $params ) {
862 // Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
863 if (
864 count( $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' ) ) &&
865 !is_null( $params['prop'] ) &&
866 in_array( 'interwikimap', $params['prop'] )
867 ) {
868 return 'anon-public-user-private';
869 }
870
871 return 'public';
872 }
873
874 public function getAllowedParams() {
875 return [
876 'prop' => [
877 ApiBase::PARAM_DFLT => 'general',
878 ApiBase::PARAM_ISMULTI => true,
879 ApiBase::PARAM_TYPE => [
880 'general',
881 'namespaces',
882 'namespacealiases',
883 'specialpagealiases',
884 'magicwords',
885 'interwikimap',
886 'dbrepllag',
887 'statistics',
888 'usergroups',
889 'libraries',
890 'extensions',
891 'fileextensions',
892 'rightsinfo',
893 'restrictions',
894 'languages',
895 'languagevariants',
896 'skins',
897 'extensiontags',
898 'functionhooks',
899 'showhooks',
900 'variables',
901 'protocols',
902 'defaultoptions',
903 'uploaddialog',
904 ],
905 ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
906 ],
907 'filteriw' => [
908 ApiBase::PARAM_TYPE => [
909 'local',
910 '!local',
911 ]
912 ],
913 'showalldb' => false,
914 'numberingroup' => false,
915 'inlanguagecode' => null,
916 ];
917 }
918
919 protected function getExamplesMessages() {
920 return [
921 'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
922 => 'apihelp-query+siteinfo-example-simple',
923 'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
924 => 'apihelp-query+siteinfo-example-interwiki',
925 'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
926 => 'apihelp-query+siteinfo-example-replag',
927 ];
928 }
929
930 public function getHelpUrls() {
931 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Siteinfo';
932 }
933 }