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