Completed
Branch master (939199)
by
unknown
39:35
created

includes/api/ApiQuerySiteinfo.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
27
/**
28
 * A query action to return meta information about the wiki site.
29
 *
30
 * @ingroup API
31
 */
32
class ApiQuerySiteinfo extends ApiQueryBase {
33
34
	public function __construct( ApiQuery $query, $moduleName ) {
35
		parent::__construct( $query, $moduleName, 'si' );
36
	}
37
38
	public function execute() {
39
		$params = $this->extractRequestParams();
40
		$done = [];
41
		$fit = false;
42
		foreach ( $params['prop'] as $p ) {
43
			switch ( $p ) {
44
				case 'general':
45
					$fit = $this->appendGeneralInfo( $p );
46
					break;
47
				case 'namespaces':
48
					$fit = $this->appendNamespaces( $p );
49
					break;
50
				case 'namespacealiases':
51
					$fit = $this->appendNamespaceAliases( $p );
52
					break;
53
				case 'specialpagealiases':
54
					$fit = $this->appendSpecialPageAliases( $p );
55
					break;
56
				case 'magicwords':
57
					$fit = $this->appendMagicWords( $p );
58
					break;
59
				case 'interwikimap':
60
					$filteriw = isset( $params['filteriw'] ) ? $params['filteriw'] : false;
61
					$fit = $this->appendInterwikiMap( $p, $filteriw );
62
					break;
63
				case 'dbrepllag':
64
					$fit = $this->appendDbReplLagInfo( $p, $params['showalldb'] );
65
					break;
66
				case 'statistics':
67
					$fit = $this->appendStatistics( $p );
68
					break;
69
				case 'usergroups':
70
					$fit = $this->appendUserGroups( $p, $params['numberingroup'] );
71
					break;
72
				case 'libraries':
73
					$fit = $this->appendInstalledLibraries( $p );
74
					break;
75
				case 'extensions':
76
					$fit = $this->appendExtensions( $p );
77
					break;
78
				case 'fileextensions':
79
					$fit = $this->appendFileExtensions( $p );
80
					break;
81
				case 'rightsinfo':
82
					$fit = $this->appendRightsInfo( $p );
83
					break;
84
				case 'restrictions':
85
					$fit = $this->appendRestrictions( $p );
86
					break;
87
				case 'languages':
88
					$fit = $this->appendLanguages( $p );
89
					break;
90
				case 'skins':
91
					$fit = $this->appendSkins( $p );
92
					break;
93
				case 'extensiontags':
94
					$fit = $this->appendExtensionTags( $p );
95
					break;
96
				case 'functionhooks':
97
					$fit = $this->appendFunctionHooks( $p );
98
					break;
99
				case 'showhooks':
100
					$fit = $this->appendSubscribedHooks( $p );
101
					break;
102
				case 'variables':
103
					$fit = $this->appendVariables( $p );
104
					break;
105
				case 'protocols':
106
					$fit = $this->appendProtocols( $p );
107
					break;
108
				case 'defaultoptions':
109
					$fit = $this->appendDefaultOptions( $p );
110
					break;
111
				case 'uploaddialog':
112
					$fit = $this->appendUploadDialog( $p );
113
					break;
114
				default:
115
					ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
116
			}
117
			if ( !$fit ) {
118
				// Abuse siprop as a query-continue parameter
119
				// and set it to all unprocessed props
120
				$this->setContinueEnumParameter( 'prop', implode( '|',
121
					array_diff( $params['prop'], $done ) ) );
122
				break;
123
			}
124
			$done[] = $p;
125
		}
126
	}
127
128
	protected function appendGeneralInfo( $property ) {
129
		global $wgContLang;
130
131
		$config = $this->getConfig();
132
133
		$data = [];
134
		$mainPage = Title::newMainPage();
135
		$data['mainpage'] = $mainPage->getPrefixedText();
136
		$data['base'] = wfExpandUrl( $mainPage->getFullURL(), PROTO_CURRENT );
137
		$data['sitename'] = $config->get( 'Sitename' );
138
139
		// wgLogo can either be a relative or an absolute path
140
		// make sure we always return an absolute path
141
		$data['logo'] = wfExpandUrl( $config->get( 'Logo' ), PROTO_RELATIVE );
142
143
		$data['generator'] = "MediaWiki {$config->get( 'Version' )}";
144
145
		$data['phpversion'] = PHP_VERSION;
146
		$data['phpsapi'] = PHP_SAPI;
147
		if ( defined( 'HHVM_VERSION' ) ) {
148
			$data['hhvmversion'] = HHVM_VERSION;
149
		}
150
		$data['dbtype'] = $config->get( 'DBtype' );
151
		$data['dbversion'] = $this->getDB()->getServerVersion();
152
153
		$allowFrom = [ '' ];
154
		$allowException = true;
155
		if ( !$config->get( 'AllowExternalImages' ) ) {
156
			$data['imagewhitelistenabled'] = (bool)$config->get( 'EnableImageWhitelist' );
157
			$allowFrom = $config->get( 'AllowExternalImagesFrom' );
158
			$allowException = !empty( $allowFrom );
159
		}
160
		if ( $allowException ) {
161
			$data['externalimages'] = (array)$allowFrom;
162
			ApiResult::setIndexedTagName( $data['externalimages'], 'prefix' );
163
		}
164
165
		$data['langconversion'] = !$config->get( 'DisableLangConversion' );
166
		$data['titleconversion'] = !$config->get( 'DisableTitleConversion' );
167
168
		if ( $wgContLang->linkPrefixExtension() ) {
169
			$linkPrefixCharset = $wgContLang->linkPrefixCharset();
170
			$data['linkprefixcharset'] = $linkPrefixCharset;
171
			// For backwards compatibility
172
			$data['linkprefix'] = "/^((?>.*[^$linkPrefixCharset]|))(.+)$/sDu";
173
		} else {
174
			$data['linkprefixcharset'] = '';
175
			$data['linkprefix'] = '';
176
		}
177
178
		$linktrail = $wgContLang->linkTrail();
179
		$data['linktrail'] = $linktrail ?: '';
180
181
		$data['legaltitlechars'] = Title::legalChars();
182
		$data['invalidusernamechars'] = $config->get( 'InvalidUsernameCharacters' );
183
184
		$data['allunicodefixes'] = (bool)$config->get( 'AllUnicodeFixes' );
185
		$data['fixarabicunicode'] = (bool)$config->get( 'FixArabicUnicode' );
186
		$data['fixmalayalamunicode'] = (bool)$config->get( 'FixMalayalamUnicode' );
187
188
		global $IP;
189
		$git = SpecialVersion::getGitHeadSha1( $IP );
190
		if ( $git ) {
191
			$data['git-hash'] = $git;
192
			$data['git-branch'] =
193
				SpecialVersion::getGitCurrentBranch( $GLOBALS['IP'] );
194
		}
195
196
		// 'case-insensitive' option is reserved for future
197
		$data['case'] = $config->get( 'CapitalLinks' ) ? 'first-letter' : 'case-sensitive';
198
		$data['lang'] = $config->get( 'LanguageCode' );
199
200
		$fallbacks = [];
201
		foreach ( $wgContLang->getFallbackLanguages() as $code ) {
202
			$fallbacks[] = [ 'code' => $code ];
203
		}
204
		$data['fallback'] = $fallbacks;
205
		ApiResult::setIndexedTagName( $data['fallback'], 'lang' );
206
207
		if ( $wgContLang->hasVariants() ) {
208
			$variants = [];
209
			foreach ( $wgContLang->getVariants() as $code ) {
210
				$variants[] = [
211
					'code' => $code,
212
					'name' => $wgContLang->getVariantname( $code ),
213
				];
214
			}
215
			$data['variants'] = $variants;
216
			ApiResult::setIndexedTagName( $data['variants'], 'lang' );
217
		}
218
219
		$data['rtl'] = $wgContLang->isRTL();
220
		$data['fallback8bitEncoding'] = $wgContLang->fallback8bitEncoding();
221
222
		$data['readonly'] = wfReadOnly();
223
		if ( $data['readonly'] ) {
224
			$data['readonlyreason'] = wfReadOnlyReason();
225
		}
226
		$data['writeapi'] = (bool)$config->get( 'EnableWriteAPI' );
227
228
		$data['maxarticlesize'] = $config->get( 'MaxArticleSize' ) * 1024;
229
230
		$tz = $config->get( 'Localtimezone' );
231
		$offset = $config->get( 'LocalTZoffset' );
232
		if ( is_null( $tz ) ) {
233
			$tz = 'UTC';
234
			$offset = 0;
235
		} elseif ( is_null( $offset ) ) {
236
			$offset = 0;
237
		}
238
		$data['timezone'] = $tz;
239
		$data['timeoffset'] = intval( $offset );
240
		$data['articlepath'] = $config->get( 'ArticlePath' );
241
		$data['scriptpath'] = $config->get( 'ScriptPath' );
242
		$data['script'] = $config->get( 'Script' );
243
		$data['variantarticlepath'] = $config->get( 'VariantArticlePath' );
244
		$data[ApiResult::META_BC_BOOLS][] = 'variantarticlepath';
245
		$data['server'] = $config->get( 'Server' );
246
		$data['servername'] = $config->get( 'ServerName' );
247
		$data['wikiid'] = wfWikiID();
248
		$data['time'] = wfTimestamp( TS_ISO_8601, time() );
249
250
		$data['misermode'] = (bool)$config->get( 'MiserMode' );
251
252
		$data['uploadsenabled'] = UploadBase::isEnabled();
253
		$data['maxuploadsize'] = UploadBase::getMaxUploadSize();
254
		$data['minuploadchunksize'] = (int)$config->get( 'MinUploadChunkSize' );
255
256
		$data['thumblimits'] = $config->get( 'ThumbLimits' );
257
		ApiResult::setArrayType( $data['thumblimits'], 'BCassoc' );
258
		ApiResult::setIndexedTagName( $data['thumblimits'], 'limit' );
259
		$data['imagelimits'] = [];
260
		ApiResult::setArrayType( $data['imagelimits'], 'BCassoc' );
261
		ApiResult::setIndexedTagName( $data['imagelimits'], 'limit' );
262
		foreach ( $config->get( 'ImageLimits' ) as $k => $limit ) {
263
			$data['imagelimits'][$k] = [ 'width' => $limit[0], 'height' => $limit[1] ];
264
		}
265
266
		$favicon = $config->get( 'Favicon' );
267
		if ( !empty( $favicon ) ) {
268
			// wgFavicon can either be a relative or an absolute path
269
			// make sure we always return an absolute path
270
			$data['favicon'] = wfExpandUrl( $favicon, PROTO_RELATIVE );
271
		}
272
273
		$data['centralidlookupprovider'] = $config->get( 'CentralIdLookupProvider' );
274
		$providerIds = array_keys( $config->get( 'CentralIdLookupProviders' ) );
275
		$data['allcentralidlookupproviders'] = $providerIds;
276
277
		$data['interwikimagic'] = (bool)$config->get( 'InterwikiMagic' );
278
		$data['magiclinks'] = $config->get( 'EnableMagicLinks' );
279
280
		Hooks::run( 'APIQuerySiteInfoGeneralInfo', [ $this, &$data ] );
281
282
		return $this->getResult()->addValue( 'query', $property, $data );
283
	}
284
285
	protected function appendNamespaces( $property ) {
286
		global $wgContLang;
287
		$data = [
288
			ApiResult::META_TYPE => 'assoc',
289
		];
290
		foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
291
			$data[$ns] = [
292
				'id' => intval( $ns ),
293
				'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
294
			];
295
			ApiResult::setContentValue( $data[$ns], 'name', $title );
296
			$canonical = MWNamespace::getCanonicalName( $ns );
297
298
			$data[$ns]['subpages'] = MWNamespace::hasSubpages( $ns );
299
300
			if ( $canonical ) {
301
				$data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
302
			}
303
304
			$data[$ns]['content'] = MWNamespace::isContent( $ns );
305
			$data[$ns]['nonincludable'] = MWNamespace::isNonincludable( $ns );
306
307
			$contentmodel = MWNamespace::getNamespaceContentModel( $ns );
308
			if ( $contentmodel ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $contentmodel of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
309
				$data[$ns]['defaultcontentmodel'] = $contentmodel;
310
			}
311
		}
312
313
		ApiResult::setArrayType( $data, 'assoc' );
314
		ApiResult::setIndexedTagName( $data, 'ns' );
315
316
		return $this->getResult()->addValue( 'query', $property, $data );
317
	}
318
319
	protected function appendNamespaceAliases( $property ) {
320
		global $wgContLang;
321
		$aliases = array_merge( $this->getConfig()->get( 'NamespaceAliases' ),
322
			$wgContLang->getNamespaceAliases() );
323
		$namespaces = $wgContLang->getNamespaces();
324
		$data = [];
325
		foreach ( $aliases as $title => $ns ) {
326
			if ( $namespaces[$ns] == $title ) {
327
				// Don't list duplicates
328
				continue;
329
			}
330
			$item = [
331
				'id' => intval( $ns )
332
			];
333
			ApiResult::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
334
			$data[] = $item;
335
		}
336
337
		sort( $data );
338
339
		ApiResult::setIndexedTagName( $data, 'ns' );
340
341
		return $this->getResult()->addValue( 'query', $property, $data );
342
	}
343
344 View Code Duplication
	protected function appendSpecialPageAliases( $property ) {
345
		global $wgContLang;
346
		$data = [];
347
		$aliases = $wgContLang->getSpecialPageAliases();
348
		foreach ( SpecialPageFactory::getNames() as $specialpage ) {
349
			if ( isset( $aliases[$specialpage] ) ) {
350
				$arr = [ 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ];
351
				ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
352
				$data[] = $arr;
353
			}
354
		}
355
		ApiResult::setIndexedTagName( $data, 'specialpage' );
356
357
		return $this->getResult()->addValue( 'query', $property, $data );
358
	}
359
360 View Code Duplication
	protected function appendMagicWords( $property ) {
361
		global $wgContLang;
362
		$data = [];
363
		foreach ( $wgContLang->getMagicWords() as $magicword => $aliases ) {
364
			$caseSensitive = array_shift( $aliases );
365
			$arr = [ 'name' => $magicword, 'aliases' => $aliases ];
366
			$arr['case-sensitive'] = (bool)$caseSensitive;
367
			ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
368
			$data[] = $arr;
369
		}
370
		ApiResult::setIndexedTagName( $data, 'magicword' );
371
372
		return $this->getResult()->addValue( 'query', $property, $data );
373
	}
374
375
	protected function appendInterwikiMap( $property, $filter ) {
376
		$local = null;
377
		if ( $filter === 'local' ) {
378
			$local = 1;
379
		} elseif ( $filter === '!local' ) {
380
			$local = 0;
381
		} elseif ( $filter ) {
382
			ApiBase::dieDebug( __METHOD__, "Unknown filter=$filter" );
383
		}
384
385
		$params = $this->extractRequestParams();
386
		$langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
387
		$langNames = Language::fetchLanguageNames( $langCode );
388
389
		$getPrefixes = Interwiki::getAllPrefixes( $local );
390
		$extraLangPrefixes = $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' );
391
		$localInterwikis = $this->getConfig()->get( 'LocalInterwikis' );
392
		$data = [];
393
394
		foreach ( $getPrefixes as $row ) {
395
			$prefix = $row['iw_prefix'];
396
			$val = [];
397
			$val['prefix'] = $prefix;
398
			if ( isset( $row['iw_local'] ) && $row['iw_local'] == '1' ) {
399
				$val['local'] = true;
400
			}
401
			if ( isset( $row['iw_trans'] ) && $row['iw_trans'] == '1' ) {
402
				$val['trans'] = true;
403
			}
404
405
			if ( isset( $langNames[$prefix] ) ) {
406
				$val['language'] = $langNames[$prefix];
407
			}
408
			if ( in_array( $prefix, $localInterwikis ) ) {
409
				$val['localinterwiki'] = true;
410
			}
411
			if ( in_array( $prefix, $extraLangPrefixes ) ) {
412
				$val['extralanglink'] = true;
413
414
				$linktext = wfMessage( "interlanguage-link-$prefix" );
415
				if ( !$linktext->isDisabled() ) {
416
					$val['linktext'] = $linktext->text();
417
				}
418
419
				$sitename = wfMessage( "interlanguage-link-sitename-$prefix" );
420
				if ( !$sitename->isDisabled() ) {
421
					$val['sitename'] = $sitename->text();
422
				}
423
			}
424
425
			$val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
426
			$val['protorel'] = substr( $row['iw_url'], 0, 2 ) == '//';
427
			if ( isset( $row['iw_wikiid'] ) && $row['iw_wikiid'] !== '' ) {
428
				$val['wikiid'] = $row['iw_wikiid'];
429
			}
430
			if ( isset( $row['iw_api'] ) && $row['iw_api'] !== '' ) {
431
				$val['api'] = $row['iw_api'];
432
			}
433
434
			$data[] = $val;
435
		}
436
437
		ApiResult::setIndexedTagName( $data, 'iw' );
438
439
		return $this->getResult()->addValue( 'query', $property, $data );
440
	}
441
442
	protected function appendDbReplLagInfo( $property, $includeAll ) {
443
		$data = [];
444
		$lb = wfGetLB();
445
		$showHostnames = $this->getConfig()->get( 'ShowHostnames' );
446
		if ( $includeAll ) {
447
			if ( !$showHostnames ) {
448
				$this->dieUsage(
449
					'Cannot view all servers info unless $wgShowHostnames is true',
450
					'includeAllDenied'
451
				);
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
582
	protected function appendExtensions( $property ) {
583
		$data = [];
584
		foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $type => $extensions ) {
585
			foreach ( $extensions as $ext ) {
586
				$ret = [];
587
				$ret['type'] = $type;
588
				if ( isset( $ext['name'] ) ) {
589
					$ret['name'] = $ext['name'];
590
				}
591
				if ( isset( $ext['namemsg'] ) ) {
592
					$ret['namemsg'] = $ext['namemsg'];
593
				}
594
				if ( isset( $ext['description'] ) ) {
595
					$ret['description'] = $ext['description'];
596
				}
597
				if ( isset( $ext['descriptionmsg'] ) ) {
598
					// Can be a string or [ key, param1, param2, ... ]
599
					if ( is_array( $ext['descriptionmsg'] ) ) {
600
						$ret['descriptionmsg'] = $ext['descriptionmsg'][0];
601
						$ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
602
						ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
603
					} else {
604
						$ret['descriptionmsg'] = $ext['descriptionmsg'];
605
					}
606
				}
607
				if ( isset( $ext['author'] ) ) {
608
					$ret['author'] = is_array( $ext['author'] ) ?
609
						implode( ', ', $ext['author'] ) : $ext['author'];
610
				}
611
				if ( isset( $ext['url'] ) ) {
612
					$ret['url'] = $ext['url'];
613
				}
614
				if ( isset( $ext['version'] ) ) {
615
					$ret['version'] = $ext['version'];
616
				}
617
				if ( isset( $ext['path'] ) ) {
618
					$extensionPath = dirname( $ext['path'] );
619
					$gitInfo = new GitInfo( $extensionPath );
620
					$vcsVersion = $gitInfo->getHeadSHA1();
621
					if ( $vcsVersion !== false ) {
622
						$ret['vcs-system'] = 'git';
623
						$ret['vcs-version'] = $vcsVersion;
624
						$ret['vcs-url'] = $gitInfo->getHeadViewUrl();
625
						$vcsDate = $gitInfo->getHeadCommitDate();
626
						if ( $vcsDate !== false ) {
627
							$ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $vcsDate );
628
						}
629
					}
630
631
					if ( SpecialVersion::getExtLicenseFileName( $extensionPath ) ) {
632
						$ret['license-name'] = isset( $ext['license-name'] ) ? $ext['license-name'] : '';
633
						$ret['license'] = SpecialPage::getTitleFor(
634
							'Version',
635
							"License/{$ext['name']}"
636
						)->getLinkURL();
637
					}
638
639
					if ( SpecialVersion::getExtAuthorsFileName( $extensionPath ) ) {
640
						$ret['credits'] = SpecialPage::getTitleFor(
641
							'Version',
642
							"Credits/{$ext['name']}"
643
						)->getLinkURL();
644
					}
645
				}
646
				$data[] = $ret;
647
			}
648
		}
649
650
		ApiResult::setIndexedTagName( $data, 'ext' );
651
652
		return $this->getResult()->addValue( 'query', $property, $data );
653
	}
654
655
	protected function appendRightsInfo( $property ) {
656
		$config = $this->getConfig();
657
		$rightsPage = $config->get( 'RightsPage' );
658
		if ( is_string( $rightsPage ) ) {
659
			$title = Title::newFromText( $rightsPage );
660
			$url = wfExpandUrl( $title, PROTO_CURRENT );
661
		} else {
662
			$title = false;
663
			$url = $config->get( 'RightsUrl' );
664
		}
665
		$text = $config->get( 'RightsText' );
666
		if ( !$text && $title ) {
667
			$text = $title->getPrefixedText();
668
		}
669
670
		$data = [
671
			'url' => $url ?: '',
672
			'text' => $text ?: ''
673
		];
674
675
		return $this->getResult()->addValue( 'query', $property, $data );
676
	}
677
678
	protected function appendRestrictions( $property ) {
679
		$config = $this->getConfig();
680
		$data = [
681
			'types' => $config->get( 'RestrictionTypes' ),
682
			'levels' => $config->get( 'RestrictionLevels' ),
683
			'cascadinglevels' => $config->get( 'CascadingRestrictionLevels' ),
684
			'semiprotectedlevels' => $config->get( 'SemiprotectedRestrictionLevels' ),
685
		];
686
687
		ApiResult::setArrayType( $data['types'], 'BCarray' );
688
		ApiResult::setArrayType( $data['levels'], 'BCarray' );
689
		ApiResult::setArrayType( $data['cascadinglevels'], 'BCarray' );
690
		ApiResult::setArrayType( $data['semiprotectedlevels'], 'BCarray' );
691
692
		ApiResult::setIndexedTagName( $data['types'], 'type' );
693
		ApiResult::setIndexedTagName( $data['levels'], 'level' );
694
		ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
695
		ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
696
697
		return $this->getResult()->addValue( 'query', $property, $data );
698
	}
699
700
	public function appendLanguages( $property ) {
701
		$params = $this->extractRequestParams();
702
		$langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
703
		$langNames = Language::fetchLanguageNames( $langCode );
704
705
		$data = [];
706
707
		foreach ( $langNames as $code => $name ) {
708
			$lang = [ 'code' => $code ];
709
			ApiResult::setContentValue( $lang, 'name', $name );
710
			$data[] = $lang;
711
		}
712
		ApiResult::setIndexedTagName( $data, 'lang' );
713
714
		return $this->getResult()->addValue( 'query', $property, $data );
715
	}
716
717
	public function appendSkins( $property ) {
718
		$data = [];
719
		$allowed = Skin::getAllowedSkins();
720
		$default = Skin::normalizeKey( 'default' );
721
		foreach ( Skin::getSkinNames() as $name => $displayName ) {
722
			$msg = $this->msg( "skinname-{$name}" );
723
			$code = $this->getParameter( 'inlanguagecode' );
724
			if ( $code && Language::isValidCode( $code ) ) {
725
				$msg->inLanguage( $code );
726
			} else {
727
				$msg->inContentLanguage();
728
			}
729
			if ( $msg->exists() ) {
730
				$displayName = $msg->text();
731
			}
732
			$skin = [ 'code' => $name ];
733
			ApiResult::setContentValue( $skin, 'name', $displayName );
734
			if ( !isset( $allowed[$name] ) ) {
735
				$skin['unusable'] = true;
736
			}
737
			if ( $name === $default ) {
738
				$skin['default'] = true;
739
			}
740
			$data[] = $skin;
741
		}
742
		ApiResult::setIndexedTagName( $data, 'skin' );
743
744
		return $this->getResult()->addValue( 'query', $property, $data );
745
	}
746
747 View Code Duplication
	public function appendExtensionTags( $property ) {
748
		global $wgParser;
749
		$wgParser->firstCallInit();
750
		$tags = array_map( [ $this, 'formatParserTags' ], $wgParser->getTags() );
751
		ApiResult::setArrayType( $tags, 'BCarray' );
752
		ApiResult::setIndexedTagName( $tags, 't' );
753
754
		return $this->getResult()->addValue( 'query', $property, $tags );
755
	}
756
757 View Code Duplication
	public function appendFunctionHooks( $property ) {
758
		global $wgParser;
759
		$wgParser->firstCallInit();
760
		$hooks = $wgParser->getFunctionHooks();
761
		ApiResult::setArrayType( $hooks, 'BCarray' );
762
		ApiResult::setIndexedTagName( $hooks, 'h' );
763
764
		return $this->getResult()->addValue( 'query', $property, $hooks );
765
	}
766
767
	public function appendVariables( $property ) {
768
		$variables = MagicWord::getVariableIDs();
769
		ApiResult::setArrayType( $variables, 'BCarray' );
770
		ApiResult::setIndexedTagName( $variables, 'v' );
771
772
		return $this->getResult()->addValue( 'query', $property, $variables );
773
	}
774
775
	public function appendProtocols( $property ) {
776
		// Make a copy of the global so we don't try to set the _element key of it - bug 45130
777
		$protocols = array_values( $this->getConfig()->get( 'UrlProtocols' ) );
778
		ApiResult::setArrayType( $protocols, 'BCarray' );
779
		ApiResult::setIndexedTagName( $protocols, 'p' );
780
781
		return $this->getResult()->addValue( 'query', $property, $protocols );
782
	}
783
784
	public function appendDefaultOptions( $property ) {
785
		$options = User::getDefaultOptions();
786
		$options[ApiResult::META_BC_BOOLS] = array_keys( $options );
787
		return $this->getResult()->addValue( 'query', $property, $options );
788
	}
789
790
	public function appendUploadDialog( $property ) {
791
		$config = $this->getConfig()->get( 'UploadDialog' );
792
		return $this->getResult()->addValue( 'query', $property, $config );
793
	}
794
795
	private function formatParserTags( $item ) {
796
		return "<{$item}>";
797
	}
798
799
	public function appendSubscribedHooks( $property ) {
800
		$hooks = $this->getConfig()->get( 'Hooks' );
801
		$myWgHooks = $hooks;
802
		ksort( $myWgHooks );
803
804
		$data = [];
805
		foreach ( $myWgHooks as $name => $subscribers ) {
806
			$arr = [
807
				'name' => $name,
808
				'subscribers' => array_map( [ 'SpecialVersion', 'arrayToString' ], $subscribers ),
809
			];
810
811
			ApiResult::setArrayType( $arr['subscribers'], 'array' );
812
			ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
813
			$data[] = $arr;
814
		}
815
816
		ApiResult::setIndexedTagName( $data, 'hook' );
817
818
		return $this->getResult()->addValue( 'query', $property, $data );
819
	}
820
821
	public function getCacheMode( $params ) {
822
		// Messages for $wgExtraInterlanguageLinkPrefixes depend on user language
823
		if (
824
			count( $this->getConfig()->get( 'ExtraInterlanguageLinkPrefixes' ) ) &&
825
			!is_null( $params['prop'] ) &&
826
			in_array( 'interwikimap', $params['prop'] )
827
		) {
828
			return 'anon-public-user-private';
829
		}
830
831
		return 'public';
832
	}
833
834
	public function getAllowedParams() {
835
		return [
836
			'prop' => [
837
				ApiBase::PARAM_DFLT => 'general',
838
				ApiBase::PARAM_ISMULTI => true,
839
				ApiBase::PARAM_TYPE => [
840
					'general',
841
					'namespaces',
842
					'namespacealiases',
843
					'specialpagealiases',
844
					'magicwords',
845
					'interwikimap',
846
					'dbrepllag',
847
					'statistics',
848
					'usergroups',
849
					'libraries',
850
					'extensions',
851
					'fileextensions',
852
					'rightsinfo',
853
					'restrictions',
854
					'languages',
855
					'skins',
856
					'extensiontags',
857
					'functionhooks',
858
					'showhooks',
859
					'variables',
860
					'protocols',
861
					'defaultoptions',
862
					'uploaddialog',
863
				],
864
				ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
865
			],
866
			'filteriw' => [
867
				ApiBase::PARAM_TYPE => [
868
					'local',
869
					'!local',
870
				]
871
			],
872
			'showalldb' => false,
873
			'numberingroup' => false,
874
			'inlanguagecode' => null,
875
		];
876
	}
877
878
	protected function getExamplesMessages() {
879
		return [
880
			'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
881
				=> 'apihelp-query+siteinfo-example-simple',
882
			'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
883
				=> 'apihelp-query+siteinfo-example-interwiki',
884
			'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
885
				=> 'apihelp-query+siteinfo-example-replag',
886
		];
887
	}
888
889
	public function getHelpUrls() {
890
		return 'https://www.mediawiki.org/wiki/API:Siteinfo';
891
	}
892
}
893