]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/api/ApiQuerySiteinfo.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.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                 Hooks::run( 'APIQuerySiteInfoGeneralInfo', [ $this, &$data ] );
287
288                 return $this->getResult()->addValue( 'query', $property, $data );
289         }
290
291         protected function appendNamespaces( $property ) {
292                 global $wgContLang;
293                 $data = [
294                         ApiResult::META_TYPE => 'assoc',
295                 ];
296                 foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
297                         $data[$ns] = [
298                                 'id' => intval( $ns ),
299                                 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
300                         ];
301                         ApiResult::setContentValue( $data[$ns], 'name', $title );
302                         $canonical = MWNamespace::getCanonicalName( $ns );
303
304                         $data[$ns]['subpages'] = MWNamespace::hasSubpages( $ns );
305
306                         if ( $canonical ) {
307                                 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
308                         }
309
310                         $data[$ns]['content'] = MWNamespace::isContent( $ns );
311                         $data[$ns]['nonincludable'] = MWNamespace::isNonincludable( $ns );
312
313                         $contentmodel = MWNamespace::getNamespaceContentModel( $ns );
314                         if ( $contentmodel ) {
315                                 $data[$ns]['defaultcontentmodel'] = $contentmodel;
316                         }
317                 }
318
319                 ApiResult::setArrayType( $data, 'assoc' );
320                 ApiResult::setIndexedTagName( $data, 'ns' );
321
322                 return $this->getResult()->addValue( 'query', $property, $data );
323         }
324
325         protected function appendNamespaceAliases( $property ) {
326                 global $wgContLang;
327                 $aliases = array_merge( $this->getConfig()->get( 'NamespaceAliases' ),
328                         $wgContLang->getNamespaceAliases() );
329                 $namespaces = $wgContLang->getNamespaces();
330                 $data = [];
331                 foreach ( $aliases as $title => $ns ) {
332                         if ( $namespaces[$ns] == $title ) {
333                                 // Don't list duplicates
334                                 continue;
335                         }
336                         $item = [
337                                 'id' => intval( $ns )
338                         ];
339                         ApiResult::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
340                         $data[] = $item;
341                 }
342
343                 sort( $data );
344
345                 ApiResult::setIndexedTagName( $data, 'ns' );
346
347                 return $this->getResult()->addValue( 'query', $property, $data );
348         }
349
350         protected function appendSpecialPageAliases( $property ) {
351                 global $wgContLang;
352                 $data = [];
353                 $aliases = $wgContLang->getSpecialPageAliases();
354                 foreach ( SpecialPageFactory::getNames() as $specialpage ) {
355                         if ( isset( $aliases[$specialpage] ) ) {
356                                 $arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ];
357                                 ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
358                                 $data[] = $arr;
359                         }
360                 }
361                 ApiResult::setIndexedTagName( $data, 'specialpage' );
362
363                 return $this->getResult()->addValue( 'query', $property, $data );
364         }
365
366         protected function appendMagicWords( $property ) {
367                 global $wgContLang;
368                 $data = [];
369                 foreach ( $wgContLang->getMagicWords() as $magicword => $aliases ) {
370                         $caseSensitive = array_shift( $aliases );
371                         $arr = [ 'name' => $magicword, 'aliases' => $aliases ];
372                         $arr['case-sensitive'] = (bool)$caseSensitive;
373                         ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
374                         $data[] = $arr;
375                 }
376                 ApiResult::setIndexedTagName( $data, 'magicword' );
377
378                 return $this->getResult()->addValue( 'query', $property, $data );
379         }
380
381         protected function appendInterwikiMap( $property, $filter ) {
382                 $local = null;
383                 if ( $filter === 'local' ) {
384                         $local = 1;
385                 } elseif ( $filter === '!local' ) {
386                         $local = 0;
387                 } elseif ( $filter ) {
388                         ApiBase::dieDebug( __METHOD__, "Unknown filter=$filter" );
389                 }
390
391                 $params = $this->extractRequestParams();
392                 $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
393                 $langNames = Language::fetchLanguageNames( $langCode );
394
395                 $getPrefixes = MediaWikiServices::getInstance()->getInterwikiLookup()->getAllPrefixes( $local );
396                 $extraLangPrefixes = $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' );
397                 $localInterwikis = $this->getConfig()->get( 'LocalInterwikis' );
398                 $data = [];
399
400                 foreach ( $getPrefixes as $row ) {
401                         $prefix = $row['iw_prefix'];
402                         $val = [];
403                         $val['prefix'] = $prefix;
404                         if ( isset( $row['iw_local'] ) && $row['iw_local'] == '1' ) {
405                                 $val['local'] = true;
406                         }
407                         if ( isset( $row['iw_trans'] ) && $row['iw_trans'] == '1' ) {
408                                 $val['trans'] = true;
409                         }
410
411                         if ( isset( $langNames[$prefix] ) ) {
412                                 $val['language'] = $langNames[$prefix];
413                         }
414                         if ( in_array( $prefix, $localInterwikis ) ) {
415                                 $val['localinterwiki'] = true;
416                         }
417                         if ( in_array( $prefix, $extraLangPrefixes ) ) {
418                                 $val['extralanglink'] = true;
419
420                                 $linktext = wfMessage( "interlanguage-link-$prefix" );
421                                 if ( !$linktext->isDisabled() ) {
422                                         $val['linktext'] = $linktext->text();
423                                 }
424
425                                 $sitename = wfMessage( "interlanguage-link-sitename-$prefix" );
426                                 if ( !$sitename->isDisabled() ) {
427                                         $val['sitename'] = $sitename->text();
428                                 }
429                         }
430
431                         $val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
432                         $val['protorel'] = substr( $row['iw_url'], 0, 2 ) == '//';
433                         if ( isset( $row['iw_wikiid'] ) && $row['iw_wikiid'] !== '' ) {
434                                 $val['wikiid'] = $row['iw_wikiid'];
435                         }
436                         if ( isset( $row['iw_api'] ) && $row['iw_api'] !== '' ) {
437                                 $val['api'] = $row['iw_api'];
438                         }
439
440                         $data[] = $val;
441                 }
442
443                 ApiResult::setIndexedTagName( $data, 'iw' );
444
445                 return $this->getResult()->addValue( 'query', $property, $data );
446         }
447
448         protected function appendDbReplLagInfo( $property, $includeAll ) {
449                 $data = [];
450                 $lb = wfGetLB();
451                 $showHostnames = $this->getConfig()->get( 'ShowHostnames' );
452                 if ( $includeAll ) {
453                         if ( !$showHostnames ) {
454                                 $this->dieWithError( 'apierror-siteinfo-includealldenied', 'includeAllDenied' );
455                         }
456
457                         $lags = $lb->getLagTimes();
458                         foreach ( $lags as $i => $lag ) {
459                                 $data[] = [
460                                         'host' => $lb->getServerName( $i ),
461                                         'lag' => $lag
462                                 ];
463                         }
464                 } else {
465                         list( , $lag, $index ) = $lb->getMaxLag();
466                         $data[] = [
467                                 'host' => $showHostnames
468                                                 ? $lb->getServerName( $index )
469                                                 : '',
470                                 'lag' => intval( $lag )
471                         ];
472                 }
473
474                 ApiResult::setIndexedTagName( $data, 'db' );
475
476                 return $this->getResult()->addValue( 'query', $property, $data );
477         }
478
479         protected function appendStatistics( $property ) {
480                 $data = [];
481                 $data['pages'] = intval( SiteStats::pages() );
482                 $data['articles'] = intval( SiteStats::articles() );
483                 $data['edits'] = intval( SiteStats::edits() );
484                 $data['images'] = intval( SiteStats::images() );
485                 $data['users'] = intval( SiteStats::users() );
486                 $data['activeusers'] = intval( SiteStats::activeUsers() );
487                 $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
488                 $data['jobs'] = intval( SiteStats::jobs() );
489
490                 Hooks::run( 'APIQuerySiteInfoStatisticsInfo', [ &$data ] );
491
492                 return $this->getResult()->addValue( 'query', $property, $data );
493         }
494
495         protected function appendUserGroups( $property, $numberInGroup ) {
496                 $config = $this->getConfig();
497
498                 $data = [];
499                 $result = $this->getResult();
500                 $allGroups = array_values( User::getAllGroups() );
501                 foreach ( $config->get( 'GroupPermissions' ) as $group => $permissions ) {
502                         $arr = [
503                                 'name' => $group,
504                                 'rights' => array_keys( $permissions, true ),
505                         ];
506
507                         if ( $numberInGroup ) {
508                                 $autopromote = $config->get( 'Autopromote' );
509
510                                 if ( $group == 'user' ) {
511                                         $arr['number'] = SiteStats::users();
512                                 // '*' and autopromote groups have no size
513                                 } elseif ( $group !== '*' && !isset( $autopromote[$group] ) ) {
514                                         $arr['number'] = SiteStats::numberingroup( $group );
515                                 }
516                         }
517
518                         $groupArr = [
519                                 'add' => $config->get( 'AddGroups' ),
520                                 'remove' => $config->get( 'RemoveGroups' ),
521                                 'add-self' => $config->get( 'GroupsAddToSelf' ),
522                                 'remove-self' => $config->get( 'GroupsRemoveFromSelf' )
523                         ];
524
525                         foreach ( $groupArr as $type => $rights ) {
526                                 if ( isset( $rights[$group] ) ) {
527                                         if ( $rights[$group] === true ) {
528                                                 $groups = $allGroups;
529                                         } else {
530                                                 $groups = array_intersect( $rights[$group], $allGroups );
531                                         }
532                                         if ( $groups ) {
533                                                 $arr[$type] = $groups;
534                                                 ApiResult::setArrayType( $arr[$type], 'BCarray' );
535                                                 ApiResult::setIndexedTagName( $arr[$type], 'group' );
536                                         }
537                                 }
538                         }
539
540                         ApiResult::setIndexedTagName( $arr['rights'], 'permission' );
541                         $data[] = $arr;
542                 }
543
544                 ApiResult::setIndexedTagName( $data, 'group' );
545
546                 return $result->addValue( 'query', $property, $data );
547         }
548
549         protected function appendFileExtensions( $property ) {
550                 $data = [];
551                 foreach ( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) as $ext ) {
552                         $data[] = [ 'ext' => $ext ];
553                 }
554                 ApiResult::setIndexedTagName( $data, 'fe' );
555
556                 return $this->getResult()->addValue( 'query', $property, $data );
557         }
558
559         protected function appendInstalledLibraries( $property ) {
560                 global $IP;
561                 $path = "$IP/vendor/composer/installed.json";
562                 if ( !file_exists( $path ) ) {
563                         return true;
564                 }
565
566                 $data = [];
567                 $installed = new ComposerInstalled( $path );
568                 foreach ( $installed->getInstalledDependencies() as $name => $info ) {
569                         if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
570                                 // Skip any extensions or skins since they'll be listed
571                                 // in their proper section
572                                 continue;
573                         }
574                         $data[] = [
575                                 'name' => $name,
576                                 'version' => $info['version'],
577                         ];
578                 }
579                 ApiResult::setIndexedTagName( $data, 'library' );
580
581                 return $this->getResult()->addValue( 'query', $property, $data );
582         }
583
584         protected function appendExtensions( $property ) {
585                 $data = [];
586                 foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $type => $extensions ) {
587                         foreach ( $extensions as $ext ) {
588                                 $ret = [];
589                                 $ret['type'] = $type;
590                                 if ( isset( $ext['name'] ) ) {
591                                         $ret['name'] = $ext['name'];
592                                 }
593                                 if ( isset( $ext['namemsg'] ) ) {
594                                         $ret['namemsg'] = $ext['namemsg'];
595                                 }
596                                 if ( isset( $ext['description'] ) ) {
597                                         $ret['description'] = $ext['description'];
598                                 }
599                                 if ( isset( $ext['descriptionmsg'] ) ) {
600                                         // Can be a string or [ key, param1, param2, ... ]
601                                         if ( is_array( $ext['descriptionmsg'] ) ) {
602                                                 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
603                                                 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
604                                                 ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
605                                         } else {
606                                                 $ret['descriptionmsg'] = $ext['descriptionmsg'];
607                                         }
608                                 }
609                                 if ( isset( $ext['author'] ) ) {
610                                         $ret['author'] = is_array( $ext['author'] ) ?
611                                                 implode( ', ', $ext['author'] ) : $ext['author'];
612                                 }
613                                 if ( isset( $ext['url'] ) ) {
614                                         $ret['url'] = $ext['url'];
615                                 }
616                                 if ( isset( $ext['version'] ) ) {
617                                         $ret['version'] = $ext['version'];
618                                 }
619                                 if ( isset( $ext['path'] ) ) {
620                                         $extensionPath = dirname( $ext['path'] );
621                                         $gitInfo = new GitInfo( $extensionPath );
622                                         $vcsVersion = $gitInfo->getHeadSHA1();
623                                         if ( $vcsVersion !== false ) {
624                                                 $ret['vcs-system'] = 'git';
625                                                 $ret['vcs-version'] = $vcsVersion;
626                                                 $ret['vcs-url'] = $gitInfo->getHeadViewUrl();
627                                                 $vcsDate = $gitInfo->getHeadCommitDate();
628                                                 if ( $vcsDate !== false ) {
629                                                         $ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $vcsDate );
630                                                 }
631                                         }
632
633                                         if ( SpecialVersion::getExtLicenseFileName( $extensionPath ) ) {
634                                                 $ret['license-name'] = isset( $ext['license-name'] ) ? $ext['license-name'] : '';
635                                                 $ret['license'] = SpecialPage::getTitleFor(
636                                                         'Version',
637                                                         "License/{$ext['name']}"
638                                                 )->getLinkURL();
639                                         }
640
641                                         if ( SpecialVersion::getExtAuthorsFileName( $extensionPath ) ) {
642                                                 $ret['credits'] = SpecialPage::getTitleFor(
643                                                         'Version',
644                                                         "Credits/{$ext['name']}"
645                                                 )->getLinkURL();
646                                         }
647                                 }
648                                 $data[] = $ret;
649                         }
650                 }
651
652                 ApiResult::setIndexedTagName( $data, 'ext' );
653
654                 return $this->getResult()->addValue( 'query', $property, $data );
655         }
656
657         protected function appendRightsInfo( $property ) {
658                 $config = $this->getConfig();
659                 $rightsPage = $config->get( 'RightsPage' );
660                 if ( is_string( $rightsPage ) ) {
661                         $title = Title::newFromText( $rightsPage );
662                         $url = wfExpandUrl( $title, PROTO_CURRENT );
663                 } else {
664                         $title = false;
665                         $url = $config->get( 'RightsUrl' );
666                 }
667                 $text = $config->get( 'RightsText' );
668                 if ( !$text && $title ) {
669                         $text = $title->getPrefixedText();
670                 }
671
672                 $data = [
673                         'url' => $url ?: '',
674                         'text' => $text ?: ''
675                 ];
676
677                 return $this->getResult()->addValue( 'query', $property, $data );
678         }
679
680         protected function appendRestrictions( $property ) {
681                 $config = $this->getConfig();
682                 $data = [
683                         'types' => $config->get( 'RestrictionTypes' ),
684                         'levels' => $config->get( 'RestrictionLevels' ),
685                         'cascadinglevels' => $config->get( 'CascadingRestrictionLevels' ),
686                         'semiprotectedlevels' => $config->get( 'SemiprotectedRestrictionLevels' ),
687                 ];
688
689                 ApiResult::setArrayType( $data['types'], 'BCarray' );
690                 ApiResult::setArrayType( $data['levels'], 'BCarray' );
691                 ApiResult::setArrayType( $data['cascadinglevels'], 'BCarray' );
692                 ApiResult::setArrayType( $data['semiprotectedlevels'], 'BCarray' );
693
694                 ApiResult::setIndexedTagName( $data['types'], 'type' );
695                 ApiResult::setIndexedTagName( $data['levels'], 'level' );
696                 ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
697                 ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
698
699                 return $this->getResult()->addValue( 'query', $property, $data );
700         }
701
702         public function appendLanguages( $property ) {
703                 $params = $this->extractRequestParams();
704                 $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
705                 $langNames = Language::fetchLanguageNames( $langCode );
706
707                 $data = [];
708
709                 foreach ( $langNames as $code => $name ) {
710                         $lang = [ 'code' => $code ];
711                         ApiResult::setContentValue( $lang, 'name', $name );
712                         $data[] = $lang;
713                 }
714                 ApiResult::setIndexedTagName( $data, 'lang' );
715
716                 return $this->getResult()->addValue( 'query', $property, $data );
717         }
718
719         // Export information about which page languages will trigger
720         // language conversion. (T153341)
721         public function appendLanguageVariants( $property ) {
722                 $langNames = LanguageConverter::$languagesWithVariants;
723                 if ( $this->getConfig()->get( 'DisableLangConversion' ) ) {
724                         // Ensure result is empty if language conversion is disabled.
725                         $langNames = [];
726                 }
727                 sort( $langNames );
728
729                 $data = [];
730                 foreach ( $langNames as $langCode ) {
731                         $lang = Language::factory( $langCode );
732                         if ( $lang->getConverter() instanceof FakeConverter ) {
733                                 // Only languages which do not return instances of
734                                 // FakeConverter implement language conversion.
735                                 continue;
736                         }
737                         $data[$langCode] = [];
738                         ApiResult::setIndexedTagName( $data[$langCode], 'variant' );
739                         ApiResult::setArrayType( $data[$langCode], 'kvp', 'code' );
740
741                         $variants = $lang->getVariants();
742                         sort( $variants );
743                         foreach ( $variants as $v ) {
744                                 $fallbacks = $lang->getConverter()->getVariantFallbacks( $v );
745                                 if ( !is_array( $fallbacks ) ) {
746                                         $fallbacks = [ $fallbacks ];
747                                 }
748                                 $data[$langCode][$v] = [
749                                         'fallbacks' => $fallbacks,
750                                 ];
751                                 ApiResult::setIndexedTagName(
752                                         $data[$langCode][$v]['fallbacks'], 'variant'
753                                 );
754                         }
755                 }
756                 ApiResult::setIndexedTagName( $data, 'lang' );
757                 ApiResult::setArrayType( $data, 'kvp', 'code' );
758
759                 return $this->getResult()->addValue( 'query', $property, $data );
760         }
761
762         public function appendSkins( $property ) {
763                 $data = [];
764                 $allowed = Skin::getAllowedSkins();
765                 $default = Skin::normalizeKey( 'default' );
766                 foreach ( Skin::getSkinNames() as $name => $displayName ) {
767                         $msg = $this->msg( "skinname-{$name}" );
768                         $code = $this->getParameter( 'inlanguagecode' );
769                         if ( $code && Language::isValidCode( $code ) ) {
770                                 $msg->inLanguage( $code );
771                         } else {
772                                 $msg->inContentLanguage();
773                         }
774                         if ( $msg->exists() ) {
775                                 $displayName = $msg->text();
776                         }
777                         $skin = [ 'code' => $name ];
778                         ApiResult::setContentValue( $skin, 'name', $displayName );
779                         if ( !isset( $allowed[$name] ) ) {
780                                 $skin['unusable'] = true;
781                         }
782                         if ( $name === $default ) {
783                                 $skin['default'] = true;
784                         }
785                         $data[] = $skin;
786                 }
787                 ApiResult::setIndexedTagName( $data, 'skin' );
788
789                 return $this->getResult()->addValue( 'query', $property, $data );
790         }
791
792         public function appendExtensionTags( $property ) {
793                 global $wgParser;
794                 $wgParser->firstCallInit();
795                 $tags = array_map( [ $this, 'formatParserTags' ], $wgParser->getTags() );
796                 ApiResult::setArrayType( $tags, 'BCarray' );
797                 ApiResult::setIndexedTagName( $tags, 't' );
798
799                 return $this->getResult()->addValue( 'query', $property, $tags );
800         }
801
802         public function appendFunctionHooks( $property ) {
803                 global $wgParser;
804                 $wgParser->firstCallInit();
805                 $hooks = $wgParser->getFunctionHooks();
806                 ApiResult::setArrayType( $hooks, 'BCarray' );
807                 ApiResult::setIndexedTagName( $hooks, 'h' );
808
809                 return $this->getResult()->addValue( 'query', $property, $hooks );
810         }
811
812         public function appendVariables( $property ) {
813                 $variables = MagicWord::getVariableIDs();
814                 ApiResult::setArrayType( $variables, 'BCarray' );
815                 ApiResult::setIndexedTagName( $variables, 'v' );
816
817                 return $this->getResult()->addValue( 'query', $property, $variables );
818         }
819
820         public function appendProtocols( $property ) {
821                 // Make a copy of the global so we don't try to set the _element key of it - T47130
822                 $protocols = array_values( $this->getConfig()->get( 'UrlProtocols' ) );
823                 ApiResult::setArrayType( $protocols, 'BCarray' );
824                 ApiResult::setIndexedTagName( $protocols, 'p' );
825
826                 return $this->getResult()->addValue( 'query', $property, $protocols );
827         }
828
829         public function appendDefaultOptions( $property ) {
830                 $options = User::getDefaultOptions();
831                 $options[ApiResult::META_BC_BOOLS] = array_keys( $options );
832                 return $this->getResult()->addValue( 'query', $property, $options );
833         }
834
835         public function appendUploadDialog( $property ) {
836                 $config = $this->getConfig()->get( 'UploadDialog' );
837                 return $this->getResult()->addValue( 'query', $property, $config );
838         }
839
840         private function formatParserTags( $item ) {
841                 return "<{$item}>";
842         }
843
844         public function appendSubscribedHooks( $property ) {
845                 $hooks = $this->getConfig()->get( 'Hooks' );
846                 $myWgHooks = $hooks;
847                 ksort( $myWgHooks );
848
849                 $data = [];
850                 foreach ( $myWgHooks as $name => $subscribers ) {
851                         $arr = [
852                                 'name' => $name,
853                                 'subscribers' => array_map( [ 'SpecialVersion', 'arrayToString' ], $subscribers ),
854                         ];
855
856                         ApiResult::setArrayType( $arr['subscribers'], 'array' );
857                         ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
858                         $data[] = $arr;
859                 }
860
861                 ApiResult::setIndexedTagName( $data, 'hook' );
862
863                 return $this->getResult()->addValue( 'query', $property, $data );
864         }
865
866         public function getCacheMode( $params ) {
867                 // Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
868                 if (
869                         count( $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' ) ) &&
870                         !is_null( $params['prop'] ) &&
871                         in_array( 'interwikimap', $params['prop'] )
872                 ) {
873                         return 'anon-public-user-private';
874                 }
875
876                 return 'public';
877         }
878
879         public function getAllowedParams() {
880                 return [
881                         'prop' => [
882                                 ApiBase::PARAM_DFLT => 'general',
883                                 ApiBase::PARAM_ISMULTI => true,
884                                 ApiBase::PARAM_TYPE => [
885                                         'general',
886                                         'namespaces',
887                                         'namespacealiases',
888                                         'specialpagealiases',
889                                         'magicwords',
890                                         'interwikimap',
891                                         'dbrepllag',
892                                         'statistics',
893                                         'usergroups',
894                                         'libraries',
895                                         'extensions',
896                                         'fileextensions',
897                                         'rightsinfo',
898                                         'restrictions',
899                                         'languages',
900                                         'languagevariants',
901                                         'skins',
902                                         'extensiontags',
903                                         'functionhooks',
904                                         'showhooks',
905                                         'variables',
906                                         'protocols',
907                                         'defaultoptions',
908                                         'uploaddialog',
909                                 ],
910                                 ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
911                         ],
912                         'filteriw' => [
913                                 ApiBase::PARAM_TYPE => [
914                                         'local',
915                                         '!local',
916                                 ]
917                         ],
918                         'showalldb' => false,
919                         'numberingroup' => false,
920                         'inlanguagecode' => null,
921                 ];
922         }
923
924         protected function getExamplesMessages() {
925                 return [
926                         'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
927                                 => 'apihelp-query+siteinfo-example-simple',
928                         'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
929                                 => 'apihelp-query+siteinfo-example-interwiki',
930                         'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
931                                 => 'apihelp-query+siteinfo-example-replag',
932                 ];
933         }
934
935         public function getHelpUrls() {
936                 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Siteinfo';
937         }
938 }