c26bb625d2f0eb9fd5ae622eb8255fbf481d6c0c
[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'] = intval( $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'] = wfWikiID();
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' => intval( $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' => intval( $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 $aliases = MediaWikiServices::getInstance()->getContentLanguage()->getSpecialPageAliases();
345 foreach ( SpecialPageFactory::getNames() as $specialpage ) {
346 if ( isset( $aliases[$specialpage] ) ) {
347 $arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ];
348 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
349 $data[] = $arr;
350 }
351 }
352 ApiResult::setIndexedTagName( $data, 'specialpage' );
353
354 return $this->getResult()->addValue( 'query', $property, $data );
355 }
356
357 protected function appendMagicWords( $property ) {
358 $data = [];
359 foreach (
360 MediaWikiServices::getInstance()->getContentLanguage()->getMagicWords()
361 as $magicword => $aliases
362 ) {
363 $caseSensitive = array_shift( $aliases );
364 $arr = [ 'name' => $magicword, 'aliases' => $aliases ];
365 $arr['case-sensitive'] = (bool)$caseSensitive;
366 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
367 $data[] = $arr;
368 }
369 ApiResult::setIndexedTagName( $data, 'magicword' );
370
371 return $this->getResult()->addValue( 'query', $property, $data );
372 }
373
374 protected function appendInterwikiMap( $property, $filter ) {
375 if ( $filter === 'local' ) {
376 $local = 1;
377 } elseif ( $filter === '!local' ) {
378 $local = 0;
379 } else {
380 // $filter === null
381 $local = null;
382 }
383
384 $params = $this->extractRequestParams();
385 $langCode = $params['inlanguagecode'] ?? '';
386 $langNames = Language::fetchLanguageNames( $langCode );
387
388 $getPrefixes = MediaWikiServices::getInstance()->getInterwikiLookup()->getAllPrefixes( $local );
389 $extraLangPrefixes = $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' );
390 $localInterwikis = $this->getConfig()->get( 'LocalInterwikis' );
391 $data = [];
392
393 foreach ( $getPrefixes as $row ) {
394 $prefix = $row['iw_prefix'];
395 $val = [];
396 $val['prefix'] = $prefix;
397 if ( isset( $row['iw_local'] ) && $row['iw_local'] == '1' ) {
398 $val['local'] = true;
399 }
400 if ( isset( $row['iw_trans'] ) && $row['iw_trans'] == '1' ) {
401 $val['trans'] = true;
402 }
403
404 if ( isset( $langNames[$prefix] ) ) {
405 $val['language'] = $langNames[$prefix];
406 }
407 if ( in_array( $prefix, $localInterwikis ) ) {
408 $val['localinterwiki'] = true;
409 }
410 if ( in_array( $prefix, $extraLangPrefixes ) ) {
411 $val['extralanglink'] = true;
412
413 $linktext = wfMessage( "interlanguage-link-$prefix" );
414 if ( !$linktext->isDisabled() ) {
415 $val['linktext'] = $linktext->text();
416 }
417
418 $sitename = wfMessage( "interlanguage-link-sitename-$prefix" );
419 if ( !$sitename->isDisabled() ) {
420 $val['sitename'] = $sitename->text();
421 }
422 }
423
424 $val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
425 $val['protorel'] = substr( $row['iw_url'], 0, 2 ) == '//';
426 if ( isset( $row['iw_wikiid'] ) && $row['iw_wikiid'] !== '' ) {
427 $val['wikiid'] = $row['iw_wikiid'];
428 }
429 if ( isset( $row['iw_api'] ) && $row['iw_api'] !== '' ) {
430 $val['api'] = $row['iw_api'];
431 }
432
433 $data[] = $val;
434 }
435
436 ApiResult::setIndexedTagName( $data, 'iw' );
437
438 return $this->getResult()->addValue( 'query', $property, $data );
439 }
440
441 protected function appendDbReplLagInfo( $property, $includeAll ) {
442 $data = [];
443 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
444 $showHostnames = $this->getConfig()->get( 'ShowHostnames' );
445 if ( $includeAll ) {
446 if ( !$showHostnames ) {
447 $this->dieWithError( 'apierror-siteinfo-includealldenied', 'includeAllDenied' );
448 }
449
450 $lags = $lb->getLagTimes();
451 foreach ( $lags as $i => $lag ) {
452 $data[] = [
453 'host' => $lb->getServerName( $i ),
454 'lag' => $lag
455 ];
456 }
457 } else {
458 list( , $lag, $index ) = $lb->getMaxLag();
459 $data[] = [
460 'host' => $showHostnames
461 ? $lb->getServerName( $index )
462 : '',
463 'lag' => $lag
464 ];
465 }
466
467 ApiResult::setIndexedTagName( $data, 'db' );
468
469 return $this->getResult()->addValue( 'query', $property, $data );
470 }
471
472 protected function appendStatistics( $property ) {
473 $data = [];
474 $data['pages'] = intval( SiteStats::pages() );
475 $data['articles'] = intval( SiteStats::articles() );
476 $data['edits'] = intval( SiteStats::edits() );
477 $data['images'] = intval( SiteStats::images() );
478 $data['users'] = intval( SiteStats::users() );
479 $data['activeusers'] = intval( SiteStats::activeUsers() );
480 $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
481 $data['jobs'] = intval( SiteStats::jobs() );
482
483 Hooks::run( 'APIQuerySiteInfoStatisticsInfo', [ &$data ] );
484
485 return $this->getResult()->addValue( 'query', $property, $data );
486 }
487
488 protected function appendUserGroups( $property, $numberInGroup ) {
489 $config = $this->getConfig();
490
491 $data = [];
492 $result = $this->getResult();
493 $allGroups = array_values( User::getAllGroups() );
494 foreach ( $config->get( 'GroupPermissions' ) as $group => $permissions ) {
495 $arr = [
496 'name' => $group,
497 'rights' => array_keys( $permissions, true ),
498 ];
499
500 if ( $numberInGroup ) {
501 $autopromote = $config->get( 'Autopromote' );
502
503 if ( $group == 'user' ) {
504 $arr['number'] = SiteStats::users();
505 // '*' and autopromote groups have no size
506 } elseif ( $group !== '*' && !isset( $autopromote[$group] ) ) {
507 $arr['number'] = SiteStats::numberingroup( $group );
508 }
509 }
510
511 $groupArr = [
512 'add' => $config->get( 'AddGroups' ),
513 'remove' => $config->get( 'RemoveGroups' ),
514 'add-self' => $config->get( 'GroupsAddToSelf' ),
515 'remove-self' => $config->get( 'GroupsRemoveFromSelf' )
516 ];
517
518 foreach ( $groupArr as $type => $rights ) {
519 if ( isset( $rights[$group] ) ) {
520 if ( $rights[$group] === true ) {
521 $groups = $allGroups;
522 } else {
523 $groups = array_intersect( $rights[$group], $allGroups );
524 }
525 if ( $groups ) {
526 $arr[$type] = $groups;
527 ApiResult::setArrayType( $arr[$type], 'BCarray' );
528 ApiResult::setIndexedTagName( $arr[$type], 'group' );
529 }
530 }
531 }
532
533 ApiResult::setIndexedTagName( $arr['rights'], 'permission' );
534 $data[] = $arr;
535 }
536
537 ApiResult::setIndexedTagName( $data, 'group' );
538
539 return $result->addValue( 'query', $property, $data );
540 }
541
542 protected function appendFileExtensions( $property ) {
543 $data = [];
544 foreach ( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) as $ext ) {
545 $data[] = [ 'ext' => $ext ];
546 }
547 ApiResult::setIndexedTagName( $data, 'fe' );
548
549 return $this->getResult()->addValue( 'query', $property, $data );
550 }
551
552 protected function appendInstalledLibraries( $property ) {
553 global $IP;
554 $path = "$IP/vendor/composer/installed.json";
555 if ( !file_exists( $path ) ) {
556 return true;
557 }
558
559 $data = [];
560 $installed = new ComposerInstalled( $path );
561 foreach ( $installed->getInstalledDependencies() as $name => $info ) {
562 if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
563 // Skip any extensions or skins since they'll be listed
564 // in their proper section
565 continue;
566 }
567 $data[] = [
568 'name' => $name,
569 'version' => $info['version'],
570 ];
571 }
572 ApiResult::setIndexedTagName( $data, 'library' );
573
574 return $this->getResult()->addValue( 'query', $property, $data );
575 }
576
577 protected function appendExtensions( $property ) {
578 $data = [];
579 foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $type => $extensions ) {
580 foreach ( $extensions as $ext ) {
581 $ret = [];
582 $ret['type'] = $type;
583 if ( isset( $ext['name'] ) ) {
584 $ret['name'] = $ext['name'];
585 }
586 if ( isset( $ext['namemsg'] ) ) {
587 $ret['namemsg'] = $ext['namemsg'];
588 }
589 if ( isset( $ext['description'] ) ) {
590 $ret['description'] = $ext['description'];
591 }
592 if ( isset( $ext['descriptionmsg'] ) ) {
593 // Can be a string or [ key, param1, param2, ... ]
594 if ( is_array( $ext['descriptionmsg'] ) ) {
595 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
596 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
597 ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
598 } else {
599 $ret['descriptionmsg'] = $ext['descriptionmsg'];
600 }
601 }
602 if ( isset( $ext['author'] ) ) {
603 $ret['author'] = is_array( $ext['author'] ) ?
604 implode( ', ', $ext['author'] ) : $ext['author'];
605 }
606 if ( isset( $ext['url'] ) ) {
607 $ret['url'] = $ext['url'];
608 }
609 if ( isset( $ext['version'] ) ) {
610 $ret['version'] = $ext['version'];
611 }
612 if ( isset( $ext['path'] ) ) {
613 $extensionPath = dirname( $ext['path'] );
614 $gitInfo = new GitInfo( $extensionPath );
615 $vcsVersion = $gitInfo->getHeadSHA1();
616 if ( $vcsVersion !== false ) {
617 $ret['vcs-system'] = 'git';
618 $ret['vcs-version'] = $vcsVersion;
619 $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
620 $vcsDate = $gitInfo->getHeadCommitDate();
621 if ( $vcsDate !== false ) {
622 $ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $vcsDate );
623 }
624 }
625
626 if ( SpecialVersion::getExtLicenseFileName( $extensionPath ) ) {
627 $ret['license-name'] = $ext['license-name'] ?? '';
628 $ret['license'] = SpecialPage::getTitleFor(
629 'Version',
630 "License/{$ext['name']}"
631 )->getLinkURL();
632 }
633
634 if ( SpecialVersion::getExtAuthorsFileName( $extensionPath ) ) {
635 $ret['credits'] = SpecialPage::getTitleFor(
636 'Version',
637 "Credits/{$ext['name']}"
638 )->getLinkURL();
639 }
640 }
641 $data[] = $ret;
642 }
643 }
644
645 ApiResult::setIndexedTagName( $data, 'ext' );
646
647 return $this->getResult()->addValue( 'query', $property, $data );
648 }
649
650 protected function appendRightsInfo( $property ) {
651 $config = $this->getConfig();
652 $rightsPage = $config->get( 'RightsPage' );
653 if ( is_string( $rightsPage ) ) {
654 $title = Title::newFromText( $rightsPage );
655 $url = wfExpandUrl( $title, PROTO_CURRENT );
656 } else {
657 $title = false;
658 $url = $config->get( 'RightsUrl' );
659 }
660 $text = $config->get( 'RightsText' );
661 if ( $title && !strlen( $text ) ) {
662 $text = $title->getPrefixedText();
663 }
664
665 $data = [
666 'url' => strlen( $url ) ? $url : '',
667 'text' => strlen( $text ) ? $text : '',
668 ];
669
670 return $this->getResult()->addValue( 'query', $property, $data );
671 }
672
673 protected function appendRestrictions( $property ) {
674 $config = $this->getConfig();
675 $data = [
676 'types' => $config->get( 'RestrictionTypes' ),
677 'levels' => $config->get( 'RestrictionLevels' ),
678 'cascadinglevels' => $config->get( 'CascadingRestrictionLevels' ),
679 'semiprotectedlevels' => $config->get( 'SemiprotectedRestrictionLevels' ),
680 ];
681
682 ApiResult::setArrayType( $data['types'], 'BCarray' );
683 ApiResult::setArrayType( $data['levels'], 'BCarray' );
684 ApiResult::setArrayType( $data['cascadinglevels'], 'BCarray' );
685 ApiResult::setArrayType( $data['semiprotectedlevels'], 'BCarray' );
686
687 ApiResult::setIndexedTagName( $data['types'], 'type' );
688 ApiResult::setIndexedTagName( $data['levels'], 'level' );
689 ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
690 ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
691
692 return $this->getResult()->addValue( 'query', $property, $data );
693 }
694
695 public function appendLanguages( $property ) {
696 $params = $this->extractRequestParams();
697 $langCode = $params['inlanguagecode'] ?? '';
698 $langNames = Language::fetchLanguageNames( $langCode );
699
700 $data = [];
701
702 foreach ( $langNames as $code => $name ) {
703 $lang = [ 'code' => $code ];
704 ApiResult::setContentValue( $lang, 'name', $name );
705 $data[] = $lang;
706 }
707 ApiResult::setIndexedTagName( $data, 'lang' );
708
709 return $this->getResult()->addValue( 'query', $property, $data );
710 }
711
712 // Export information about which page languages will trigger
713 // language conversion. (T153341)
714 public function appendLanguageVariants( $property ) {
715 $langNames = LanguageConverter::$languagesWithVariants;
716 if ( $this->getConfig()->get( 'DisableLangConversion' ) ) {
717 // Ensure result is empty if language conversion is disabled.
718 $langNames = [];
719 }
720 sort( $langNames );
721
722 $data = [];
723 foreach ( $langNames as $langCode ) {
724 $lang = Language::factory( $langCode );
725 if ( $lang->getConverter() instanceof FakeConverter ) {
726 // Only languages which do not return instances of
727 // FakeConverter implement language conversion.
728 continue;
729 }
730 $data[$langCode] = [];
731 ApiResult::setIndexedTagName( $data[$langCode], 'variant' );
732 ApiResult::setArrayType( $data[$langCode], 'kvp', 'code' );
733
734 $variants = $lang->getVariants();
735 sort( $variants );
736 foreach ( $variants as $v ) {
737 $fallbacks = $lang->getConverter()->getVariantFallbacks( $v );
738 if ( !is_array( $fallbacks ) ) {
739 $fallbacks = [ $fallbacks ];
740 }
741 $data[$langCode][$v] = [
742 'fallbacks' => $fallbacks,
743 ];
744 ApiResult::setIndexedTagName(
745 $data[$langCode][$v]['fallbacks'], 'variant'
746 );
747 }
748 }
749 ApiResult::setIndexedTagName( $data, 'lang' );
750 ApiResult::setArrayType( $data, 'kvp', 'code' );
751
752 return $this->getResult()->addValue( 'query', $property, $data );
753 }
754
755 public function appendSkins( $property ) {
756 $data = [];
757 $allowed = Skin::getAllowedSkins();
758 $default = Skin::normalizeKey( 'default' );
759 foreach ( Skin::getSkinNames() as $name => $displayName ) {
760 $msg = $this->msg( "skinname-{$name}" );
761 $code = $this->getParameter( 'inlanguagecode' );
762 if ( $code && Language::isValidCode( $code ) ) {
763 $msg->inLanguage( $code );
764 } else {
765 $msg->inContentLanguage();
766 }
767 if ( $msg->exists() ) {
768 $displayName = $msg->text();
769 }
770 $skin = [ 'code' => $name ];
771 ApiResult::setContentValue( $skin, 'name', $displayName );
772 if ( !isset( $allowed[$name] ) ) {
773 $skin['unusable'] = true;
774 }
775 if ( $name === $default ) {
776 $skin['default'] = true;
777 }
778 $data[] = $skin;
779 }
780 ApiResult::setIndexedTagName( $data, 'skin' );
781
782 return $this->getResult()->addValue( 'query', $property, $data );
783 }
784
785 public function appendExtensionTags( $property ) {
786 global $wgParser;
787 $wgParser->firstCallInit();
788 $tags = array_map(
789 function ( $item ) {
790 return "<$item>";
791 },
792 $wgParser->getTags()
793 );
794 ApiResult::setArrayType( $tags, 'BCarray' );
795 ApiResult::setIndexedTagName( $tags, 't' );
796
797 return $this->getResult()->addValue( 'query', $property, $tags );
798 }
799
800 public function appendFunctionHooks( $property ) {
801 global $wgParser;
802 $wgParser->firstCallInit();
803 $hooks = $wgParser->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 }