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