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