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