Followup r87150
[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 if ( !defined( 'MEDIAWIKI' ) ) {
28 // Eclipse helper - will be ignored in production
29 require_once( 'ApiQueryBase.php' );
30 }
31
32 /**
33 * A query action to return meta information about the wiki site.
34 *
35 * @ingroup API
36 */
37 class ApiQuerySiteinfo extends ApiQueryBase {
38
39 public function __construct( $query, $moduleName ) {
40 parent::__construct( $query, $moduleName, 'si' );
41 }
42
43 public function execute() {
44 $params = $this->extractRequestParams();
45 $done = array();
46 foreach ( $params['prop'] as $p ) {
47 switch ( $p ) {
48 case 'general':
49 $fit = $this->appendGeneralInfo( $p );
50 break;
51 case 'namespaces':
52 $fit = $this->appendNamespaces( $p );
53 break;
54 case 'namespacealiases':
55 $fit = $this->appendNamespaceAliases( $p );
56 break;
57 case 'specialpagealiases':
58 $fit = $this->appendSpecialPageAliases( $p );
59 break;
60 case 'magicwords':
61 $fit = $this->appendMagicWords( $p );
62 break;
63 case 'interwikimap':
64 $filteriw = isset( $params['filteriw'] ) ? $params['filteriw'] : false;
65 $fit = $this->appendInterwikiMap( $p, $filteriw );
66 break;
67 case 'dbrepllag':
68 $fit = $this->appendDbReplLagInfo( $p, $params['showalldb'] );
69 break;
70 case 'statistics':
71 $fit = $this->appendStatistics( $p );
72 break;
73 case 'usergroups':
74 $fit = $this->appendUserGroups( $p, $params['numberingroup'] );
75 break;
76 case 'extensions':
77 $fit = $this->appendExtensions( $p );
78 break;
79 case 'fileextensions':
80 $fit = $this->appendFileExtensions( $p );
81 break;
82 case 'rightsinfo':
83 $fit = $this->appendRightsInfo( $p );
84 break;
85 case 'languages':
86 $fit = $this->appendLanguages( $p );
87 break;
88 case 'skins':
89 $fit = $this->appendSkins( $p );
90 break;
91 case 'extensiontags':
92 $fit = $this->appendExtensionTags( $p );
93 break;
94 case 'functionhooks':
95 $fit = $this->appendFunctionHooks( $p );
96 break;
97 case 'showhooks':
98 $fit = $this->appendSubscribedHooks( $p );
99 break;
100 default:
101 ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
102 }
103 if ( !$fit ) {
104 // Abuse siprop as a query-continue parameter
105 // and set it to all unprocessed props
106 $this->setContinueEnumParameter( 'prop', implode( '|',
107 array_diff( $params['prop'], $done ) ) );
108 break;
109 }
110 $done[] = $p;
111 }
112 }
113
114 protected function appendGeneralInfo( $property ) {
115 global $wgContLang;
116
117 $data = array();
118 $mainPage = Title::newMainPage();
119 $data['mainpage'] = $mainPage->getPrefixedText();
120 $data['base'] = $mainPage->getFullUrl();
121 $data['sitename'] = $GLOBALS['wgSitename'];
122 $data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}";
123 $data['phpversion'] = phpversion();
124 $data['phpsapi'] = php_sapi_name();
125 $data['dbtype'] = $GLOBALS['wgDBtype'];
126 $data['dbversion'] = $this->getDB()->getServerVersion();
127
128 $svn = SpecialVersion::getSvnRevision( $GLOBALS['IP'] );
129 if ( $svn ) {
130 $data['rev'] = $svn;
131 }
132
133 // 'case-insensitive' option is reserved for future
134 $data['case'] = $GLOBALS['wgCapitalLinks'] ? 'first-letter' : 'case-sensitive';
135
136 if ( isset( $GLOBALS['wgRightsCode'] ) ) {
137 $data['rightscode'] = $GLOBALS['wgRightsCode'];
138 }
139 $data['rights'] = $GLOBALS['wgRightsText'];
140 $data['lang'] = $GLOBALS['wgLanguageCode'];
141 if ( $wgContLang->isRTL() ) {
142 $data['rtl'] = '';
143 }
144 $data['fallback8bitEncoding'] = $wgContLang->fallback8bitEncoding();
145
146 if ( wfReadOnly() ) {
147 $data['readonly'] = '';
148 $data['readonlyreason'] = wfReadOnlyReason();
149 }
150 if ( $GLOBALS['wgEnableWriteAPI'] ) {
151 $data['writeapi'] = '';
152 }
153
154 $tz = $GLOBALS['wgLocaltimezone'];
155 $offset = $GLOBALS['wgLocalTZoffset'];
156 if ( is_null( $tz ) ) {
157 $tz = 'UTC';
158 $offset = 0;
159 } elseif ( is_null( $offset ) ) {
160 $offset = 0;
161 }
162 $data['timezone'] = $tz;
163 $data['timeoffset'] = intval( $offset );
164 $data['articlepath'] = $GLOBALS['wgArticlePath'];
165 $data['scriptpath'] = $GLOBALS['wgScriptPath'];
166 $data['script'] = $GLOBALS['wgScript'];
167 $data['variantarticlepath'] = $GLOBALS['wgVariantArticlePath'];
168 $data['server'] = $GLOBALS['wgServer'];
169 $data['wikiid'] = wfWikiID();
170 $data['time'] = wfTimestamp( TS_ISO_8601, time() );
171
172 if ( $GLOBALS['wgMiserMode'] ) {
173 $data['misermode'] = '';
174 }
175
176 wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
177
178 return $this->getResult()->addValue( 'query', $property, $data );
179 }
180
181 protected function appendNamespaces( $property ) {
182 global $wgContLang;
183 $data = array();
184 foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
185 $data[$ns] = array(
186 'id' => intval( $ns ),
187 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
188 );
189 ApiResult::setContent( $data[$ns], $title );
190 $canonical = MWNamespace::getCanonicalName( $ns );
191
192 if ( MWNamespace::hasSubpages( $ns ) ) {
193 $data[$ns]['subpages'] = '';
194 }
195
196 if ( $canonical ) {
197 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
198 }
199
200 if ( MWNamespace::isContent( $ns ) ) {
201 $data[$ns]['content'] = '';
202 }
203 }
204
205 $this->getResult()->setIndexedTagName( $data, 'ns' );
206 return $this->getResult()->addValue( 'query', $property, $data );
207 }
208
209 protected function appendNamespaceAliases( $property ) {
210 global $wgNamespaceAliases, $wgContLang;
211 $aliases = array_merge( $wgNamespaceAliases, $wgContLang->getNamespaceAliases() );
212 $namespaces = $wgContLang->getNamespaces();
213 $data = array();
214 foreach ( $aliases as $title => $ns ) {
215 if ( $namespaces[$ns] == $title ) {
216 // Don't list duplicates
217 continue;
218 }
219 $item = array(
220 'id' => intval( $ns )
221 );
222 ApiResult::setContent( $item, strtr( $title, '_', ' ' ) );
223 $data[] = $item;
224 }
225
226 $this->getResult()->setIndexedTagName( $data, 'ns' );
227 return $this->getResult()->addValue( 'query', $property, $data );
228 }
229
230 protected function appendSpecialPageAliases( $property ) {
231 global $wgContLang;
232 $data = array();
233 foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases ) {
234 $arr = array( 'realname' => $specialpage, 'aliases' => $aliases );
235 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
236 $data[] = $arr;
237 }
238 $this->getResult()->setIndexedTagName( $data, 'specialpage' );
239 return $this->getResult()->addValue( 'query', $property, $data );
240 }
241
242 protected function appendMagicWords( $property ) {
243 global $wgContLang;
244 $data = array();
245 foreach ( $wgContLang->getMagicWords() as $magicword => $aliases ) {
246 $caseSensitive = array_shift( $aliases );
247 $arr = array( 'name' => $magicword, 'aliases' => $aliases );
248 if ( $caseSensitive ) {
249 $arr['case-sensitive'] = '';
250 }
251 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
252 $data[] = $arr;
253 }
254 $this->getResult()->setIndexedTagName( $data, 'magicword' );
255 return $this->getResult()->addValue( 'query', $property, $data );
256 }
257
258 protected function appendInterwikiMap( $property, $filter ) {
259 $this->resetQueryParams();
260 $this->addTables( 'interwiki' );
261 $this->addFields( array( 'iw_prefix', 'iw_local', 'iw_url', 'iw_wikiid', 'iw_api' ) );
262
263 if ( $filter === 'local' ) {
264 $this->addWhere( 'iw_local = 1' );
265 } elseif ( $filter === '!local' ) {
266 $this->addWhere( 'iw_local = 0' );
267 } elseif ( $filter ) {
268 ApiBase::dieDebug( __METHOD__, "Unknown filter=$filter" );
269 }
270
271 $this->addOption( 'ORDER BY', 'iw_prefix' );
272
273 $res = $this->select( __METHOD__ );
274
275 $data = array();
276 $langNames = Language::getLanguageNames();
277 foreach ( $res as $row ) {
278 $val = array();
279 $val['prefix'] = $row->iw_prefix;
280 if ( $row->iw_local == '1' ) {
281 $val['local'] = '';
282 }
283 // $val['trans'] = intval( $row->iw_trans ); // should this be exposed?
284 if ( isset( $langNames[$row->iw_prefix] ) ) {
285 $val['language'] = $langNames[$row->iw_prefix];
286 }
287 $val['url'] = $row->iw_url;
288 $val['wikiid'] = $row->iw_wikiid;
289 $val['api'] = $row->iw_api;
290
291 $data[] = $val;
292 }
293
294 $this->getResult()->setIndexedTagName( $data, 'iw' );
295 return $this->getResult()->addValue( 'query', $property, $data );
296 }
297
298 protected function appendDbReplLagInfo( $property, $includeAll ) {
299 global $wgShowHostnames;
300 $data = array();
301 if ( $includeAll ) {
302 if ( !$wgShowHostnames ) {
303 $this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' );
304 }
305
306 $lb = wfGetLB();
307 $lags = $lb->getLagTimes();
308 foreach ( $lags as $i => $lag ) {
309 $data[] = array(
310 'host' => $lb->getServerName( $i ),
311 'lag' => $lag
312 );
313 }
314 } else {
315 list( $host, $lag ) = wfGetLB()->getMaxLag();
316 $data[] = array(
317 'host' => $wgShowHostnames ? $host : '',
318 'lag' => intval( $lag )
319 );
320 }
321
322 $result = $this->getResult();
323 $result->setIndexedTagName( $data, 'db' );
324 return $this->getResult()->addValue( 'query', $property, $data );
325 }
326
327 protected function appendStatistics( $property ) {
328 global $wgDisableCounters;
329 $data = array();
330 $data['pages'] = intval( SiteStats::pages() );
331 $data['articles'] = intval( SiteStats::articles() );
332 if ( !$wgDisableCounters ) {
333 $data['views'] = intval( SiteStats::views() );
334 }
335 $data['edits'] = intval( SiteStats::edits() );
336 $data['images'] = intval( SiteStats::images() );
337 $data['users'] = intval( SiteStats::users() );
338 $data['activeusers'] = intval( SiteStats::activeUsers() );
339 $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
340 $data['jobs'] = intval( SiteStats::jobs() );
341 return $this->getResult()->addValue( 'query', $property, $data );
342 }
343
344 protected function appendUserGroups( $property, $numberInGroup ) {
345 global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
346
347 $data = array();
348 foreach ( $wgGroupPermissions as $group => $permissions ) {
349 $arr = array(
350 'name' => $group,
351 'rights' => array_keys( $permissions, true ),
352 );
353
354 if ( $numberInGroup ) {
355 global $wgAutopromote;
356
357 if ( $group == 'user' ) {
358 $arr['number'] = SiteStats::users();
359
360 // '*' and autopromote groups have no size
361 } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) {
362 $arr['number'] = SiteStats::numberInGroup( $group );
363 }
364 }
365
366 $groupArr = array(
367 'add' => $wgAddGroups,
368 'remove' => $wgRemoveGroups,
369 'add-self' => $wgGroupsAddToSelf,
370 'remove-self' => $wgGroupsRemoveFromSelf
371 );
372
373 foreach ( $groupArr as $type => $rights ) {
374 if ( isset( $rights[$group] ) ) {
375 $arr[$type] = $rights[$group];
376 $this->getResult()->setIndexedTagName( $arr[$type], 'group' );
377 }
378 }
379
380 $this->getResult()->setIndexedTagName( $arr['rights'], 'permission' );
381 $data[] = $arr;
382 }
383
384 $this->getResult()->setIndexedTagName( $data, 'group' );
385 return $this->getResult()->addValue( 'query', $property, $data );
386 }
387
388 protected function appendFileExtensions( $property ) {
389 global $wgFileExtensions;
390
391 $data = array();
392 foreach ( $wgFileExtensions as $ext ) {
393 $data[] = array( 'ext' => $ext );
394 }
395 $this->getResult()->setIndexedTagName( $data, 'fe' );
396 return $this->getResult()->addValue( 'query', $property, $data );
397 }
398
399 protected function appendExtensions( $property ) {
400 global $wgExtensionCredits;
401 $data = array();
402 foreach ( $wgExtensionCredits as $type => $extensions ) {
403 foreach ( $extensions as $ext ) {
404 $ret = array();
405 $ret['type'] = $type;
406 if ( isset( $ext['name'] ) ) {
407 $ret['name'] = $ext['name'];
408 }
409 if ( isset( $ext['description'] ) ) {
410 $ret['description'] = $ext['description'];
411 }
412 if ( isset( $ext['descriptionmsg'] ) ) {
413 // Can be a string or array( key, param1, param2, ... )
414 if ( is_array( $ext['descriptionmsg'] ) ) {
415 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
416 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
417 $this->getResult()->setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
418 } else {
419 $ret['descriptionmsg'] = $ext['descriptionmsg'];
420 }
421 }
422 if ( isset( $ext['author'] ) ) {
423 $ret['author'] = is_array( $ext['author'] ) ?
424 implode( ', ', $ext['author' ] ) : $ext['author'];
425 }
426 if ( isset( $ext['url'] ) ) {
427 $ret['url'] = $ext['url'];
428 }
429 if ( isset( $ext['version'] ) ) {
430 $ret['version'] = $ext['version'];
431 } elseif ( isset( $ext['svn-revision'] ) &&
432 preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/',
433 $ext['svn-revision'], $m ) )
434 {
435 $ret['version'] = 'r' . $m[1];
436 }
437 $data[] = $ret;
438 }
439 }
440
441 $this->getResult()->setIndexedTagName( $data, 'ext' );
442 return $this->getResult()->addValue( 'query', $property, $data );
443 }
444
445 protected function appendRightsInfo( $property ) {
446 global $wgRightsPage, $wgRightsUrl, $wgRightsText;
447 $title = Title::newFromText( $wgRightsPage );
448 $url = $title ? $title->getFullURL() : $wgRightsUrl;
449 $text = $wgRightsText;
450 if ( !$text && $title ) {
451 $text = $title->getPrefixedText();
452 }
453
454 $data = array(
455 'url' => $url ? $url : '',
456 'text' => $text ? $text : ''
457 );
458
459 return $this->getResult()->addValue( 'query', $property, $data );
460 }
461
462 public function appendLanguages( $property ) {
463 $data = array();
464 foreach ( Language::getLanguageNames() as $code => $name ) {
465 $lang = array( 'code' => $code );
466 ApiResult::setContent( $lang, $name );
467 $data[] = $lang;
468 }
469 $this->getResult()->setIndexedTagName( $data, 'lang' );
470 return $this->getResult()->addValue( 'query', $property, $data );
471 }
472
473 public function appendSkins( $property ) {
474 $data = array();
475 foreach ( Skin::getSkinNames() as $name => $displayName ) {
476 $skin = array( 'code' => $name );
477 ApiResult::setContent( $skin, $displayName );
478 $data[] = $skin;
479 }
480 $this->getResult()->setIndexedTagName( $data, 'skin' );
481 return $this->getResult()->addValue( 'query', $property, $data );
482 }
483
484 public function appendExtensionTags( $property ) {
485 global $wgParser;
486 $wgParser->firstCallInit();
487 $tags = array_map( array( $this, 'formatParserTags'), $wgParser->getTags() );
488 $this->getResult()->setIndexedTagName( $tags, 't' );
489 return $this->getResult()->addValue( 'query', $property, $tags );
490 }
491
492 public function appendFunctionHooks( $property ) {
493 global $wgParser;
494 $wgParser->firstCallInit();
495 $hooks = $wgParser->getFunctionHooks();
496 $this->getResult()->setIndexedTagName( $hooks, 'h' );
497 return $this->getResult()->addValue( 'query', $property, $hooks );
498 }
499
500 private function formatParserTags( $item ) {
501 return "<{$item}>";
502 }
503
504 public function appendSubscribedHooks( $property ) {
505 global $wgHooks;
506 $myWgHooks = $wgHooks;
507 ksort( $myWgHooks );
508
509 $data = array();
510 foreach ( $myWgHooks as $hook => $hooks ) {
511 $arr = array(
512 'name' => $hook,
513 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
514 );
515
516 $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
517 $data[] = $arr;
518 }
519
520 $this->getResult()->setIndexedTagName( $data, 'hook' );
521 return $this->getResult()->addValue( 'query', $property, $data );
522 }
523
524 public function getCacheMode( $params ) {
525 return 'public';
526 }
527
528 public function getAllowedParams() {
529 return array(
530 'prop' => array(
531 ApiBase::PARAM_DFLT => 'general',
532 ApiBase::PARAM_ISMULTI => true,
533 ApiBase::PARAM_TYPE => array(
534 'general',
535 'namespaces',
536 'namespacealiases',
537 'specialpagealiases',
538 'magicwords',
539 'interwikimap',
540 'dbrepllag',
541 'statistics',
542 'usergroups',
543 'extensions',
544 'fileextensions',
545 'rightsinfo',
546 'languages',
547 'skins',
548 'extensiontags',
549 'functionhooks',
550 'showhooks',
551 )
552 ),
553 'filteriw' => array(
554 ApiBase::PARAM_TYPE => array(
555 'local',
556 '!local',
557 )
558 ),
559 'showalldb' => false,
560 'numberingroup' => false,
561 );
562 }
563
564 public function getParamDescription() {
565 return array(
566 'prop' => array(
567 'Which sysinfo properties to get:',
568 ' general - Overall system information',
569 ' namespaces - List of registered namespaces and their canonical names',
570 ' namespacealiases - List of registered namespace aliases',
571 ' specialpagealiases - List of special page aliases',
572 ' magicwords - List of magic words and their aliases',
573 ' statistics - Returns site statistics',
574 ' interwikimap - Returns interwiki map (optionally filtered)',
575 ' dbrepllag - Returns database server with the highest replication lag',
576 ' usergroups - Returns user groups and the associated permissions',
577 ' extensions - Returns extensions installed on the wiki',
578 ' fileextensions - Returns list of file extensions allowed to be uploaded',
579 ' rightsinfo - Returns wiki rights (license) information if available',
580 ' languages - Returns a list of languages MediaWiki supports',
581 ' skins - Returns a list of all enabled skins',
582 ' extensiontags - Returns a list of parser extension tags',
583 ' functionhooks - Returns a list of parser function hooks',
584 ' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)'
585 ),
586 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
587 'showalldb' => 'List all database servers, not just the one lagging the most',
588 'numberingroup' => 'Lists the number of users in user groups',
589 );
590 }
591
592 public function getDescription() {
593 return 'Return general information about the site';
594 }
595
596 public function getPossibleErrors() {
597 return array_merge( parent::getPossibleErrors(), array(
598 array( 'code' => 'includeAllDenied', 'info' => 'Cannot view all servers info unless $wgShowHostnames is true' ),
599 ) );
600 }
601
602 protected function getExamples() {
603 return array(
604 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics',
605 'api.php?action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local',
606 'api.php?action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb=',
607 );
608 }
609
610 public function getVersion() {
611 return __CLASS__ . ': $Id$';
612 }
613 }