Merge "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 foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases ) {
261 $arr = array( 'realname' => $specialpage, 'aliases' => $aliases );
262 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
263 $data[] = $arr;
264 }
265 $this->getResult()->setIndexedTagName( $data, 'specialpage' );
266 return $this->getResult()->addValue( 'query', $property, $data );
267 }
268
269 protected function appendMagicWords( $property ) {
270 global $wgContLang;
271 $data = array();
272 foreach ( $wgContLang->getMagicWords() as $magicword => $aliases ) {
273 $caseSensitive = array_shift( $aliases );
274 $arr = array( 'name' => $magicword, 'aliases' => $aliases );
275 if ( $caseSensitive ) {
276 $arr['case-sensitive'] = '';
277 }
278 $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
279 $data[] = $arr;
280 }
281 $this->getResult()->setIndexedTagName( $data, 'magicword' );
282 return $this->getResult()->addValue( 'query', $property, $data );
283 }
284
285 protected function appendInterwikiMap( $property, $filter ) {
286 $local = null;
287 if ( $filter === 'local' ) {
288 $local = 1;
289 } elseif ( $filter === '!local' ) {
290 $local = 0;
291 } elseif ( $filter ) {
292 ApiBase::dieDebug( __METHOD__, "Unknown filter=$filter" );
293 }
294
295 $params = $this->extractRequestParams();
296 $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
297 $langNames = Language::fetchLanguageNames( $langCode );
298
299 $getPrefixes = Interwiki::getAllPrefixes( $local );
300 $data = array();
301
302 foreach ( $getPrefixes as $row ) {
303 $prefix = $row['iw_prefix'];
304 $val = array();
305 $val['prefix'] = $prefix;
306 if ( $row['iw_local'] == '1' ) {
307 $val['local'] = '';
308 }
309 // $val['trans'] = intval( $row['iw_trans'] ); // should this be exposed?
310 if ( isset( $langNames[$prefix] ) ) {
311 $val['language'] = $langNames[$prefix];
312 }
313 $val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
314 if( isset( $row['iw_wikiid'] ) ) {
315 $val['wikiid'] = $row['iw_wikiid'];
316 }
317 if( isset( $row['iw_api'] ) ) {
318 $val['api'] = $row['iw_api'];
319 }
320
321 $data[] = $val;
322 }
323
324 $this->getResult()->setIndexedTagName( $data, 'iw' );
325 return $this->getResult()->addValue( 'query', $property, $data );
326 }
327
328 protected function appendDbReplLagInfo( $property, $includeAll ) {
329 global $wgShowHostnames;
330 $data = array();
331 $lb = wfGetLB();
332 if ( $includeAll ) {
333 if ( !$wgShowHostnames ) {
334 $this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' );
335 }
336
337 $lags = $lb->getLagTimes();
338 foreach ( $lags as $i => $lag ) {
339 $data[] = array(
340 'host' => $lb->getServerName( $i ),
341 'lag' => $lag
342 );
343 }
344 } else {
345 list( $host, $lag, $index ) = $lb->getMaxLag();
346 $data[] = array(
347 'host' => $wgShowHostnames
348 ? $lb->getServerName( $index )
349 : '',
350 'lag' => intval( $lag )
351 );
352 }
353
354 $result = $this->getResult();
355 $result->setIndexedTagName( $data, 'db' );
356 return $this->getResult()->addValue( 'query', $property, $data );
357 }
358
359 protected function appendStatistics( $property ) {
360 global $wgDisableCounters;
361 $data = array();
362 $data['pages'] = intval( SiteStats::pages() );
363 $data['articles'] = intval( SiteStats::articles() );
364 if ( !$wgDisableCounters ) {
365 $data['views'] = intval( SiteStats::views() );
366 }
367 $data['edits'] = intval( SiteStats::edits() );
368 $data['images'] = intval( SiteStats::images() );
369 $data['users'] = intval( SiteStats::users() );
370 $data['activeusers'] = intval( SiteStats::activeUsers() );
371 $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
372 $data['jobs'] = intval( SiteStats::jobs() );
373 return $this->getResult()->addValue( 'query', $property, $data );
374 }
375
376 protected function appendUserGroups( $property, $numberInGroup ) {
377 global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
378
379 $data = array();
380 $result = $this->getResult();
381 foreach ( $wgGroupPermissions as $group => $permissions ) {
382 $arr = array(
383 'name' => $group,
384 'rights' => array_keys( $permissions, true ),
385 );
386
387 if ( $numberInGroup ) {
388 global $wgAutopromote;
389
390 if ( $group == 'user' ) {
391 $arr['number'] = SiteStats::users();
392
393 // '*' and autopromote groups have no size
394 } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) {
395 $arr['number'] = SiteStats::numberInGroup( $group );
396 }
397 }
398
399 $groupArr = array(
400 'add' => $wgAddGroups,
401 'remove' => $wgRemoveGroups,
402 'add-self' => $wgGroupsAddToSelf,
403 'remove-self' => $wgGroupsRemoveFromSelf
404 );
405
406 foreach ( $groupArr as $type => $rights ) {
407 if ( isset( $rights[$group] ) ) {
408 $arr[$type] = $rights[$group];
409 $result->setIndexedTagName( $arr[$type], 'group' );
410 }
411 }
412
413 $result->setIndexedTagName( $arr['rights'], 'permission' );
414 $data[] = $arr;
415 }
416
417 $result->setIndexedTagName( $data, 'group' );
418 return $result->addValue( 'query', $property, $data );
419 }
420
421 protected function appendFileExtensions( $property ) {
422 global $wgFileExtensions;
423
424 $data = array();
425 foreach ( $wgFileExtensions as $ext ) {
426 $data[] = array( 'ext' => $ext );
427 }
428 $this->getResult()->setIndexedTagName( $data, 'fe' );
429 return $this->getResult()->addValue( 'query', $property, $data );
430 }
431
432 protected function appendExtensions( $property ) {
433 global $wgExtensionCredits;
434 $data = array();
435 foreach ( $wgExtensionCredits as $type => $extensions ) {
436 foreach ( $extensions as $ext ) {
437 $ret = array();
438 $ret['type'] = $type;
439 if ( isset( $ext['name'] ) ) {
440 $ret['name'] = $ext['name'];
441 }
442 if ( isset( $ext['description'] ) ) {
443 $ret['description'] = $ext['description'];
444 }
445 if ( isset( $ext['descriptionmsg'] ) ) {
446 // Can be a string or array( key, param1, param2, ... )
447 if ( is_array( $ext['descriptionmsg'] ) ) {
448 $ret['descriptionmsg'] = $ext['descriptionmsg'][0];
449 $ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
450 $this->getResult()->setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
451 } else {
452 $ret['descriptionmsg'] = $ext['descriptionmsg'];
453 }
454 }
455 if ( isset( $ext['author'] ) ) {
456 $ret['author'] = is_array( $ext['author'] ) ?
457 implode( ', ', $ext['author' ] ) : $ext['author'];
458 }
459 if ( isset( $ext['url'] ) ) {
460 $ret['url'] = $ext['url'];
461 }
462 if ( isset( $ext['version'] ) ) {
463 $ret['version'] = $ext['version'];
464 } elseif ( isset( $ext['svn-revision'] ) &&
465 preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/',
466 $ext['svn-revision'], $m ) )
467 {
468 $ret['version'] = 'r' . $m[1];
469 }
470 $data[] = $ret;
471 }
472 }
473
474 $this->getResult()->setIndexedTagName( $data, 'ext' );
475 return $this->getResult()->addValue( 'query', $property, $data );
476 }
477
478 protected function appendRightsInfo( $property ) {
479 global $wgRightsPage, $wgRightsUrl, $wgRightsText;
480 $title = Title::newFromText( $wgRightsPage );
481 $url = $title ? wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ) : $wgRightsUrl;
482 $text = $wgRightsText;
483 if ( !$text && $title ) {
484 $text = $title->getPrefixedText();
485 }
486
487 $data = array(
488 'url' => $url ? $url : '',
489 'text' => $text ? $text : ''
490 );
491
492 return $this->getResult()->addValue( 'query', $property, $data );
493 }
494
495 public function appendLanguages( $property ) {
496 $params = $this->extractRequestParams();
497 $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
498 $langNames = Language::fetchLanguageNames( $langCode );
499
500 $data = array();
501
502 foreach ( $langNames as $code => $name ) {
503 $lang = array( 'code' => $code );
504 ApiResult::setContent( $lang, $name );
505 $data[] = $lang;
506 }
507 $this->getResult()->setIndexedTagName( $data, 'lang' );
508 return $this->getResult()->addValue( 'query', $property, $data );
509 }
510
511 public function appendSkins( $property ) {
512 $data = array();
513 foreach ( Skin::getSkinNames() as $name => $displayName ) {
514 $skin = array( 'code' => $name );
515 ApiResult::setContent( $skin, $displayName );
516 $data[] = $skin;
517 }
518 $this->getResult()->setIndexedTagName( $data, 'skin' );
519 return $this->getResult()->addValue( 'query', $property, $data );
520 }
521
522 public function appendExtensionTags( $property ) {
523 global $wgParser;
524 $wgParser->firstCallInit();
525 $tags = array_map( array( $this, 'formatParserTags'), $wgParser->getTags() );
526 $this->getResult()->setIndexedTagName( $tags, 't' );
527 return $this->getResult()->addValue( 'query', $property, $tags );
528 }
529
530 public function appendFunctionHooks( $property ) {
531 global $wgParser;
532 $wgParser->firstCallInit();
533 $hooks = $wgParser->getFunctionHooks();
534 $this->getResult()->setIndexedTagName( $hooks, 'h' );
535 return $this->getResult()->addValue( 'query', $property, $hooks );
536 }
537
538 public function appendVariables( $property ) {
539 $variables = MagicWord::getVariableIDs();
540 $this->getResult()->setIndexedTagName( $variables, 'v' );
541 return $this->getResult()->addValue( 'query', $property, $variables );
542 }
543
544 private function formatParserTags( $item ) {
545 return "<{$item}>";
546 }
547
548 public function appendSubscribedHooks( $property ) {
549 global $wgHooks;
550 $myWgHooks = $wgHooks;
551 ksort( $myWgHooks );
552
553 $data = array();
554 foreach ( $myWgHooks as $hook => $hooks ) {
555 $arr = array(
556 'name' => $hook,
557 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
558 );
559
560 $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
561 $data[] = $arr;
562 }
563
564 $this->getResult()->setIndexedTagName( $data, 'hook' );
565 return $this->getResult()->addValue( 'query', $property, $data );
566 }
567
568 public function getCacheMode( $params ) {
569 return 'public';
570 }
571
572 public function getAllowedParams() {
573 return array(
574 'prop' => array(
575 ApiBase::PARAM_DFLT => 'general',
576 ApiBase::PARAM_ISMULTI => true,
577 ApiBase::PARAM_TYPE => array(
578 'general',
579 'namespaces',
580 'namespacealiases',
581 'specialpagealiases',
582 'magicwords',
583 'interwikimap',
584 'dbrepllag',
585 'statistics',
586 'usergroups',
587 'extensions',
588 'fileextensions',
589 'rightsinfo',
590 'languages',
591 'skins',
592 'extensiontags',
593 'functionhooks',
594 'showhooks',
595 'variables',
596 )
597 ),
598 'filteriw' => array(
599 ApiBase::PARAM_TYPE => array(
600 'local',
601 '!local',
602 )
603 ),
604 'showalldb' => false,
605 'numberingroup' => false,
606 'inlanguagecode' => null,
607 );
608 }
609
610 public function getParamDescription() {
611 $p = $this->getModulePrefix();
612 return array(
613 'prop' => array(
614 'Which sysinfo properties to get:',
615 ' general - Overall system information',
616 ' namespaces - List of registered namespaces and their canonical names',
617 ' namespacealiases - List of registered namespace aliases',
618 ' specialpagealiases - List of special page aliases',
619 ' magicwords - List of magic words and their aliases',
620 ' statistics - Returns site statistics',
621 " interwikimap - Returns interwiki map (optionally filtered, (optionally localised by using {$p}inlanguagecode))",
622 ' dbrepllag - Returns database server with the highest replication lag',
623 ' usergroups - Returns user groups and the associated permissions',
624 ' extensions - Returns extensions installed on the wiki',
625 ' fileextensions - Returns list of file extensions allowed to be uploaded',
626 ' rightsinfo - Returns wiki rights (license) information if available',
627 " languages - Returns a list of languages MediaWiki supports (optionally localised by using {$p}inlanguagecode)",
628 ' skins - Returns a list of all enabled skins',
629 ' extensiontags - Returns a list of parser extension tags',
630 ' functionhooks - Returns a list of parser function hooks',
631 ' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)',
632 ' variables - Returns a list of variable IDs',
633 ),
634 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
635 'showalldb' => 'List all database servers, not just the one lagging the most',
636 'numberingroup' => 'Lists the number of users in user groups',
637 'inlanguagecode' => 'Language code for localised language names (best effort, use CLDR extension)',
638 );
639 }
640
641 public function getDescription() {
642 return 'Return general information about the site';
643 }
644
645 public function getPossibleErrors() {
646 return array_merge( parent::getPossibleErrors(), array(
647 array( 'code' => 'includeAllDenied', 'info' => 'Cannot view all servers info unless $wgShowHostnames is true' ),
648 ) );
649 }
650
651 public function getExamples() {
652 return array(
653 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics',
654 'api.php?action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local',
655 'api.php?action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb=',
656 );
657 }
658
659 public function getHelpUrls() {
660 return 'https://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si';
661 }
662
663 public function getVersion() {
664 return __CLASS__ . ': $Id$';
665 }
666 }