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