HTML escape parameter 'text' of 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 foreach (
286 MediaWikiServices::getInstance()->getContentLanguage()->getFormattedNamespaces()
287 as $ns => $title
288 ) {
289 $data[$ns] = [
290 'id' => (int)$ns,
291 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
292 ];
293 ApiResult::setContentValue( $data[$ns], 'name', $title );
294 $canonical = MWNamespace::getCanonicalName( $ns );
295
296 $data[$ns]['subpages'] = MWNamespace::hasSubpages( $ns );
297
298 if ( $canonical ) {
299 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
300 }
301
302 $data[$ns]['content'] = MWNamespace::isContent( $ns );
303 $data[$ns]['nonincludable'] = MWNamespace::isNonincludable( $ns );
304
305 $contentmodel = MWNamespace::getNamespaceContentModel( $ns );
306 if ( $contentmodel ) {
307 $data[$ns]['defaultcontentmodel'] = $contentmodel;
308 }
309 }
310
311 ApiResult::setArrayType( $data, 'assoc' );
312 ApiResult::setIndexedTagName( $data, 'ns' );
313
314 return $this->getResult()->addValue( 'query', $property, $data );
315 }
316
317 protected function appendNamespaceAliases( $property ) {
318 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
319 $aliases = array_merge( $this->getConfig()->get( 'NamespaceAliases' ),
320 $contLang->getNamespaceAliases() );
321 $namespaces = $contLang->getNamespaces();
322 $data = [];
323 foreach ( $aliases as $title => $ns ) {
324 if ( $namespaces[$ns] == $title ) {
325 // Don't list duplicates
326 continue;
327 }
328 $item = [
329 'id' => (int)$ns
330 ];
331 ApiResult::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
332 $data[] = $item;
333 }
334
335 sort( $data );
336
337 ApiResult::setIndexedTagName( $data, 'ns' );
338
339 return $this->getResult()->addValue( 'query', $property, $data );
340 }
341
342 protected function appendSpecialPageAliases( $property ) {
343 $data = [];
344 $services = MediaWikiServices::getInstance();
345 $aliases = $services->getContentLanguage()->getSpecialPageAliases();
346 foreach ( $services->getSpecialPageFactory()->getNames() as $specialpage ) {
347 if ( isset( $aliases[$specialpage] ) ) {
348 $arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ];
349 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
350 $data[] = $arr;
351 }
352 }
353 ApiResult::setIndexedTagName( $data, 'specialpage' );
354
355 return $this->getResult()->addValue( 'query', $property, $data );
356 }
357
358 protected function appendMagicWords( $property ) {
359 $data = [];
360 foreach (
361 MediaWikiServices::getInstance()->getContentLanguage()->getMagicWords()
362 as $magicword => $aliases
363 ) {
364 $caseSensitive = array_shift( $aliases );
365 $arr = [ 'name' => $magicword, 'aliases' => $aliases ];
366 $arr['case-sensitive'] = (bool)$caseSensitive;
367 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
368 $data[] = $arr;
369 }
370 ApiResult::setIndexedTagName( $data, 'magicword' );
371
372 return $this->getResult()->addValue( 'query', $property, $data );
373 }
374
375 protected function appendInterwikiMap( $property, $filter ) {
376 if ( $filter === 'local' ) {
377 $local = 1;
378 } elseif ( $filter === '!local' ) {
379 $local = 0;
380 } else {
381 // $filter === null
382 $local = null;
383 }
384
385 $params = $this->extractRequestParams();
386 $langCode = $params['inlanguagecode'] ?? '';
387 $langNames = Language::fetchLanguageNames( $langCode );
388
389 $getPrefixes = MediaWikiServices::getInstance()->getInterwikiLookup()->getAllPrefixes( $local );
390 $extraLangPrefixes = $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' );
391 $localInterwikis = $this->getConfig()->get( 'LocalInterwikis' );
392 $data = [];
393
394 foreach ( $getPrefixes as $row ) {
395 $prefix = $row['iw_prefix'];
396 $val = [];
397 $val['prefix'] = $prefix;
398 if ( isset( $row['iw_local'] ) && $row['iw_local'] == '1' ) {
399 $val['local'] = true;
400 }
401 if ( isset( $row['iw_trans'] ) && $row['iw_trans'] == '1' ) {
402 $val['trans'] = true;
403 }
404
405 if ( isset( $langNames[$prefix] ) ) {
406 $val['language'] = $langNames[$prefix];
407 }
408 if ( in_array( $prefix, $localInterwikis ) ) {
409 $val['localinterwiki'] = true;
410 }
411 if ( in_array( $prefix, $extraLangPrefixes ) ) {
412 $val['extralanglink'] = true;
413
414 $linktext = wfMessage( "interlanguage-link-$prefix" );
415 if ( !$linktext->isDisabled() ) {
416 $val['linktext'] = $linktext->text();
417 }
418
419 $sitename = wfMessage( "interlanguage-link-sitename-$prefix" );
420 if ( !$sitename->isDisabled() ) {
421 $val['sitename'] = $sitename->text();
422 }
423 }
424
425 $val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
426 $val['protorel'] = substr( $row['iw_url'], 0, 2 ) == '//';
427 if ( isset( $row['iw_wikiid'] ) && $row['iw_wikiid'] !== '' ) {
428 $val['wikiid'] = $row['iw_wikiid'];
429 }
430 if ( isset( $row['iw_api'] ) && $row['iw_api'] !== '' ) {
431 $val['api'] = $row['iw_api'];
432 }
433
434 $data[] = $val;
435 }
436
437 ApiResult::setIndexedTagName( $data, 'iw' );
438
439 return $this->getResult()->addValue( 'query', $property, $data );
440 }
441
442 protected function appendDbReplLagInfo( $property, $includeAll ) {
443 $data = [];
444 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
445 $showHostnames = $this->getConfig()->get( 'ShowHostnames' );
446 if ( $includeAll ) {
447 if ( !$showHostnames ) {
448 $this->dieWithError( 'apierror-siteinfo-includealldenied', 'includeAllDenied' );
449 }
450
451 $lags = $lb->getLagTimes();
452 foreach ( $lags as $i => $lag ) {
453 $data[] = [
454 'host' => $lb->getServerName( $i ),
455 'lag' => $lag
456 ];
457 }
458 } else {
459 list( , $lag, $index ) = $lb->getMaxLag();
460 $data[] = [
461 'host' => $showHostnames
462 ? $lb->getServerName( $index )
463 : '',
464 'lag' => $lag
465 ];
466 }
467
468 ApiResult::setIndexedTagName( $data, 'db' );
469
470 return $this->getResult()->addValue( 'query', $property, $data );
471 }
472
473 protected function appendStatistics( $property ) {
474 $data = [];
475 $data['pages'] = (int)SiteStats::pages();
476 $data['articles'] = (int)SiteStats::articles();
477 $data['edits'] = (int)SiteStats::edits();
478 $data['images'] = (int)SiteStats::images();
479 $data['users'] = (int)SiteStats::users();
480 $data['activeusers'] = (int)SiteStats::activeUsers();
481 $data['admins'] = (int)SiteStats::numberingroup( 'sysop' );
482 $data['jobs'] = (int)SiteStats::jobs();
483
484 Hooks::run( 'APIQuerySiteInfoStatisticsInfo', [ &$data ] );
485
486 return $this->getResult()->addValue( 'query', $property, $data );
487 }
488
489 protected function appendUserGroups( $property, $numberInGroup ) {
490 $config = $this->getConfig();
491
492 $data = [];
493 $result = $this->getResult();
494 $allGroups = array_values( User::getAllGroups() );
495 foreach ( $config->get( 'GroupPermissions' ) as $group => $permissions ) {
496 $arr = [
497 'name' => $group,
498 'rights' => array_keys( $permissions, true ),
499 ];
500
501 if ( $numberInGroup ) {
502 $autopromote = $config->get( 'Autopromote' );
503
504 if ( $group == 'user' ) {
505 $arr['number'] = SiteStats::users();
506 // '*' and autopromote groups have no size
507 } elseif ( $group !== '*' && !isset( $autopromote[$group] ) ) {
508 $arr['number'] = SiteStats::numberingroup( $group );
509 }
510 }
511
512 $groupArr = [
513 'add' => $config->get( 'AddGroups' ),
514 'remove' => $config->get( 'RemoveGroups' ),
515 'add-self' => $config->get( 'GroupsAddToSelf' ),
516 'remove-self' => $config->get( 'GroupsRemoveFromSelf' )
517 ];
518
519 foreach ( $groupArr as $type => $rights ) {
520 if ( isset( $rights[$group] ) ) {
521 if ( $rights[$group] === true ) {
522 $groups = $allGroups;
523 } else {
524 $groups = array_intersect( $rights[$group], $allGroups );
525 }
526 if ( $groups ) {
527 $arr[$type] = $groups;
528 ApiResult::setArrayType( $arr[$type], 'BCarray' );
529 ApiResult::setIndexedTagName( $arr[$type], 'group' );
530 }
531 }
532 }
533
534 ApiResult::setIndexedTagName( $arr['rights'], 'permission' );
535 $data[] = $arr;
536 }
537
538 ApiResult::setIndexedTagName( $data, 'group' );
539
540 return $result->addValue( 'query', $property, $data );
541 }
542
543 protected function appendFileExtensions( $property ) {
544 $data = [];
545 foreach ( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) as $ext ) {
546 $data[] = [ 'ext' => $ext ];
547 }
548 ApiResult::setIndexedTagName( $data, 'fe' );
549
550 return $this->getResult()->addValue( 'query', $property, $data );
551 }
552
553 protected function appendInstalledLibraries( $property ) {
554 global $IP;
555 $path = "$IP/vendor/composer/installed.json";
556 if ( !file_exists( $path ) ) {
557 return true;
558 }
559
560 $data = [];
561 $installed = new ComposerInstalled( $path );
562 foreach ( $installed->getInstalledDependencies() as $name => $info ) {
563 if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
564 // Skip any extensions or skins since they'll be listed
565 // in their proper section
566 continue;
567 }
568 $data[] = [
569 'name' => $name,
570 'version' => $info['version'],
571 ];
572 }
573 ApiResult::setIndexedTagName( $data, 'library' );
574
575 return $this->getResult()->addValue( 'query', $property, $data );
576 }
577
578 protected function appendExtensions( $property ) {
579 $data = [];
580 foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $type => $extensions ) {
581 foreach ( $extensions as $ext ) {
582 $ret = [];
583 $ret['type'] = $type;
584 if ( isset( $ext['name'] ) ) {
585 $ret['name'] = $ext['name'];
586 }
587 if ( isset( $ext['namemsg'] ) ) {
588 $ret['namemsg'] = $ext['namemsg'];
589 }
590 if ( isset( $ext['description'] ) ) {
591 $ret['description'] = $ext['description'];
592 }
593 if ( isset( $ext['descriptionmsg'] ) ) {
594 // Can be a string or [ key, param1, param2, ... ]
595 if ( is_array( $ext['descriptionmsg'] ) ) {
596 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
597 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
598 ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
599 } else {
600 $ret['descriptionmsg'] = $ext['descriptionmsg'];
601 }
602 }
603 if ( isset( $ext['author'] ) ) {
604 $ret['author'] = is_array( $ext['author'] ) ?
605 implode( ', ', $ext['author'] ) : $ext['author'];
606 }
607 if ( isset( $ext['url'] ) ) {
608 $ret['url'] = $ext['url'];
609 }
610 if ( isset( $ext['version'] ) ) {
611 $ret['version'] = $ext['version'];
612 }
613 if ( isset( $ext['path'] ) ) {
614 $extensionPath = dirname( $ext['path'] );
615 $gitInfo = new GitInfo( $extensionPath );
616 $vcsVersion = $gitInfo->getHeadSHA1();
617 if ( $vcsVersion !== false ) {
618 $ret['vcs-system'] = 'git';
619 $ret['vcs-version'] = $vcsVersion;
620 $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
621 $vcsDate = $gitInfo->getHeadCommitDate();
622 if ( $vcsDate !== false ) {
623 $ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $vcsDate );
624 }
625 }
626
627 if ( SpecialVersion::getExtLicenseFileName( $extensionPath ) ) {
628 $ret['license-name'] = $ext['license-name'] ?? '';
629 $ret['license'] = SpecialPage::getTitleFor(
630 'Version',
631 "License/{$ext['name']}"
632 )->getLinkURL();
633 }
634
635 if ( SpecialVersion::getExtAuthorsFileName( $extensionPath ) ) {
636 $ret['credits'] = SpecialPage::getTitleFor(
637 'Version',
638 "Credits/{$ext['name']}"
639 )->getLinkURL();
640 }
641 }
642 $data[] = $ret;
643 }
644 }
645
646 ApiResult::setIndexedTagName( $data, 'ext' );
647
648 return $this->getResult()->addValue( 'query', $property, $data );
649 }
650
651 protected function appendRightsInfo( $property ) {
652 $config = $this->getConfig();
653 $rightsPage = $config->get( 'RightsPage' );
654 if ( is_string( $rightsPage ) ) {
655 $title = Title::newFromText( $rightsPage );
656 $url = wfExpandUrl( $title, PROTO_CURRENT );
657 } else {
658 $title = false;
659 $url = $config->get( 'RightsUrl' );
660 }
661 $text = $config->get( 'RightsText' );
662 if ( $title && !strlen( $text ) ) {
663 $text = $title->getPrefixedText();
664 }
665
666 $data = [
667 'url' => (string)$url,
668 'text' => (string)$text,
669 ];
670
671 return $this->getResult()->addValue( 'query', $property, $data );
672 }
673
674 protected function appendRestrictions( $property ) {
675 $config = $this->getConfig();
676 $data = [
677 'types' => $config->get( 'RestrictionTypes' ),
678 'levels' => $config->get( 'RestrictionLevels' ),
679 'cascadinglevels' => $config->get( 'CascadingRestrictionLevels' ),
680 'semiprotectedlevels' => $config->get( 'SemiprotectedRestrictionLevels' ),
681 ];
682
683 ApiResult::setArrayType( $data['types'], 'BCarray' );
684 ApiResult::setArrayType( $data['levels'], 'BCarray' );
685 ApiResult::setArrayType( $data['cascadinglevels'], 'BCarray' );
686 ApiResult::setArrayType( $data['semiprotectedlevels'], 'BCarray' );
687
688 ApiResult::setIndexedTagName( $data['types'], 'type' );
689 ApiResult::setIndexedTagName( $data['levels'], 'level' );
690 ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
691 ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
692
693 return $this->getResult()->addValue( 'query', $property, $data );
694 }
695
696 public function appendLanguages( $property ) {
697 $params = $this->extractRequestParams();
698 $langCode = $params['inlanguagecode'] ?? '';
699 $langNames = Language::fetchLanguageNames( $langCode );
700
701 $data = [];
702
703 foreach ( $langNames as $code => $name ) {
704 $lang = [
705 'code' => $code,
706 'bcp47' => LanguageCode::bcp47( $code ),
707 ];
708 ApiResult::setContentValue( $lang, 'name', $name );
709 $data[] = $lang;
710 }
711 ApiResult::setIndexedTagName( $data, 'lang' );
712
713 return $this->getResult()->addValue( 'query', $property, $data );
714 }
715
716 // Export information about which page languages will trigger
717 // language conversion. (T153341)
718 public function appendLanguageVariants( $property ) {
719 $langNames = LanguageConverter::$languagesWithVariants;
720 if ( $this->getConfig()->get( 'DisableLangConversion' ) ) {
721 // Ensure result is empty if language conversion is disabled.
722 $langNames = [];
723 }
724 sort( $langNames );
725
726 $data = [];
727 foreach ( $langNames as $langCode ) {
728 $lang = Language::factory( $langCode );
729 if ( $lang->getConverter() instanceof FakeConverter ) {
730 // Only languages which do not return instances of
731 // FakeConverter implement language conversion.
732 continue;
733 }
734 $data[$langCode] = [];
735 ApiResult::setIndexedTagName( $data[$langCode], 'variant' );
736 ApiResult::setArrayType( $data[$langCode], 'kvp', 'code' );
737
738 $variants = $lang->getVariants();
739 sort( $variants );
740 foreach ( $variants as $v ) {
741 $fallbacks = $lang->getConverter()->getVariantFallbacks( $v );
742 if ( !is_array( $fallbacks ) ) {
743 $fallbacks = [ $fallbacks ];
744 }
745 $data[$langCode][$v] = [
746 'fallbacks' => $fallbacks,
747 ];
748 ApiResult::setIndexedTagName(
749 $data[$langCode][$v]['fallbacks'], 'variant'
750 );
751 }
752 }
753 ApiResult::setIndexedTagName( $data, 'lang' );
754 ApiResult::setArrayType( $data, 'kvp', 'code' );
755
756 return $this->getResult()->addValue( 'query', $property, $data );
757 }
758
759 public function appendSkins( $property ) {
760 $data = [];
761 $allowed = Skin::getAllowedSkins();
762 $default = Skin::normalizeKey( 'default' );
763 foreach ( Skin::getSkinNames() as $name => $displayName ) {
764 $msg = $this->msg( "skinname-{$name}" );
765 $code = $this->getParameter( 'inlanguagecode' );
766 if ( $code && Language::isValidCode( $code ) ) {
767 $msg->inLanguage( $code );
768 } else {
769 $msg->inContentLanguage();
770 }
771 if ( $msg->exists() ) {
772 $displayName = $msg->text();
773 }
774 $skin = [ 'code' => $name ];
775 ApiResult::setContentValue( $skin, 'name', $displayName );
776 if ( !isset( $allowed[$name] ) ) {
777 $skin['unusable'] = true;
778 }
779 if ( $name === $default ) {
780 $skin['default'] = true;
781 }
782 $data[] = $skin;
783 }
784 ApiResult::setIndexedTagName( $data, 'skin' );
785
786 return $this->getResult()->addValue( 'query', $property, $data );
787 }
788
789 public function appendExtensionTags( $property ) {
790 $tags = array_map(
791 function ( $item ) {
792 return "<$item>";
793 },
794 MediaWikiServices::getInstance()->getParser()->getTags()
795 );
796 ApiResult::setArrayType( $tags, 'BCarray' );
797 ApiResult::setIndexedTagName( $tags, 't' );
798
799 return $this->getResult()->addValue( 'query', $property, $tags );
800 }
801
802 public function appendFunctionHooks( $property ) {
803 $hooks = MediaWikiServices::getInstance()->getParser()->getFunctionHooks();
804 ApiResult::setArrayType( $hooks, 'BCarray' );
805 ApiResult::setIndexedTagName( $hooks, 'h' );
806
807 return $this->getResult()->addValue( 'query', $property, $hooks );
808 }
809
810 public function appendVariables( $property ) {
811 $variables = MediaWikiServices::getInstance()->getMagicWordFactory()->getVariableIDs();
812 ApiResult::setArrayType( $variables, 'BCarray' );
813 ApiResult::setIndexedTagName( $variables, 'v' );
814
815 return $this->getResult()->addValue( 'query', $property, $variables );
816 }
817
818 public function appendProtocols( $property ) {
819 // Make a copy of the global so we don't try to set the _element key of it - T47130
820 $protocols = array_values( $this->getConfig()->get( 'UrlProtocols' ) );
821 ApiResult::setArrayType( $protocols, 'BCarray' );
822 ApiResult::setIndexedTagName( $protocols, 'p' );
823
824 return $this->getResult()->addValue( 'query', $property, $protocols );
825 }
826
827 public function appendDefaultOptions( $property ) {
828 $options = User::getDefaultOptions();
829 $options[ApiResult::META_BC_BOOLS] = array_keys( $options );
830 return $this->getResult()->addValue( 'query', $property, $options );
831 }
832
833 public function appendUploadDialog( $property ) {
834 $config = $this->getConfig()->get( 'UploadDialog' );
835 return $this->getResult()->addValue( 'query', $property, $config );
836 }
837
838 public function appendSubscribedHooks( $property ) {
839 $hooks = $this->getConfig()->get( 'Hooks' );
840 $myWgHooks = $hooks;
841 ksort( $myWgHooks );
842
843 $data = [];
844 foreach ( $myWgHooks as $name => $subscribers ) {
845 $arr = [
846 'name' => $name,
847 'subscribers' => array_map( [ SpecialVersion::class, 'arrayToString' ], $subscribers ),
848 ];
849
850 ApiResult::setArrayType( $arr['subscribers'], 'array' );
851 ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
852 $data[] = $arr;
853 }
854
855 ApiResult::setIndexedTagName( $data, 'hook' );
856
857 return $this->getResult()->addValue( 'query', $property, $data );
858 }
859
860 public function getCacheMode( $params ) {
861 // Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
862 if (
863 count( $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' ) ) &&
864 !is_null( $params['prop'] ) &&
865 in_array( 'interwikimap', $params['prop'] )
866 ) {
867 return 'anon-public-user-private';
868 }
869
870 return 'public';
871 }
872
873 public function getAllowedParams() {
874 return [
875 'prop' => [
876 ApiBase::PARAM_DFLT => 'general',
877 ApiBase::PARAM_ISMULTI => true,
878 ApiBase::PARAM_TYPE => [
879 'general',
880 'namespaces',
881 'namespacealiases',
882 'specialpagealiases',
883 'magicwords',
884 'interwikimap',
885 'dbrepllag',
886 'statistics',
887 'usergroups',
888 'libraries',
889 'extensions',
890 'fileextensions',
891 'rightsinfo',
892 'restrictions',
893 'languages',
894 'languagevariants',
895 'skins',
896 'extensiontags',
897 'functionhooks',
898 'showhooks',
899 'variables',
900 'protocols',
901 'defaultoptions',
902 'uploaddialog',
903 ],
904 ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
905 ],
906 'filteriw' => [
907 ApiBase::PARAM_TYPE => [
908 'local',
909 '!local',
910 ]
911 ],
912 'showalldb' => false,
913 'numberingroup' => false,
914 'inlanguagecode' => null,
915 ];
916 }
917
918 protected function getExamplesMessages() {
919 return [
920 'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
921 => 'apihelp-query+siteinfo-example-simple',
922 'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
923 => 'apihelp-query+siteinfo-example-interwiki',
924 'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
925 => 'apihelp-query+siteinfo-example-replag',
926 ];
927 }
928
929 public function getHelpUrls() {
930 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Siteinfo';
931 }
932 }