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