Bug 36453 - Provide the git info on action=query&meta=siteinfo
[mediawiki.git] / includes / api / ApiQuerySiteinfo.php
blob38e37c01864286f47a9043dc930f39ede1870f1c
1 <?php
2 /**
5 * Created on Sep 25, 2006
7 * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
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.
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.
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
24 * @file
27 /**
28 * A query action to return meta information about the wiki site.
30 * @ingroup API
32 class ApiQuerySiteinfo extends ApiQueryBase {
34 public function __construct( $query, $moduleName ) {
35 parent::__construct( $query, $moduleName, 'si' );
38 public function execute() {
39 $params = $this->extractRequestParams();
40 $done = array();
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 'extensions':
73 $fit = $this->appendExtensions( $p );
74 break;
75 case 'fileextensions':
76 $fit = $this->appendFileExtensions( $p );
77 break;
78 case 'rightsinfo':
79 $fit = $this->appendRightsInfo( $p );
80 break;
81 case 'languages':
82 $fit = $this->appendLanguages( $p );
83 break;
84 case 'skins':
85 $fit = $this->appendSkins( $p );
86 break;
87 case 'extensiontags':
88 $fit = $this->appendExtensionTags( $p );
89 break;
90 case 'functionhooks':
91 $fit = $this->appendFunctionHooks( $p );
92 break;
93 case 'showhooks':
94 $fit = $this->appendSubscribedHooks( $p );
95 break;
96 default:
97 ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
99 if ( !$fit ) {
100 // Abuse siprop as a query-continue parameter
101 // and set it to all unprocessed props
102 $this->setContinueEnumParameter( 'prop', implode( '|',
103 array_diff( $params['prop'], $done ) ) );
104 break;
106 $done[] = $p;
110 protected function appendGeneralInfo( $property ) {
111 global $wgContLang;
113 $data = array();
114 $mainPage = Title::newMainPage();
115 $data['mainpage'] = $mainPage->getPrefixedText();
116 $data['base'] = wfExpandUrl( $mainPage->getFullUrl(), PROTO_CURRENT );
117 $data['sitename'] = $GLOBALS['wgSitename'];
118 $data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}";
119 $data['phpversion'] = phpversion();
120 $data['phpsapi'] = php_sapi_name();
121 $data['dbtype'] = $GLOBALS['wgDBtype'];
122 $data['dbversion'] = $this->getDB()->getServerVersion();
124 $git = SpecialVersion::getGitHeadSha1( $GLOBALS['IP'] );
125 if ( $git ) {
126 $data['git-hash'] = $git;
127 } else {
128 $svn = SpecialVersion::getSvnRevision( $GLOBALS['IP'] );
129 if ( $svn ) {
130 $data['rev'] = $svn;
134 // 'case-insensitive' option is reserved for future
135 $data['case'] = $GLOBALS['wgCapitalLinks'] ? 'first-letter' : 'case-sensitive';
137 if ( isset( $GLOBALS['wgRightsCode'] ) ) {
138 $data['rightscode'] = $GLOBALS['wgRightsCode'];
140 $data['rights'] = $GLOBALS['wgRightsText'];
141 $data['lang'] = $GLOBALS['wgLanguageCode'];
143 $fallbacks = array();
144 foreach( $wgContLang->getFallbackLanguages() as $code ) {
145 $fallbacks[] = array( 'code' => $code );
147 $data['fallback'] = $fallbacks;
148 $this->getResult()->setIndexedTagName( $data['fallback'], 'lang' );
150 if( $wgContLang->hasVariants() ) {
151 $variants = array();
152 foreach( $wgContLang->getVariants() as $code ) {
153 $variants[] = array( 'code' => $code );
155 $data['variants'] = $variants;
156 $this->getResult()->setIndexedTagName( $data['variants'], 'lang' );
159 if ( $wgContLang->isRTL() ) {
160 $data['rtl'] = '';
162 $data['fallback8bitEncoding'] = $wgContLang->fallback8bitEncoding();
164 if ( wfReadOnly() ) {
165 $data['readonly'] = '';
166 $data['readonlyreason'] = wfReadOnlyReason();
168 if ( $GLOBALS['wgEnableWriteAPI'] ) {
169 $data['writeapi'] = '';
172 $tz = $GLOBALS['wgLocaltimezone'];
173 $offset = $GLOBALS['wgLocalTZoffset'];
174 if ( is_null( $tz ) ) {
175 $tz = 'UTC';
176 $offset = 0;
177 } elseif ( is_null( $offset ) ) {
178 $offset = 0;
180 $data['timezone'] = $tz;
181 $data['timeoffset'] = intval( $offset );
182 $data['articlepath'] = $GLOBALS['wgArticlePath'];
183 $data['scriptpath'] = $GLOBALS['wgScriptPath'];
184 $data['script'] = $GLOBALS['wgScript'];
185 $data['variantarticlepath'] = $GLOBALS['wgVariantArticlePath'];
186 $data['server'] = $GLOBALS['wgServer'];
187 $data['wikiid'] = wfWikiID();
188 $data['time'] = wfTimestamp( TS_ISO_8601, time() );
190 if ( $GLOBALS['wgMiserMode'] ) {
191 $data['misermode'] = '';
194 $data['maxuploadsize'] = UploadBase::getMaxUploadSize();
196 wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
198 return $this->getResult()->addValue( 'query', $property, $data );
201 protected function appendNamespaces( $property ) {
202 global $wgContLang;
203 $data = array();
204 foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
205 $data[$ns] = array(
206 'id' => intval( $ns ),
207 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
209 ApiResult::setContent( $data[$ns], $title );
210 $canonical = MWNamespace::getCanonicalName( $ns );
212 if ( MWNamespace::hasSubpages( $ns ) ) {
213 $data[$ns]['subpages'] = '';
216 if ( $canonical ) {
217 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
220 if ( MWNamespace::isContent( $ns ) ) {
221 $data[$ns]['content'] = '';
224 if ( MWNamespace::isNonincludable( $ns ) ) {
225 $data[$ns]['nonincludable'] = '';
229 $this->getResult()->setIndexedTagName( $data, 'ns' );
230 return $this->getResult()->addValue( 'query', $property, $data );
233 protected function appendNamespaceAliases( $property ) {
234 global $wgNamespaceAliases, $wgContLang;
235 $aliases = array_merge( $wgNamespaceAliases, $wgContLang->getNamespaceAliases() );
236 $namespaces = $wgContLang->getNamespaces();
237 $data = array();
238 foreach ( $aliases as $title => $ns ) {
239 if ( $namespaces[$ns] == $title ) {
240 // Don't list duplicates
241 continue;
243 $item = array(
244 'id' => intval( $ns )
246 ApiResult::setContent( $item, strtr( $title, '_', ' ' ) );
247 $data[] = $item;
250 $this->getResult()->setIndexedTagName( $data, 'ns' );
251 return $this->getResult()->addValue( 'query', $property, $data );
254 protected function appendSpecialPageAliases( $property ) {
255 global $wgContLang;
256 $data = array();
257 foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases ) {
258 $arr = array( 'realname' => $specialpage, 'aliases' => $aliases );
259 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
260 $data[] = $arr;
262 $this->getResult()->setIndexedTagName( $data, 'specialpage' );
263 return $this->getResult()->addValue( 'query', $property, $data );
266 protected function appendMagicWords( $property ) {
267 global $wgContLang;
268 $data = array();
269 foreach ( $wgContLang->getMagicWords() as $magicword => $aliases ) {
270 $caseSensitive = array_shift( $aliases );
271 $arr = array( 'name' => $magicword, 'aliases' => $aliases );
272 if ( $caseSensitive ) {
273 $arr['case-sensitive'] = '';
275 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
276 $data[] = $arr;
278 $this->getResult()->setIndexedTagName( $data, 'magicword' );
279 return $this->getResult()->addValue( 'query', $property, $data );
282 protected function appendInterwikiMap( $property, $filter ) {
283 $local = null;
284 if ( $filter === 'local' ) {
285 $local = 1;
286 } elseif ( $filter === '!local' ) {
287 $local = 0;
288 } elseif ( $filter ) {
289 ApiBase::dieDebug( __METHOD__, "Unknown filter=$filter" );
292 $params = $this->extractRequestParams();
293 $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
294 $langNames = Language::fetchLanguageNames( $langCode );
296 $getPrefixes = Interwiki::getAllPrefixes( $local );
297 $data = array();
299 foreach ( $getPrefixes as $row ) {
300 $prefix = $row['iw_prefix'];
301 $val = array();
302 $val['prefix'] = $prefix;
303 if ( $row['iw_local'] == '1' ) {
304 $val['local'] = '';
306 // $val['trans'] = intval( $row['iw_trans'] ); // should this be exposed?
307 if ( isset( $langNames[$prefix] ) ) {
308 $val['language'] = $langNames[$prefix];
310 $val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
311 if( isset( $row['iw_wikiid'] ) ) {
312 $val['wikiid'] = $row['iw_wikiid'];
314 if( isset( $row['iw_api'] ) ) {
315 $val['api'] = $row['iw_api'];
318 $data[] = $val;
321 $this->getResult()->setIndexedTagName( $data, 'iw' );
322 return $this->getResult()->addValue( 'query', $property, $data );
325 protected function appendDbReplLagInfo( $property, $includeAll ) {
326 global $wgShowHostnames;
327 $data = array();
328 $lb = wfGetLB();
329 if ( $includeAll ) {
330 if ( !$wgShowHostnames ) {
331 $this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' );
334 $lags = $lb->getLagTimes();
335 foreach ( $lags as $i => $lag ) {
336 $data[] = array(
337 'host' => $lb->getServerName( $i ),
338 'lag' => $lag
341 } else {
342 list( $host, $lag, $index ) = $lb->getMaxLag();
343 $data[] = array(
344 'host' => $wgShowHostnames
345 ? $lb->getServerName( $index )
346 : '',
347 'lag' => intval( $lag )
351 $result = $this->getResult();
352 $result->setIndexedTagName( $data, 'db' );
353 return $this->getResult()->addValue( 'query', $property, $data );
356 protected function appendStatistics( $property ) {
357 global $wgDisableCounters;
358 $data = array();
359 $data['pages'] = intval( SiteStats::pages() );
360 $data['articles'] = intval( SiteStats::articles() );
361 if ( !$wgDisableCounters ) {
362 $data['views'] = intval( SiteStats::views() );
364 $data['edits'] = intval( SiteStats::edits() );
365 $data['images'] = intval( SiteStats::images() );
366 $data['users'] = intval( SiteStats::users() );
367 $data['activeusers'] = intval( SiteStats::activeUsers() );
368 $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
369 $data['jobs'] = intval( SiteStats::jobs() );
370 return $this->getResult()->addValue( 'query', $property, $data );
373 protected function appendUserGroups( $property, $numberInGroup ) {
374 global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
376 $data = array();
377 $result = $this->getResult();
378 foreach ( $wgGroupPermissions as $group => $permissions ) {
379 $arr = array(
380 'name' => $group,
381 'rights' => array_keys( $permissions, true ),
384 if ( $numberInGroup ) {
385 global $wgAutopromote;
387 if ( $group == 'user' ) {
388 $arr['number'] = SiteStats::users();
390 // '*' and autopromote groups have no size
391 } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) {
392 $arr['number'] = SiteStats::numberInGroup( $group );
396 $groupArr = array(
397 'add' => $wgAddGroups,
398 'remove' => $wgRemoveGroups,
399 'add-self' => $wgGroupsAddToSelf,
400 'remove-self' => $wgGroupsRemoveFromSelf
403 foreach ( $groupArr as $type => $rights ) {
404 if ( isset( $rights[$group] ) ) {
405 $arr[$type] = $rights[$group];
406 $result->setIndexedTagName( $arr[$type], 'group' );
410 $result->setIndexedTagName( $arr['rights'], 'permission' );
411 $data[] = $arr;
414 $result->setIndexedTagName( $data, 'group' );
415 return $result->addValue( 'query', $property, $data );
418 protected function appendFileExtensions( $property ) {
419 global $wgFileExtensions;
421 $data = array();
422 foreach ( $wgFileExtensions as $ext ) {
423 $data[] = array( 'ext' => $ext );
425 $this->getResult()->setIndexedTagName( $data, 'fe' );
426 return $this->getResult()->addValue( 'query', $property, $data );
429 protected function appendExtensions( $property ) {
430 global $wgExtensionCredits;
431 $data = array();
432 foreach ( $wgExtensionCredits as $type => $extensions ) {
433 foreach ( $extensions as $ext ) {
434 $ret = array();
435 $ret['type'] = $type;
436 if ( isset( $ext['name'] ) ) {
437 $ret['name'] = $ext['name'];
439 if ( isset( $ext['description'] ) ) {
440 $ret['description'] = $ext['description'];
442 if ( isset( $ext['descriptionmsg'] ) ) {
443 // Can be a string or array( key, param1, param2, ... )
444 if ( is_array( $ext['descriptionmsg'] ) ) {
445 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
446 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
447 $this->getResult()->setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
448 } else {
449 $ret['descriptionmsg'] = $ext['descriptionmsg'];
452 if ( isset( $ext['author'] ) ) {
453 $ret['author'] = is_array( $ext['author'] ) ?
454 implode( ', ', $ext['author' ] ) : $ext['author'];
456 if ( isset( $ext['url'] ) ) {
457 $ret['url'] = $ext['url'];
459 if ( isset( $ext['version'] ) ) {
460 $ret['version'] = $ext['version'];
461 } elseif ( isset( $ext['svn-revision'] ) &&
462 preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/',
463 $ext['svn-revision'], $m ) )
465 $ret['version'] = 'r' . $m[1];
467 $data[] = $ret;
471 $this->getResult()->setIndexedTagName( $data, 'ext' );
472 return $this->getResult()->addValue( 'query', $property, $data );
475 protected function appendRightsInfo( $property ) {
476 global $wgRightsPage, $wgRightsUrl, $wgRightsText;
477 $title = Title::newFromText( $wgRightsPage );
478 $url = $title ? wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ) : $wgRightsUrl;
479 $text = $wgRightsText;
480 if ( !$text && $title ) {
481 $text = $title->getPrefixedText();
484 $data = array(
485 'url' => $url ? $url : '',
486 'text' => $text ? $text : ''
489 return $this->getResult()->addValue( 'query', $property, $data );
492 public function appendLanguages( $property ) {
493 $params = $this->extractRequestParams();
494 $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
495 $langNames = Language::fetchLanguageNames( $langCode );
497 $data = array();
499 foreach ( $langNames as $code => $name ) {
500 $lang = array( 'code' => $code );
501 ApiResult::setContent( $lang, $name );
502 $data[] = $lang;
504 $this->getResult()->setIndexedTagName( $data, 'lang' );
505 return $this->getResult()->addValue( 'query', $property, $data );
508 public function appendSkins( $property ) {
509 $data = array();
510 foreach ( Skin::getSkinNames() as $name => $displayName ) {
511 $skin = array( 'code' => $name );
512 ApiResult::setContent( $skin, $displayName );
513 $data[] = $skin;
515 $this->getResult()->setIndexedTagName( $data, 'skin' );
516 return $this->getResult()->addValue( 'query', $property, $data );
519 public function appendExtensionTags( $property ) {
520 global $wgParser;
521 $wgParser->firstCallInit();
522 $tags = array_map( array( $this, 'formatParserTags'), $wgParser->getTags() );
523 $this->getResult()->setIndexedTagName( $tags, 't' );
524 return $this->getResult()->addValue( 'query', $property, $tags );
527 public function appendFunctionHooks( $property ) {
528 global $wgParser;
529 $wgParser->firstCallInit();
530 $hooks = $wgParser->getFunctionHooks();
531 $this->getResult()->setIndexedTagName( $hooks, 'h' );
532 return $this->getResult()->addValue( 'query', $property, $hooks );
535 private function formatParserTags( $item ) {
536 return "<{$item}>";
539 public function appendSubscribedHooks( $property ) {
540 global $wgHooks;
541 $myWgHooks = $wgHooks;
542 ksort( $myWgHooks );
544 $data = array();
545 foreach ( $myWgHooks as $hook => $hooks ) {
546 $arr = array(
547 'name' => $hook,
548 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
551 $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
552 $data[] = $arr;
555 $this->getResult()->setIndexedTagName( $data, 'hook' );
556 return $this->getResult()->addValue( 'query', $property, $data );
559 public function getCacheMode( $params ) {
560 return 'public';
563 public function getAllowedParams() {
564 return array(
565 'prop' => array(
566 ApiBase::PARAM_DFLT => 'general',
567 ApiBase::PARAM_ISMULTI => true,
568 ApiBase::PARAM_TYPE => array(
569 'general',
570 'namespaces',
571 'namespacealiases',
572 'specialpagealiases',
573 'magicwords',
574 'interwikimap',
575 'dbrepllag',
576 'statistics',
577 'usergroups',
578 'extensions',
579 'fileextensions',
580 'rightsinfo',
581 'languages',
582 'skins',
583 'extensiontags',
584 'functionhooks',
585 'showhooks',
588 'filteriw' => array(
589 ApiBase::PARAM_TYPE => array(
590 'local',
591 '!local',
594 'showalldb' => false,
595 'numberingroup' => false,
596 'inlanguagecode' => null,
600 public function getParamDescription() {
601 $p = $this->getModulePrefix();
602 return array(
603 'prop' => array(
604 'Which sysinfo properties to get:',
605 ' general - Overall system information',
606 ' namespaces - List of registered namespaces and their canonical names',
607 ' namespacealiases - List of registered namespace aliases',
608 ' specialpagealiases - List of special page aliases',
609 ' magicwords - List of magic words and their aliases',
610 ' statistics - Returns site statistics',
611 " interwikimap - Returns interwiki map (optionally filtered, (optionally localised by using {$p}inlanguagecode))",
612 ' dbrepllag - Returns database server with the highest replication lag',
613 ' usergroups - Returns user groups and the associated permissions',
614 ' extensions - Returns extensions installed on the wiki',
615 ' fileextensions - Returns list of file extensions allowed to be uploaded',
616 ' rightsinfo - Returns wiki rights (license) information if available',
617 " languages - Returns a list of languages MediaWiki supports (optionally localised by using {$p}inlanguagecode)",
618 ' skins - Returns a list of all enabled skins',
619 ' extensiontags - Returns a list of parser extension tags',
620 ' functionhooks - Returns a list of parser function hooks',
621 ' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)'
623 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
624 'showalldb' => 'List all database servers, not just the one lagging the most',
625 'numberingroup' => 'Lists the number of users in user groups',
626 'inlanguagecode' => 'Language code for localised language names (best effort, use CLDR extension)',
630 public function getDescription() {
631 return 'Return general information about the site';
634 public function getPossibleErrors() {
635 return array_merge( parent::getPossibleErrors(), array(
636 array( 'code' => 'includeAllDenied', 'info' => 'Cannot view all servers info unless $wgShowHostnames is true' ),
637 ) );
640 public function getExamples() {
641 return array(
642 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics',
643 'api.php?action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local',
644 'api.php?action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb=',
648 public function getHelpUrls() {
649 return 'https://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si';
652 public function getVersion() {
653 return __CLASS__ . ': $Id$';