Merge "Paranoia, escape image alignment parameters before outputting."
[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 $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'] = intval( SiteStats::pages() );
476 $data['articles'] = intval( SiteStats::articles() );
477 $data['edits'] = intval( SiteStats::edits() );
478 $data['images'] = intval( SiteStats::images() );
479 $data['users'] = intval( SiteStats::users() );
480 $data['activeusers'] = intval( SiteStats::activeUsers() );
481 $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
482 $data['jobs'] = intval( 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' => strlen( $url ) ? $url : '',
668 'text' => strlen( $text ) ? $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 = [ 'code' => $code ];
705 ApiResult::setContentValue( $lang, 'name', $name );
706 $data[] = $lang;
707 }
708 ApiResult::setIndexedTagName( $data, 'lang' );
709
710 return $this->getResult()->addValue( 'query', $property, $data );
711 }
712
713 // Export information about which page languages will trigger
714 // language conversion. (T153341)
715 public function appendLanguageVariants( $property ) {
716 $langNames = LanguageConverter::$languagesWithVariants;
717 if ( $this->getConfig()->get( 'DisableLangConversion' ) ) {
718 // Ensure result is empty if language conversion is disabled.
719 $langNames = [];
720 }
721 sort( $langNames );
722
723 $data = [];
724 foreach ( $langNames as $langCode ) {
725 $lang = Language::factory( $langCode );
726 if ( $lang->getConverter() instanceof FakeConverter ) {
727 // Only languages which do not return instances of
728 // FakeConverter implement language conversion.
729 continue;
730 }
731 $data[$langCode] = [];
732 ApiResult::setIndexedTagName( $data[$langCode], 'variant' );
733 ApiResult::setArrayType( $data[$langCode], 'kvp', 'code' );
734
735 $variants = $lang->getVariants();
736 sort( $variants );
737 foreach ( $variants as $v ) {
738 $fallbacks = $lang->getConverter()->getVariantFallbacks( $v );
739 if ( !is_array( $fallbacks ) ) {
740 $fallbacks = [ $fallbacks ];
741 }
742 $data[$langCode][$v] = [
743 'fallbacks' => $fallbacks,
744 ];
745 ApiResult::setIndexedTagName(
746 $data[$langCode][$v]['fallbacks'], 'variant'
747 );
748 }
749 }
750 ApiResult::setIndexedTagName( $data, 'lang' );
751 ApiResult::setArrayType( $data, 'kvp', 'code' );
752
753 return $this->getResult()->addValue( 'query', $property, $data );
754 }
755
756 public function appendSkins( $property ) {
757 $data = [];
758 $allowed = Skin::getAllowedSkins();
759 $default = Skin::normalizeKey( 'default' );
760 foreach ( Skin::getSkinNames() as $name => $displayName ) {
761 $msg = $this->msg( "skinname-{$name}" );
762 $code = $this->getParameter( 'inlanguagecode' );
763 if ( $code && Language::isValidCode( $code ) ) {
764 $msg->inLanguage( $code );
765 } else {
766 $msg->inContentLanguage();
767 }
768 if ( $msg->exists() ) {
769 $displayName = $msg->text();
770 }
771 $skin = [ 'code' => $name ];
772 ApiResult::setContentValue( $skin, 'name', $displayName );
773 if ( !isset( $allowed[$name] ) ) {
774 $skin['unusable'] = true;
775 }
776 if ( $name === $default ) {
777 $skin['default'] = true;
778 }
779 $data[] = $skin;
780 }
781 ApiResult::setIndexedTagName( $data, 'skin' );
782
783 return $this->getResult()->addValue( 'query', $property, $data );
784 }
785
786 public function appendExtensionTags( $property ) {
787 global $wgParser;
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 $hooks = $wgParser->getFunctionHooks();
803 ApiResult::setArrayType( $hooks, 'BCarray' );
804 ApiResult::setIndexedTagName( $hooks, 'h' );
805
806 return $this->getResult()->addValue( 'query', $property, $hooks );
807 }
808
809 public function appendVariables( $property ) {
810 $variables = MediaWikiServices::getInstance()->getMagicWordFactory()->getVariableIDs();
811 ApiResult::setArrayType( $variables, 'BCarray' );
812 ApiResult::setIndexedTagName( $variables, 'v' );
813
814 return $this->getResult()->addValue( 'query', $property, $variables );
815 }
816
817 public function appendProtocols( $property ) {
818 // Make a copy of the global so we don't try to set the _element key of it - T47130
819 $protocols = array_values( $this->getConfig()->get( 'UrlProtocols' ) );
820 ApiResult::setArrayType( $protocols, 'BCarray' );
821 ApiResult::setIndexedTagName( $protocols, 'p' );
822
823 return $this->getResult()->addValue( 'query', $property, $protocols );
824 }
825
826 public function appendDefaultOptions( $property ) {
827 $options = User::getDefaultOptions();
828 $options[ApiResult::META_BC_BOOLS] = array_keys( $options );
829 return $this->getResult()->addValue( 'query', $property, $options );
830 }
831
832 public function appendUploadDialog( $property ) {
833 $config = $this->getConfig()->get( 'UploadDialog' );
834 return $this->getResult()->addValue( 'query', $property, $config );
835 }
836
837 public function appendSubscribedHooks( $property ) {
838 $hooks = $this->getConfig()->get( 'Hooks' );
839 $myWgHooks = $hooks;
840 ksort( $myWgHooks );
841
842 $data = [];
843 foreach ( $myWgHooks as $name => $subscribers ) {
844 $arr = [
845 'name' => $name,
846 'subscribers' => array_map( [ SpecialVersion::class, 'arrayToString' ], $subscribers ),
847 ];
848
849 ApiResult::setArrayType( $arr['subscribers'], 'array' );
850 ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
851 $data[] = $arr;
852 }
853
854 ApiResult::setIndexedTagName( $data, 'hook' );
855
856 return $this->getResult()->addValue( 'query', $property, $data );
857 }
858
859 public function getCacheMode( $params ) {
860 // Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
861 if (
862 count( $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' ) ) &&
863 !is_null( $params['prop'] ) &&
864 in_array( 'interwikimap', $params['prop'] )
865 ) {
866 return 'anon-public-user-private';
867 }
868
869 return 'public';
870 }
871
872 public function getAllowedParams() {
873 return [
874 'prop' => [
875 ApiBase::PARAM_DFLT => 'general',
876 ApiBase::PARAM_ISMULTI => true,
877 ApiBase::PARAM_TYPE => [
878 'general',
879 'namespaces',
880 'namespacealiases',
881 'specialpagealiases',
882 'magicwords',
883 'interwikimap',
884 'dbrepllag',
885 'statistics',
886 'usergroups',
887 'libraries',
888 'extensions',
889 'fileextensions',
890 'rightsinfo',
891 'restrictions',
892 'languages',
893 'languagevariants',
894 'skins',
895 'extensiontags',
896 'functionhooks',
897 'showhooks',
898 'variables',
899 'protocols',
900 'defaultoptions',
901 'uploaddialog',
902 ],
903 ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
904 ],
905 'filteriw' => [
906 ApiBase::PARAM_TYPE => [
907 'local',
908 '!local',
909 ]
910 ],
911 'showalldb' => false,
912 'numberingroup' => false,
913 'inlanguagecode' => null,
914 ];
915 }
916
917 protected function getExamplesMessages() {
918 return [
919 'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
920 => 'apihelp-query+siteinfo-example-simple',
921 'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
922 => 'apihelp-query+siteinfo-example-interwiki',
923 'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
924 => 'apihelp-query+siteinfo-example-replag',
925 ];
926 }
927
928 public function getHelpUrls() {
929 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Siteinfo';
930 }
931 }