* (bug 18634) Create API to fetch MediaWiki's language fallback tree structure
[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'] = wfExpandUrl( $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
142 $fallbackLang = $wgContLang->getFallbackLanguageCode();
143 $fallbackLangArray = array();
144 while( $fallbackLang ) {
145 $fallbackLangArray[] = array( 'code' => $fallbackLang );
146 $fallbackLang = Language::getFallbackFor( $fallbackLang );
147 }
148 $data['fallback'] = $fallbackLangArray;
149 $this->getResult()->setIndexedTagName( $data['fallback'], 'lang' );
150
151 if ( $wgContLang->isRTL() ) {
152 $data['rtl'] = '';
153 }
154 $data['fallback8bitEncoding'] = $wgContLang->fallback8bitEncoding();
155
156 if ( wfReadOnly() ) {
157 $data['readonly'] = '';
158 $data['readonlyreason'] = wfReadOnlyReason();
159 }
160 if ( $GLOBALS['wgEnableWriteAPI'] ) {
161 $data['writeapi'] = '';
162 }
163
164 $tz = $GLOBALS['wgLocaltimezone'];
165 $offset = $GLOBALS['wgLocalTZoffset'];
166 if ( is_null( $tz ) ) {
167 $tz = 'UTC';
168 $offset = 0;
169 } elseif ( is_null( $offset ) ) {
170 $offset = 0;
171 }
172 $data['timezone'] = $tz;
173 $data['timeoffset'] = intval( $offset );
174 $data['articlepath'] = $GLOBALS['wgArticlePath'];
175 $data['scriptpath'] = $GLOBALS['wgScriptPath'];
176 $data['script'] = $GLOBALS['wgScript'];
177 $data['variantarticlepath'] = $GLOBALS['wgVariantArticlePath'];
178 $data['server'] = $GLOBALS['wgServer'];
179 $data['wikiid'] = wfWikiID();
180 $data['time'] = wfTimestamp( TS_ISO_8601, time() );
181
182 if ( $GLOBALS['wgMiserMode'] ) {
183 $data['misermode'] = '';
184 }
185
186 wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
187
188 return $this->getResult()->addValue( 'query', $property, $data );
189 }
190
191 protected function appendNamespaces( $property ) {
192 global $wgContLang;
193 $data = array();
194 foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
195 $data[$ns] = array(
196 'id' => intval( $ns ),
197 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
198 );
199 ApiResult::setContent( $data[$ns], $title );
200 $canonical = MWNamespace::getCanonicalName( $ns );
201
202 if ( MWNamespace::hasSubpages( $ns ) ) {
203 $data[$ns]['subpages'] = '';
204 }
205
206 if ( $canonical ) {
207 $data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
208 }
209
210 if ( MWNamespace::isContent( $ns ) ) {
211 $data[$ns]['content'] = '';
212 }
213 }
214
215 $this->getResult()->setIndexedTagName( $data, 'ns' );
216 return $this->getResult()->addValue( 'query', $property, $data );
217 }
218
219 protected function appendNamespaceAliases( $property ) {
220 global $wgNamespaceAliases, $wgContLang;
221 $aliases = array_merge( $wgNamespaceAliases, $wgContLang->getNamespaceAliases() );
222 $namespaces = $wgContLang->getNamespaces();
223 $data = array();
224 foreach ( $aliases as $title => $ns ) {
225 if ( $namespaces[$ns] == $title ) {
226 // Don't list duplicates
227 continue;
228 }
229 $item = array(
230 'id' => intval( $ns )
231 );
232 ApiResult::setContent( $item, strtr( $title, '_', ' ' ) );
233 $data[] = $item;
234 }
235
236 $this->getResult()->setIndexedTagName( $data, 'ns' );
237 return $this->getResult()->addValue( 'query', $property, $data );
238 }
239
240 protected function appendSpecialPageAliases( $property ) {
241 global $wgContLang;
242 $data = array();
243 foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases ) {
244 $arr = array( 'realname' => $specialpage, 'aliases' => $aliases );
245 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
246 $data[] = $arr;
247 }
248 $this->getResult()->setIndexedTagName( $data, 'specialpage' );
249 return $this->getResult()->addValue( 'query', $property, $data );
250 }
251
252 protected function appendMagicWords( $property ) {
253 global $wgContLang;
254 $data = array();
255 foreach ( $wgContLang->getMagicWords() as $magicword => $aliases ) {
256 $caseSensitive = array_shift( $aliases );
257 $arr = array( 'name' => $magicword, 'aliases' => $aliases );
258 if ( $caseSensitive ) {
259 $arr['case-sensitive'] = '';
260 }
261 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
262 $data[] = $arr;
263 }
264 $this->getResult()->setIndexedTagName( $data, 'magicword' );
265 return $this->getResult()->addValue( 'query', $property, $data );
266 }
267
268 protected function appendInterwikiMap( $property, $filter ) {
269 $local = null;
270 if ( $filter === 'local' ) {
271 $local = 1;
272 } elseif ( $filter === '!local' ) {
273 $local = 0;
274 } elseif ( $filter ) {
275 ApiBase::dieDebug( __METHOD__, "Unknown filter=$filter" );
276 }
277
278 $params = $this->extractRequestParams();
279 $langCode = isset( $params['inlanguagecode '] ) ? $params['inlanguagecode '] : '';
280
281 if( $langCode ) {
282 $langNames = Language::getTranslatedLanguageNames( $langCode );
283 } else {
284 $langNames = Language::getLanguageNames();
285 }
286
287 $getPrefixes = Interwiki::getAllPrefixes( $local );
288 $data = array();
289
290 foreach ( $getPrefixes as $row ) {
291 $prefix = $row['iw_prefix'];
292 $val = array();
293 $val['prefix'] = $prefix;
294 if ( $row['iw_local'] == '1' ) {
295 $val['local'] = '';
296 }
297 // $val['trans'] = intval( $row['iw_trans'] ); // should this be exposed?
298 if ( isset( $langNames[$prefix] ) ) {
299 $val['language'] = $langNames[$prefix];
300 }
301 $val['url'] = wfExpandUrl( $row['iw_url'] );
302 if( isset( $row['iw_wikiid'] ) ) {
303 $val['wikiid'] = $row['iw_wikiid'];
304 }
305 if( isset( $row['iw_api'] ) ) {
306 $val['api'] = $row['iw_api'];
307 }
308
309 $data[] = $val;
310 }
311
312 $this->getResult()->setIndexedTagName( $data, 'iw' );
313 return $this->getResult()->addValue( 'query', $property, $data );
314 }
315
316 protected function appendDbReplLagInfo( $property, $includeAll ) {
317 global $wgShowHostnames;
318 $data = array();
319 $lb = wfGetLB();
320 if ( $includeAll ) {
321 if ( !$wgShowHostnames ) {
322 $this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' );
323 }
324
325 $lags = $lb->getLagTimes();
326 foreach ( $lags as $i => $lag ) {
327 $data[] = array(
328 'host' => $lb->getServerName( $i ),
329 'lag' => $lag
330 );
331 }
332 } else {
333 list( $host, $lag, $index ) = $lb->getMaxLag();
334 $data[] = array(
335 'host' => $wgShowHostnames
336 ? $lb->getServerName( $index )
337 : '',
338 'lag' => intval( $lag )
339 );
340 }
341
342 $result = $this->getResult();
343 $result->setIndexedTagName( $data, 'db' );
344 return $this->getResult()->addValue( 'query', $property, $data );
345 }
346
347 protected function appendStatistics( $property ) {
348 global $wgDisableCounters;
349 $data = array();
350 $data['pages'] = intval( SiteStats::pages() );
351 $data['articles'] = intval( SiteStats::articles() );
352 if ( !$wgDisableCounters ) {
353 $data['views'] = intval( SiteStats::views() );
354 }
355 $data['edits'] = intval( SiteStats::edits() );
356 $data['images'] = intval( SiteStats::images() );
357 $data['users'] = intval( SiteStats::users() );
358 $data['activeusers'] = intval( SiteStats::activeUsers() );
359 $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
360 $data['jobs'] = intval( SiteStats::jobs() );
361 return $this->getResult()->addValue( 'query', $property, $data );
362 }
363
364 protected function appendUserGroups( $property, $numberInGroup ) {
365 global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
366
367 $data = array();
368 $result = $this->getResult();
369 foreach ( $wgGroupPermissions as $group => $permissions ) {
370 $arr = array(
371 'name' => $group,
372 'rights' => array_keys( $permissions, true ),
373 );
374
375 if ( $numberInGroup ) {
376 global $wgAutopromote;
377
378 if ( $group == 'user' ) {
379 $arr['number'] = SiteStats::users();
380
381 // '*' and autopromote groups have no size
382 } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) {
383 $arr['number'] = SiteStats::numberInGroup( $group );
384 }
385 }
386
387 $groupArr = array(
388 'add' => $wgAddGroups,
389 'remove' => $wgRemoveGroups,
390 'add-self' => $wgGroupsAddToSelf,
391 'remove-self' => $wgGroupsRemoveFromSelf
392 );
393
394 foreach ( $groupArr as $type => $rights ) {
395 if ( isset( $rights[$group] ) ) {
396 $arr[$type] = $rights[$group];
397 $result->setIndexedTagName( $arr[$type], 'group' );
398 }
399 }
400
401 $result->setIndexedTagName( $arr['rights'], 'permission' );
402 $data[] = $arr;
403 }
404
405 $result->setIndexedTagName( $data, 'group' );
406 return $result->addValue( 'query', $property, $data );
407 }
408
409 protected function appendFileExtensions( $property ) {
410 global $wgFileExtensions;
411
412 $data = array();
413 foreach ( $wgFileExtensions as $ext ) {
414 $data[] = array( 'ext' => $ext );
415 }
416 $this->getResult()->setIndexedTagName( $data, 'fe' );
417 return $this->getResult()->addValue( 'query', $property, $data );
418 }
419
420 protected function appendExtensions( $property ) {
421 global $wgExtensionCredits;
422 $data = array();
423 foreach ( $wgExtensionCredits as $type => $extensions ) {
424 foreach ( $extensions as $ext ) {
425 $ret = array();
426 $ret['type'] = $type;
427 if ( isset( $ext['name'] ) ) {
428 $ret['name'] = $ext['name'];
429 }
430 if ( isset( $ext['description'] ) ) {
431 $ret['description'] = $ext['description'];
432 }
433 if ( isset( $ext['descriptionmsg'] ) ) {
434 // Can be a string or array( key, param1, param2, ... )
435 if ( is_array( $ext['descriptionmsg'] ) ) {
436 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
437 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
438 $this->getResult()->setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
439 } else {
440 $ret['descriptionmsg'] = $ext['descriptionmsg'];
441 }
442 }
443 if ( isset( $ext['author'] ) ) {
444 $ret['author'] = is_array( $ext['author'] ) ?
445 implode( ', ', $ext['author' ] ) : $ext['author'];
446 }
447 if ( isset( $ext['url'] ) ) {
448 $ret['url'] = $ext['url'];
449 }
450 if ( isset( $ext['version'] ) ) {
451 $ret['version'] = $ext['version'];
452 } elseif ( isset( $ext['svn-revision'] ) &&
453 preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/',
454 $ext['svn-revision'], $m ) )
455 {
456 $ret['version'] = 'r' . $m[1];
457 }
458 $data[] = $ret;
459 }
460 }
461
462 $this->getResult()->setIndexedTagName( $data, 'ext' );
463 return $this->getResult()->addValue( 'query', $property, $data );
464 }
465
466 protected function appendRightsInfo( $property ) {
467 global $wgRightsPage, $wgRightsUrl, $wgRightsText;
468 $title = Title::newFromText( $wgRightsPage );
469 $url = $title ? wfExpandUrl( $title->getFullURL() ) : $wgRightsUrl;
470 $text = $wgRightsText;
471 if ( !$text && $title ) {
472 $text = $title->getPrefixedText();
473 }
474
475 $data = array(
476 'url' => $url ? $url : '',
477 'text' => $text ? $text : ''
478 );
479
480 return $this->getResult()->addValue( 'query', $property, $data );
481 }
482
483 public function appendLanguages( $property ) {
484 $params = $this->extractRequestParams();
485 $langCode = isset( $params['inlanguagecode '] ) ? $params['inlanguagecode '] : '';
486
487 if( $langCode ) {
488 $langNames = Language::getTranslatedLanguageNames( $langCode );
489 } else {
490 $langNames = Language::getLanguageNames();
491 }
492
493 $data = array();
494
495 foreach ( $langNames as $code => $name ) {
496 $lang = array( 'code' => $code );
497 ApiResult::setContent( $lang, $name );
498 $data[] = $lang;
499 }
500 $this->getResult()->setIndexedTagName( $data, 'lang' );
501 return $this->getResult()->addValue( 'query', $property, $data );
502 }
503
504 public function appendSkins( $property ) {
505 $data = array();
506 foreach ( Skin::getSkinNames() as $name => $displayName ) {
507 $skin = array( 'code' => $name );
508 ApiResult::setContent( $skin, $displayName );
509 $data[] = $skin;
510 }
511 $this->getResult()->setIndexedTagName( $data, 'skin' );
512 return $this->getResult()->addValue( 'query', $property, $data );
513 }
514
515 public function appendExtensionTags( $property ) {
516 global $wgParser;
517 $wgParser->firstCallInit();
518 $tags = array_map( array( $this, 'formatParserTags'), $wgParser->getTags() );
519 $this->getResult()->setIndexedTagName( $tags, 't' );
520 return $this->getResult()->addValue( 'query', $property, $tags );
521 }
522
523 public function appendFunctionHooks( $property ) {
524 global $wgParser;
525 $wgParser->firstCallInit();
526 $hooks = $wgParser->getFunctionHooks();
527 $this->getResult()->setIndexedTagName( $hooks, 'h' );
528 return $this->getResult()->addValue( 'query', $property, $hooks );
529 }
530
531 private function formatParserTags( $item ) {
532 return "<{$item}>";
533 }
534
535 public function appendSubscribedHooks( $property ) {
536 global $wgHooks;
537 $myWgHooks = $wgHooks;
538 ksort( $myWgHooks );
539
540 $data = array();
541 foreach ( $myWgHooks as $hook => $hooks ) {
542 $arr = array(
543 'name' => $hook,
544 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
545 );
546
547 $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
548 $data[] = $arr;
549 }
550
551 $this->getResult()->setIndexedTagName( $data, 'hook' );
552 return $this->getResult()->addValue( 'query', $property, $data );
553 }
554
555 public function getCacheMode( $params ) {
556 return 'public';
557 }
558
559 public function getAllowedParams() {
560 return array(
561 'prop' => array(
562 ApiBase::PARAM_DFLT => 'general',
563 ApiBase::PARAM_ISMULTI => true,
564 ApiBase::PARAM_TYPE => array(
565 'general',
566 'namespaces',
567 'namespacealiases',
568 'specialpagealiases',
569 'magicwords',
570 'interwikimap',
571 'dbrepllag',
572 'statistics',
573 'usergroups',
574 'extensions',
575 'fileextensions',
576 'rightsinfo',
577 'languages',
578 'skins',
579 'extensiontags',
580 'functionhooks',
581 'showhooks',
582 )
583 ),
584 'filteriw' => array(
585 ApiBase::PARAM_TYPE => array(
586 'local',
587 '!local',
588 )
589 ),
590 'showalldb' => false,
591 'numberingroup' => false,
592 'inlanguagecode ' => null,
593 );
594 }
595
596 public function getParamDescription() {
597 $p = $this->getModulePrefix();
598 return array(
599 'prop' => array(
600 'Which sysinfo properties to get:',
601 ' general - Overall system information',
602 ' namespaces - List of registered namespaces and their canonical names',
603 ' namespacealiases - List of registered namespace aliases',
604 ' specialpagealiases - List of special page aliases',
605 ' magicwords - List of magic words and their aliases',
606 ' statistics - Returns site statistics',
607 " interwikimap - Returns interwiki map (optionally filtered, (optionally localised by using {$p}inlanguagecode))",
608 ' dbrepllag - Returns database server with the highest replication lag',
609 ' usergroups - Returns user groups and the associated permissions',
610 ' extensions - Returns extensions installed on the wiki',
611 ' fileextensions - Returns list of file extensions allowed to be uploaded',
612 ' rightsinfo - Returns wiki rights (license) information if available',
613 " languages - Returns a list of languages MediaWiki supports (optionally localised by using {$p}inlanguagecode)",
614 ' skins - Returns a list of all enabled skins',
615 ' extensiontags - Returns a list of parser extension tags',
616 ' functionhooks - Returns a list of parser function hooks',
617 ' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)'
618 ),
619 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
620 'showalldb' => 'List all database servers, not just the one lagging the most',
621 'numberingroup' => 'Lists the number of users in user groups',
622 'inlanguagecode ' => 'Language code for localised language names (best effort, use CLDR extension)',
623 );
624 }
625
626 public function getDescription() {
627 return 'Return general information about the site';
628 }
629
630 public function getPossibleErrors() {
631 return array_merge( parent::getPossibleErrors(), array(
632 array( 'code' => 'includeAllDenied', 'info' => 'Cannot view all servers info unless $wgShowHostnames is true' ),
633 ) );
634 }
635
636 protected function getExamples() {
637 return array(
638 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics',
639 'api.php?action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local',
640 'api.php?action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb=',
641 );
642 }
643
644 public function getHelpUrls() {
645 return 'http://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si';
646 }
647
648 public function getVersion() {
649 return __CLASS__ . ': $Id$';
650 }
651 }