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