Merge "(bug 32297) Use symbolic names, not offsets for a default timezone."
[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( $query, $moduleName ) {
35 parent::__construct( $query, $moduleName, 'si' );
36 }
37
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" );
98 }
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;
105 }
106 $done[] = $p;
107 }
108 }
109
110 protected function appendGeneralInfo( $property ) {
111 global $wgContLang;
112
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();
123
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;
131 }
132 }
133
134 // 'case-insensitive' option is reserved for future
135 $data['case'] = $GLOBALS['wgCapitalLinks'] ? 'first-letter' : 'case-sensitive';
136
137 if ( isset( $GLOBALS['wgRightsCode'] ) ) {
138 $data['rightscode'] = $GLOBALS['wgRightsCode'];
139 }
140 $data['rights'] = $GLOBALS['wgRightsText'];
141 $data['lang'] = $GLOBALS['wgLanguageCode'];
142
143 $fallbacks = array();
144 foreach( $wgContLang->getFallbackLanguages() as $code ) {
145 $fallbacks[] = array( 'code' => $code );
146 }
147 $data['fallback'] = $fallbacks;
148 $this->getResult()->setIndexedTagName( $data['fallback'], 'lang' );
149
150 if( $wgContLang->hasVariants() ) {
151 $variants = array();
152 foreach( $wgContLang->getVariants() as $code ) {
153 $variants[] = array( 'code' => $code );
154 }
155 $data['variants'] = $variants;
156 $this->getResult()->setIndexedTagName( $data['variants'], 'lang' );
157 }
158
159 if ( $wgContLang->isRTL() ) {
160 $data['rtl'] = '';
161 }
162 $data['fallback8bitEncoding'] = $wgContLang->fallback8bitEncoding();
163
164 if ( wfReadOnly() ) {
165 $data['readonly'] = '';
166 $data['readonlyreason'] = wfReadOnlyReason();
167 }
168 if ( $GLOBALS['wgEnableWriteAPI'] ) {
169 $data['writeapi'] = '';
170 }
171
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;
179 }
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() );
189
190 if ( $GLOBALS['wgMiserMode'] ) {
191 $data['misermode'] = '';
192 }
193
194 $data['maxuploadsize'] = UploadBase::getMaxUploadSize();
195
196 wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
197
198 return $this->getResult()->addValue( 'query', $property, $data );
199 }
200
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',
208 );
209 ApiResult::setContent( $data[$ns], $title );
210 $canonical = MWNamespace::getCanonicalName( $ns );
211
212 if ( MWNamespace::hasSubpages( $ns ) ) {
213 $data[$ns]['subpages'] = '';
214 }
215
216 if ( $canonical ) {
217 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
218 }
219
220 if ( MWNamespace::isContent( $ns ) ) {
221 $data[$ns]['content'] = '';
222 }
223
224 if ( MWNamespace::isNonincludable( $ns ) ) {
225 $data[$ns]['nonincludable'] = '';
226 }
227 }
228
229 $this->getResult()->setIndexedTagName( $data, 'ns' );
230 return $this->getResult()->addValue( 'query', $property, $data );
231 }
232
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;
242 }
243 $item = array(
244 'id' => intval( $ns )
245 );
246 ApiResult::setContent( $item, strtr( $title, '_', ' ' ) );
247 $data[] = $item;
248 }
249
250 $this->getResult()->setIndexedTagName( $data, 'ns' );
251 return $this->getResult()->addValue( 'query', $property, $data );
252 }
253
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;
261 }
262 $this->getResult()->setIndexedTagName( $data, 'specialpage' );
263 return $this->getResult()->addValue( 'query', $property, $data );
264 }
265
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'] = '';
274 }
275 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
276 $data[] = $arr;
277 }
278 $this->getResult()->setIndexedTagName( $data, 'magicword' );
279 return $this->getResult()->addValue( 'query', $property, $data );
280 }
281
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" );
290 }
291
292 $params = $this->extractRequestParams();
293 $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
294 $langNames = Language::fetchLanguageNames( $langCode );
295
296 $getPrefixes = Interwiki::getAllPrefixes( $local );
297 $data = array();
298
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'] = '';
305 }
306 // $val['trans'] = intval( $row['iw_trans'] ); // should this be exposed?
307 if ( isset( $langNames[$prefix] ) ) {
308 $val['language'] = $langNames[$prefix];
309 }
310 $val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
311 if( isset( $row['iw_wikiid'] ) ) {
312 $val['wikiid'] = $row['iw_wikiid'];
313 }
314 if( isset( $row['iw_api'] ) ) {
315 $val['api'] = $row['iw_api'];
316 }
317
318 $data[] = $val;
319 }
320
321 $this->getResult()->setIndexedTagName( $data, 'iw' );
322 return $this->getResult()->addValue( 'query', $property, $data );
323 }
324
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' );
332 }
333
334 $lags = $lb->getLagTimes();
335 foreach ( $lags as $i => $lag ) {
336 $data[] = array(
337 'host' => $lb->getServerName( $i ),
338 'lag' => $lag
339 );
340 }
341 } else {
342 list( $host, $lag, $index ) = $lb->getMaxLag();
343 $data[] = array(
344 'host' => $wgShowHostnames
345 ? $lb->getServerName( $index )
346 : '',
347 'lag' => intval( $lag )
348 );
349 }
350
351 $result = $this->getResult();
352 $result->setIndexedTagName( $data, 'db' );
353 return $this->getResult()->addValue( 'query', $property, $data );
354 }
355
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() );
363 }
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 );
371 }
372
373 protected function appendUserGroups( $property, $numberInGroup ) {
374 global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
375
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 ),
382 );
383
384 if ( $numberInGroup ) {
385 global $wgAutopromote;
386
387 if ( $group == 'user' ) {
388 $arr['number'] = SiteStats::users();
389
390 // '*' and autopromote groups have no size
391 } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) {
392 $arr['number'] = SiteStats::numberInGroup( $group );
393 }
394 }
395
396 $groupArr = array(
397 'add' => $wgAddGroups,
398 'remove' => $wgRemoveGroups,
399 'add-self' => $wgGroupsAddToSelf,
400 'remove-self' => $wgGroupsRemoveFromSelf
401 );
402
403 foreach ( $groupArr as $type => $rights ) {
404 if ( isset( $rights[$group] ) ) {
405 $arr[$type] = $rights[$group];
406 $result->setIndexedTagName( $arr[$type], 'group' );
407 }
408 }
409
410 $result->setIndexedTagName( $arr['rights'], 'permission' );
411 $data[] = $arr;
412 }
413
414 $result->setIndexedTagName( $data, 'group' );
415 return $result->addValue( 'query', $property, $data );
416 }
417
418 protected function appendFileExtensions( $property ) {
419 global $wgFileExtensions;
420
421 $data = array();
422 foreach ( $wgFileExtensions as $ext ) {
423 $data[] = array( 'ext' => $ext );
424 }
425 $this->getResult()->setIndexedTagName( $data, 'fe' );
426 return $this->getResult()->addValue( 'query', $property, $data );
427 }
428
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'];
438 }
439 if ( isset( $ext['description'] ) ) {
440 $ret['description'] = $ext['description'];
441 }
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'];
450 }
451 }
452 if ( isset( $ext['author'] ) ) {
453 $ret['author'] = is_array( $ext['author'] ) ?
454 implode( ', ', $ext['author' ] ) : $ext['author'];
455 }
456 if ( isset( $ext['url'] ) ) {
457 $ret['url'] = $ext['url'];
458 }
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 ) )
464 {
465 $ret['version'] = 'r' . $m[1];
466 }
467 $data[] = $ret;
468 }
469 }
470
471 $this->getResult()->setIndexedTagName( $data, 'ext' );
472 return $this->getResult()->addValue( 'query', $property, $data );
473 }
474
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();
482 }
483
484 $data = array(
485 'url' => $url ? $url : '',
486 'text' => $text ? $text : ''
487 );
488
489 return $this->getResult()->addValue( 'query', $property, $data );
490 }
491
492 public function appendLanguages( $property ) {
493 $params = $this->extractRequestParams();
494 $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
495 $langNames = Language::fetchLanguageNames( $langCode );
496
497 $data = array();
498
499 foreach ( $langNames as $code => $name ) {
500 $lang = array( 'code' => $code );
501 ApiResult::setContent( $lang, $name );
502 $data[] = $lang;
503 }
504 $this->getResult()->setIndexedTagName( $data, 'lang' );
505 return $this->getResult()->addValue( 'query', $property, $data );
506 }
507
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;
514 }
515 $this->getResult()->setIndexedTagName( $data, 'skin' );
516 return $this->getResult()->addValue( 'query', $property, $data );
517 }
518
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 );
525 }
526
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 );
533 }
534
535 private function formatParserTags( $item ) {
536 return "<{$item}>";
537 }
538
539 public function appendSubscribedHooks( $property ) {
540 global $wgHooks;
541 $myWgHooks = $wgHooks;
542 ksort( $myWgHooks );
543
544 $data = array();
545 foreach ( $myWgHooks as $hook => $hooks ) {
546 $arr = array(
547 'name' => $hook,
548 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
549 );
550
551 $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
552 $data[] = $arr;
553 }
554
555 $this->getResult()->setIndexedTagName( $data, 'hook' );
556 return $this->getResult()->addValue( 'query', $property, $data );
557 }
558
559 public function getCacheMode( $params ) {
560 return 'public';
561 }
562
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',
586 )
587 ),
588 'filteriw' => array(
589 ApiBase::PARAM_TYPE => array(
590 'local',
591 '!local',
592 )
593 ),
594 'showalldb' => false,
595 'numberingroup' => false,
596 'inlanguagecode' => null,
597 );
598 }
599
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)'
622 ),
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)',
627 );
628 }
629
630 public function getDescription() {
631 return 'Return general information about the site';
632 }
633
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 ) );
638 }
639
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=',
645 );
646 }
647
648 public function getHelpUrls() {
649 return 'https://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si';
650 }
651
652 public function getVersion() {
653 return __CLASS__ . ': $Id$';
654 }
655 }