Use Html for HTML, not Xml
[lhc/web/wiklou.git] / includes / specials / SpecialVersion.php
1 <?php
2
3 /**
4 * Give information about the version of MediaWiki, PHP, the DB and extensions
5 *
6 * @ingroup SpecialPage
7 *
8 * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
9 * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
10 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
11 */
12 class SpecialVersion extends SpecialPage {
13 private $firstExtOpened = true;
14
15 static $viewvcUrls = array(
16 'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
17 'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
18 # Doesn't work at the time of writing but maybe some day:
19 'https://svn.wikimedia.org/viewvc/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
20 );
21
22 function __construct(){
23 parent::__construct( 'Version' );
24 }
25
26 /**
27 * main()
28 */
29 function execute( $par ) {
30 global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks, $wgContLang;
31 $wgMessageCache->loadAllMessages();
32
33 $this->setHeaders();
34 $this->outputHeader();
35
36 $wgOut->addHTML( Xml::openElement( 'div',
37 array( 'dir' => $wgContLang->getDir() ) ) );
38 $text =
39 $this->MediaWikiCredits() .
40 $this->softwareInformation() .
41 $this->extensionCredits();
42 if ( $wgSpecialVersionShowHooks ) {
43 $text .= $this->wgHooks();
44 }
45 $wgOut->addWikiText( $text );
46 $wgOut->addHTML( $this->IPInfo() );
47 $wgOut->addHTML( '</div>' );
48 }
49
50 /**#@+
51 * @private
52 */
53
54 /**
55 * @return wiki text showing the license information
56 */
57 static function MediaWikiCredits() {
58 global $wgContLang;
59
60 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) );
61
62 // This text is always left-to-right.
63 $ret .= '<div dir="ltr">';
64 $ret .= "__NOTOC__
65 This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''',
66 copyright © 2001-2010 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
67 Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason,
68 Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor,
69 Aaron Schulz, Andrew Garrett, Raimond Spekking, Alexandre Emsenhuber,
70 Siebrand Mazeland, Chad Horohoe and others.
71
72 MediaWiki is free software; you can redistribute it and/or modify
73 it under the terms of the GNU General Public License as published by
74 the Free Software Foundation; either version 2 of the License, or
75 (at your option) any later version.
76
77 MediaWiki is distributed in the hope that it will be useful,
78 but WITHOUT ANY WARRANTY; without even the implied warranty of
79 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
80 GNU General Public License for more details.
81
82 You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU General Public License]
83 along with this program; if not, write to the Free Software
84 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
85 or [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html read it online].
86 ";
87 $ret .= '</div>';
88
89 return str_replace( "\t\t", '', $ret ) . "\n";
90 }
91
92 /**
93 * @return wiki text showing the third party software versions (apache, php, mysql).
94 */
95 static function softwareInformation() {
96 $dbr = wfGetDB( DB_SLAVE );
97
98 // Put the software in an array of form 'name' => 'version'. All messages should
99 // be loaded here, so feel free to use wfMsg*() in the 'name'. Raw HTML or wikimarkup
100 // can be used
101 $software = array();
102 $software['[http://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
103 $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")";
104 $software[$dbr->getSoftwareLink()] = $dbr->getServerVersion();
105
106 // Allow a hook to add/remove items
107 wfRunHooks( 'SoftwareInfo', array( &$software ) );
108
109 $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) .
110 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-software' ) ) .
111 "<tr>
112 <th>" . wfMsg( 'version-software-product' ) . "</th>
113 <th>" . wfMsg( 'version-software-version' ) . "</th>
114 </tr>\n";
115 foreach( $software as $name => $version ) {
116 $out .= "<tr>
117 <td>" . $name . "</td>
118 <td>" . $version . "</td>
119 </tr>\n";
120 }
121 return $out . Xml::closeElement( 'table' );
122 }
123
124 /**
125 * Return a string of the MediaWiki version with SVN revision if available
126 *
127 * @return mixed
128 */
129 public static function getVersion( $flags = '' ) {
130 global $wgVersion, $IP;
131 wfProfileIn( __METHOD__ );
132
133 $info = self::getSvnInfo( $IP );
134 if ( !$info ) {
135 $version = $wgVersion;
136 } elseif( $flags === 'nodb' ) {
137 $version = "$wgVersion (r{$info['checkout-rev']})";
138 } else {
139 $version = $wgVersion . ' ' .
140 wfMsg(
141 'version-svn-revision',
142 isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
143 $info['checkout-rev']
144 );
145 }
146
147 wfProfileOut( __METHOD__ );
148 return $version;
149 }
150
151 /**
152 * Return a wikitext-formatted string of the MediaWiki version with a link to
153 * the SVN revision if available
154 *
155 * @return mixed
156 */
157 public static function getVersionLinked() {
158 global $wgVersion, $IP;
159 wfProfileIn( __METHOD__ );
160 $info = self::getSvnInfo( $IP );
161 if ( isset( $info['checkout-rev'] ) ) {
162 $linkText = wfMsg(
163 'version-svn-revision',
164 isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
165 $info['checkout-rev']
166 );
167 if ( isset( $info['viewvc-url'] ) ) {
168 $version = "$wgVersion [{$info['viewvc-url']} $linkText]";
169 } else {
170 $version = "$wgVersion $linkText";
171 }
172 } else {
173 $version = $wgVersion;
174 }
175 wfProfileOut( __METHOD__ );
176 return $version;
177 }
178
179 /**
180 * Generate wikitext showing extensions name, URL, author and description
181 *
182 * @return String: Wikitext
183 */
184 function extensionCredits() {
185 global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions;
186
187 if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) )
188 return '';
189
190 $extensionTypes = array(
191 'specialpage' => wfMsg( 'version-specialpages' ),
192 'parserhook' => wfMsg( 'version-parserhooks' ),
193 'variable' => wfMsg( 'version-variables' ),
194 'media' => wfMsg( 'version-mediahandlers' ),
195 'other' => wfMsg( 'version-other' ),
196 );
197 wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
198
199 $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) .
200 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-ext' ) );
201
202 foreach ( $extensionTypes as $type => $text ) {
203 if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) {
204 $out .= $this->openExtType( $text, 'credits-' . $type );
205
206 usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
207
208 foreach ( $wgExtensionCredits[$type] as $extension ) {
209 $out .= $this->formatCredits( $extension );
210 }
211 }
212 }
213
214 if ( count( $wgExtensionFunctions ) ) {
215 $out .= $this->openExtType( wfMsg( 'version-extension-functions' ), 'extension-functions' );
216 $out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
217 }
218
219 if ( $cnt = count( $tags = $wgParser->getTags() ) ) {
220 for ( $i = 0; $i < $cnt; ++$i )
221 $tags[$i] = "&lt;{$tags[$i]}&gt;";
222 $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ), 'parser-tags' );
223 $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
224 }
225
226 if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
227 $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ), 'parser-function-hooks' );
228 $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
229 }
230
231 if ( count( $wgSkinExtensionFunctions ) ) {
232 $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ), 'skin-extension-functions' );
233 $out .= '<tr><td colspan="4">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n";
234 }
235 $out .= Xml::closeElement( 'table' );
236 return $out;
237 }
238
239 /** Callback to sort extensions by type */
240 function compare( $a, $b ) {
241 global $wgLang;
242 if( $a['name'] === $b['name'] ) {
243 return 0;
244 } else {
245 return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] )
246 ? 1
247 : -1;
248 }
249 }
250
251 function formatCredits( $extension ) {
252 $name = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
253 if ( isset( $extension['path'] ) ) {
254 $svnInfo = self::getSvnInfo( dirname($extension['path']) );
255 $directoryRev = isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : null;
256 $checkoutRev = isset( $svnInfo['checkout-rev'] ) ? $svnInfo['checkout-rev'] : null;
257 $viewvcUrl = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : null;
258 } else {
259 $directoryRev = null;
260 $checkoutRev = null;
261 $viewvcUrl = null;
262 }
263
264 # Make main link (or just the name if there is no URL)
265 if ( isset( $extension['url'] ) ) {
266 $mainLink = "[{$extension['url']} $name]";
267 } else {
268 $mainLink = $name;
269 }
270 if ( isset( $extension['version'] ) ) {
271 $versionText = '<span class="mw-version-ext-version">' .
272 wfMsg( 'version-version', $extension['version'] ) .
273 '</span>';
274 } else {
275 $versionText = '';
276 }
277
278 # Make subversion text/link
279 if ( $checkoutRev ) {
280 $svnText = wfMsg( 'version-svn-revision', $directoryRev, $checkoutRev );
281 $svnText = isset( $viewvcUrl ) ? "[$viewvcUrl $svnText]" : $svnText;
282 } else {
283 $svnText = false;
284 }
285
286 # Make description text
287 $description = isset ( $extension['description'] ) ? $extension['description'] : '';
288 if( isset ( $extension['descriptionmsg'] ) ) {
289 # Look for a localized description
290 $descriptionMsg = $extension['descriptionmsg'];
291 if( is_array( $descriptionMsg ) ) {
292 $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
293 array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
294 array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
295 $msg = wfMsg( $descriptionMsgKey, $descriptionMsg );
296 } else {
297 $msg = wfMsg( $descriptionMsg );
298 }
299 if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
300 $description = $msg;
301 }
302 }
303
304 if ( $svnText !== false ) {
305 $extNameVer = "<tr>
306 <td><em>$mainLink $versionText</em></td>
307 <td><em>$svnText</em></td>";
308 } else {
309 $extNameVer = "<tr>
310 <td colspan=\"2\"><em>$mainLink $versionText</em></td>";
311 }
312 $author = isset ( $extension['author'] ) ? $extension['author'] : array();
313 $extDescAuthor = "<td>$description</td>
314 <td>" . $this->listToText( (array)$author, false ) . "</td>
315 </tr>\n";
316 return $extNameVer . $extDescAuthor;
317 }
318
319 /**
320 * Generate wikitext showing hooks in $wgHooks
321 *
322 * @return String: wikitext
323 */
324 function wgHooks() {
325 global $wgHooks;
326
327 if ( count( $wgHooks ) ) {
328 $myWgHooks = $wgHooks;
329 ksort( $myWgHooks );
330
331 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) .
332 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
333 "<tr>
334 <th>" . wfMsg( 'version-hook-name' ) . "</th>
335 <th>" . wfMsg( 'version-hook-subscribedby' ) . "</th>
336 </tr>\n";
337
338 foreach ( $myWgHooks as $hook => $hooks )
339 $ret .= "<tr>
340 <td>$hook</td>
341 <td>" . $this->listToText( $hooks ) . "</td>
342 </tr>\n";
343
344 $ret .= Xml::closeElement( 'table' );
345 return $ret;
346 } else
347 return '';
348 }
349
350 private function openExtType( $text, $name = null ) {
351 $opt = array( 'colspan' => 4 );
352 $out = '';
353
354 if( !$this->firstExtOpened ) {
355 // Insert a spacing line
356 $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n";
357 }
358 $this->firstExtOpened = false;
359
360 if( $name )
361 $opt['id'] = "sv-$name";
362
363 $out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n";
364 return $out;
365 }
366
367 /**
368 * Get information about client's IP address
369 *
370 * @return String: HTML fragment
371 */
372 function IPInfo() {
373 $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) );
374 return "<!-- visited from $ip -->\n" .
375 "<span style='display:none'>visited from $ip</span>";
376 }
377
378 /**
379 * Convert an array of items into a list for display
380 *
381 * @param $list Array of elements to display
382 * @param $sort Boolean: whether to sort the items in $list
383 * @return String
384 */
385 function listToText( $list, $sort = true ) {
386 $cnt = count( $list );
387
388 if ( $cnt == 1 ) {
389 // Enforce always returning a string
390 return (string)self::arrayToString( $list[0] );
391 } elseif ( $cnt == 0 ) {
392 return '';
393 } else {
394 global $wgLang;
395 if ( $sort ) {
396 sort( $list );
397 }
398 return $wgLang->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
399 }
400 }
401
402 /**
403 * Convert an array or object to a string for display
404 *
405 * @param $list Mixed: will convert an array to string if given and return
406 * the paramater unaltered otherwise
407 * @return Mixed
408 */
409 static function arrayToString( $list ) {
410 if( is_array( $list ) && count( $list ) == 1 )
411 $list = $list[0];
412 if( is_object( $list ) ) {
413 $class = get_class( $list );
414 return "($class)";
415 } elseif ( !is_array( $list ) ) {
416 return $list;
417 } else {
418 if( is_object( $list[0] ) )
419 $class = get_class( $list[0] );
420 else
421 $class = $list[0];
422 return "($class, {$list[1]})";
423 }
424 }
425
426 /**
427 * Get an associative array of information about a given path, from its .svn
428 * subdirectory. Returns false on error, such as if the directory was not
429 * checked out with subversion.
430 *
431 * Returned keys are:
432 * Required:
433 * checkout-rev The revision which was checked out
434 * Optional:
435 * directory-rev The revision when the directory was last modified
436 * url The subversion URL of the directory
437 * repo-url The base URL of the repository
438 * viewvc-url A ViewVC URL pointing to the checked-out revision
439 */
440 public static function getSvnInfo( $dir ) {
441 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
442 $entries = $dir . '/.svn/entries';
443
444 if( !file_exists( $entries ) ) {
445 return false;
446 }
447
448 $lines = file( $entries );
449 if ( !count( $lines ) ) {
450 return false;
451 }
452
453 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
454 if( preg_match( '/^<\?xml/', $lines[0] ) ) {
455 // subversion is release <= 1.3
456 if( !function_exists( 'simplexml_load_file' ) ) {
457 // We could fall back to expat... YUCK
458 return false;
459 }
460
461 // SimpleXml whines about the xmlns...
462 wfSuppressWarnings();
463 $xml = simplexml_load_file( $entries );
464 wfRestoreWarnings();
465
466 if( $xml ) {
467 foreach( $xml->entry as $entry ) {
468 if( $xml->entry[0]['name'] == '' ) {
469 // The directory entry should always have a revision marker.
470 if( $entry['revision'] ) {
471 return array( 'checkout-rev' => intval( $entry['revision'] ) );
472 }
473 }
474 }
475 }
476 return false;
477 }
478
479 // subversion is release 1.4 or above
480 if ( count( $lines ) < 11 ) {
481 return false;
482 }
483 $info = array(
484 'checkout-rev' => intval( trim( $lines[3] ) ),
485 'url' => trim( $lines[4] ),
486 'repo-url' => trim( $lines[5] ),
487 'directory-rev' => intval( trim( $lines[10] ) )
488 );
489 if ( isset( self::$viewvcUrls[$info['repo-url']] ) ) {
490 $viewvc = str_replace(
491 $info['repo-url'],
492 self::$viewvcUrls[$info['repo-url']],
493 $info['url']
494 );
495 $pathRelativeToRepo = substr( $info['url'], strlen( $info['repo-url'] ) );
496 $viewvc .= '/?pathrev=';
497 $viewvc .= urlencode( $info['checkout-rev'] );
498 $info['viewvc-url'] = $viewvc;
499 }
500 return $info;
501 }
502
503 /**
504 * Retrieve the revision number of a Subversion working directory.
505 *
506 * @param $dir String: directory of the svn checkout
507 * @return Integer: revision number as int
508 */
509 public static function getSvnRevision( $dir ) {
510 $info = self::getSvnInfo( $dir );
511 if ( $info === false ) {
512 return false;
513 } elseif ( isset( $info['checkout-rev'] ) ) {
514 return $info['checkout-rev'];
515 } else {
516 return false;
517 }
518 }
519
520 /**#@-*/
521 }