fix regression in r52516 for some distributions which are unable to show the version...
[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 function __construct(){
16 parent::__construct( 'Version' );
17 }
18
19 /**
20 * main()
21 */
22 function execute( $par ) {
23 global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks, $wgContLang;
24 $wgMessageCache->loadAllMessages();
25
26 $this->setHeaders();
27 $this->outputHeader();
28
29 if( $wgContLang->isRTL() ) {
30 $wgOut->addHTML( '<div dir="rtl">' );
31 } else {
32 $wgOut->addHTML( '<div dir="ltr">' );
33 }
34 $text =
35 $this->MediaWikiCredits() .
36 $this->softwareInformation() .
37 $this->extensionCredits();
38 if ( $wgSpecialVersionShowHooks ) {
39 $text .= $this->wgHooks();
40 }
41 $wgOut->addWikiText( $text );
42 $wgOut->addHTML( $this->IPInfo() );
43 $wgOut->addHTML( '</div>' );
44 }
45
46 /**
47 * execuate command for output
48 * @param string command
49 * @return string output
50 */
51 static function execOutput( $cmd ) {
52 $out = array( $cmd );
53 exec( $cmd.' 2>&1', $out );
54 unset($out[0]);
55 return implode("\n", $out );
56 }
57
58 /**#@+
59 * @private
60 */
61
62 /**
63 * @return wiki text showing the license information
64 */
65 static function MediaWikiCredits() {
66 global $wgContLang;
67
68 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) );
69
70 // This text is always left-to-right.
71 $ret .= '<div dir="ltr">';
72 $ret .= "__NOTOC__
73 This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''',
74 copyright © 2001-2009 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
75 Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason,
76 Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor,
77 Aaron Schulz and others.
78
79 MediaWiki is free software; you can redistribute it and/or modify
80 it under the terms of the GNU General Public License as published by
81 the Free Software Foundation; either version 2 of the License, or
82 (at your option) any later version.
83
84 MediaWiki is distributed in the hope that it will be useful,
85 but WITHOUT ANY WARRANTY; without even the implied warranty of
86 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
87 GNU General Public License for more details.
88
89 You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU General Public License]
90 along with this program; if not, write to the Free Software
91 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
92 or [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html read it online].
93 ";
94 $ret .= '</div>';
95
96 return str_replace( "\t\t", '', $ret ) . "\n";
97 }
98
99 /**
100 * @return wiki text showing the third party software versions (apache, php, mysql).
101 */
102 static function softwareInformation() {
103 global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgDiff3, $wgDiff;
104 global $wgAllowTitlesInSVG, $wgSVGConverter, $wgSVGConverters, $wgSVGConverterPath;
105 $dbr = wfGetDB( DB_SLAVE );
106
107 // Put the software in an array of form 'name' => 'version'. All messages should
108 // be loaded here, so feel free to use wfMsg*() in the 'name'. Raw HTML or wikimarkup
109 // can be used
110 $software = array();
111 $software['[http://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
112 $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")";
113 $software[$dbr->getSoftwareLink()] = $dbr->getServerVersion();
114
115 // Version information for diff3
116 if ( file_exists( trim( $wgDiff3, '"' ) ) ) {
117 $swDiff3Info = self::execOutput( $wgDiff3 . ' -v' );
118 $swDiff3Line = explode("\n",$swDiff3Info ,2);
119 $swDiff3Ver = $swDiff3Line[0];
120 $swDiff3Ver = str_replace( 'diff3 (GNU diffutils) ', '' , $swDiff3Ver);
121 $software['[http://www.gnu.org/software/diffutils/diffutils.html diff3]'] = $swDiff3Ver;
122 }
123
124 // Version information for diff
125 if ( file_exists( trim( $wgDiff, '"' ) ) ) {
126 $swDiffInfo = self::execOutput( $wgDiff . ' -v' );
127 $swDiffLine = explode("\n",$swDiffInfo ,2);
128 $swDiffVer = $swDiffLine[0];
129 $swDiffVer = str_replace( 'diff (GNU diffutils) ', '' , $swDiffVer);
130 $software['[http://www.gnu.org/software/diffutils/diffutils.html diff]'] = $swDiffVer;
131 }
132
133 // Look for ImageMagick's version, if did not found, try to find the GD library version
134 if ( $wgUseImageMagick ) {
135 if ( file_exists( trim( $wgImageMagickConvertCommand, '"' ) ) ) {
136 $swImageMagickInfo = self::execOutput( $wgImageMagickConvertCommand . ' -version' );
137 list( $head, $tail ) = explode( 'ImageMagick', $swImageMagickInfo );
138 list( $swImageMagickVer ) = explode('http://www.imagemagick.org', $tail );
139 $software['[http://www.imagemagick.org/ ImageMagick]'] = $swImageMagickVer;
140 }
141 } else {
142 if( function_exists( 'gd_info' ) ) {
143 $gdInfo = gd_info();
144 if ( strstr( $gdInfo['GD Version'], 'bundled' ) != false ) {
145 $gd_URL = 'http://www.php.net/gd';
146 }
147 else {
148 $gd_URL = 'http://www.libgd.org';
149 }
150 $software['[' . $gd_URL . ' GD library]'] = $gdInfo['GD Version'];
151 }
152 }
153
154 // Look for SVG converter and print the version info
155 if ( $wgAllowTitlesInSVG ) {
156 $swSVGConvName = $wgSVGConverter;
157 $haveSVGConvVer = false;
158 $pathVar = '$path/';
159 $binPath = '/usr/bin/';
160 $execPath = strtok(strstr($wgSVGConverters[$wgSVGConverter],$pathVar), ' ');
161 $execPath = substr_replace($execPath, '', 0, strlen($pathVar));
162 $execFullPath = trim($wgSVGConverterPath,'"') . $execPath;
163 $execBinPath = $binPath . $execPath;
164 if (strstr($execFullPath, ' ') != false) {
165 $execFullPath = '"' . $execFullPath . '"';
166 }
167 if ( !strcmp( $wgSVGConverter, 'ImageMagick') ) {
168 // Get version info for ImageMagick
169 if ( ( file_exists( $execBinPath ) ) || file_exists( trim( $execFullPath, '"' ) ) || ( file_exists( trim( $execFullPath, '"' ) . '.exe' ) ) )
170 $swSVGConvInfo = self::execOutput( $execFullPath . ' -version' );
171 list( $head, $tail ) = explode( 'ImageMagick', $swSVGConvInfo );
172 list( $swSVGConvVer ) = explode('http://www.imagemagick.org', $tail );
173 $swSVGConvURL = 'http://www.imagemagick.org/';
174 $haveSVGConvVer = true;
175 } else if ( strstr ($execFullPath, 'rsvg') != false ) {
176 // Get version info for rsvg
177 if ( ( file_exists( $execBinPath ) ) || file_exists( trim( $execFullPath, '"' ) ) || ( file_exists( trim( $execFullPath, '"' ) . '.exe' ) ) )
178 $swSVGConvInfo = self::execOutput( $execFullPath . ' -v' );
179 $swSVGConvLine = explode("\n",$swSVGConvInfo ,2);
180 $swSVGConvVer = $swSVGConvLine[0];
181 $swSVGConvURL = 'http://librsvg.sourceforge.net/';
182 $haveSVGConvVer = true;
183 } else if ( strstr ($execFullPath, 'inkscape') != false ) {
184 // Get version info for Inkscape
185 if ( ( file_exists( $execBinPath ) ) || file_exists( trim( $execFullPath, '"' ) ) || ( file_exists( trim( $execFullPath, '"' ) . '.exe' ) ) )
186 $swSVGConvInfo = self::execOutput( $execFullPath . ' -z -V' );
187 $swSVGConvLine = explode("\n",$swSVGConvInfo ,2);
188 $swSVGConvVer = ltrim( $swSVGConvLine[0], 'Inkscape ' );
189 $swSVGConvURL = 'http://www.inkscape.org/';
190 $swSVGConvName = ucfirst( $wgSVGConverter );
191 $haveSVGConvVer = true;
192 }
193 if ( $haveSVGConvVer )
194 $software["[$swSVGConvURL $swSVGConvName]"] = $swSVGConvVer;
195 }
196
197 // Allow a hook to add/remove items
198 wfRunHooks( 'SoftwareInfo', array( &$software ) );
199
200 $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) .
201 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-software' ) ) .
202 "<tr>
203 <th>" . wfMsg( 'version-software-product' ) . "</th>
204 <th>" . wfMsg( 'version-software-version' ) . "</th>
205 </tr>\n";
206 foreach( $software as $name => $version ) {
207 $out .= "<tr>
208 <td>" . $name . "</td>
209 <td>" . $version . "</td>
210 </tr>\n";
211 }
212 return $out . Xml::closeElement( 'table' );
213 }
214
215 /**
216 * Return a string of the MediaWiki version with SVN revision if available
217 *
218 * @return mixed
219 */
220 public static function getVersion( $flags = '' ) {
221 global $wgVersion, $IP;
222 wfProfileIn( __METHOD__ );
223 $svn = self::getSvnRevision( $IP, false, false , false );
224 $svnCo = self::getSvnRevision( $IP, true, false , false );
225 if ( !$svn ) {
226 $version = $wgVersion;
227 } elseif( $flags === 'nodb' ) {
228 $version = "$wgVersion (r$svnCo)";
229 } else {
230 $version = $wgVersion . wfMsg( 'version-svn-revision', $svn, $svnCo );
231 }
232 wfProfileOut( __METHOD__ );
233 return $version;
234 }
235
236 /**
237 * Return a string of the MediaWiki version with a link to SVN revision if
238 * available
239 *
240 * @return mixed
241 */
242 public static function getVersionLinked() {
243 global $wgVersion, $IP;
244 wfProfileIn( __METHOD__ );
245 $svn = self::getSvnRevision( $IP, false, false, false );
246 $svnCo = self::getSvnRevision( $IP, true, false, false );
247 $svnDir = self::getSvnRevision( $IP, true, false, true );
248 $viewvcStart = 'http://svn.wikimedia.org/viewvc/mediawiki/';
249 $viewvcEnd = '/?pathrev=';
250 $viewvc = $viewvcStart . $svnDir . $viewvcEnd;
251 $version = $svn ? $wgVersion . " [{$viewvc}{$svnCo} " . wfMsg( 'version-svn-revision', $svn, $svnCo ) . ']' : $wgVersion;
252 wfProfileOut( __METHOD__ );
253 return $version;
254 }
255
256 /** Generate wikitext showing extensions name, URL, author and description */
257 function extensionCredits() {
258 global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions;
259
260 if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) )
261 return '';
262
263 $extensionTypes = array(
264 'specialpage' => wfMsg( 'version-specialpages' ),
265 'parserhook' => wfMsg( 'version-parserhooks' ),
266 'variable' => wfMsg( 'version-variables' ),
267 'media' => wfMsg( 'version-mediahandlers' ),
268 'other' => wfMsg( 'version-other' ),
269 );
270 wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
271
272 $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) .
273 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-ext' ) );
274
275 foreach ( $extensionTypes as $type => $text ) {
276 if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) {
277 $out .= $this->openExtType( $text );
278
279 usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
280
281 foreach ( $wgExtensionCredits[$type] as $extension ) {
282 $version = null;
283 $subVersion = null;
284 $subVersionCo = null;
285 $viewvc = null;
286 if ( isset( $extension['path'] ) ) {
287 $subVersion = self::getSvnRevision(dirname($extension['path']), false, true, false);
288 $subVersionCo = self::getSvnRevision(dirname($extension['path']), true, true, false);
289 $subVersionDir = self::getSvnRevision(dirname($extension['path']), false, true, true);
290 if ($subVersionDir)
291 $viewvc = $subVersionDir . $subVersionCo;
292 }
293 if ( isset( $extension['version'] ) ) {
294 $version = $extension['version'];
295 }
296
297 $out .= $this->formatCredits(
298 isset ( $extension['name'] ) ? $extension['name'] : '',
299 $version,
300 $subVersion,
301 $subVersionCo,
302 $viewvc,
303 isset ( $extension['author'] ) ? $extension['author'] : '',
304 isset ( $extension['url'] ) ? $extension['url'] : null,
305 isset ( $extension['description'] ) ? $extension['description'] : '',
306 isset ( $extension['descriptionmsg'] ) ? $extension['descriptionmsg'] : ''
307 );
308 }
309 }
310 }
311
312 if ( count( $wgExtensionFunctions ) ) {
313 $out .= $this->openExtType( wfMsg( 'version-extension-functions' ) );
314 $out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
315 }
316
317 if ( $cnt = count( $tags = $wgParser->getTags() ) ) {
318 for ( $i = 0; $i < $cnt; ++$i )
319 $tags[$i] = "&lt;{$tags[$i]}&gt;";
320 $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ) );
321 $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
322 }
323
324 if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
325 $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ) );
326 $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
327 }
328
329 if ( count( $wgSkinExtensionFunctions ) ) {
330 $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ) );
331 $out .= '<tr><td colspan="4">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n";
332 }
333 $out .= Xml::closeElement( 'table' );
334 return $out;
335 }
336
337 /** Callback to sort extensions by type */
338 function compare( $a, $b ) {
339 global $wgLang;
340 if( $a['name'] === $b['name'] ) {
341 return 0;
342 } else {
343 return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] )
344 ? 1
345 : -1;
346 }
347 }
348
349 function formatCredits( $name, $version = null, $subVersion = null, $subVersionCo = null, $subVersionURL = null, $author = null, $url = null, $description = null, $descriptionMsg = null ) {
350 $haveSubversion = $subVersion;
351 $extension = isset( $url ) ? "[$url $name]" : $name;
352 $version = isset( $version ) ? wfMsg( 'version-version', $version ) : '';
353 $subVersion = isset( $subVersion ) ? wfMsg( 'version-svn-revision', $subVersion, $subVersionCo ) : '';
354 $subVersion = isset( $subVersionURL ) ? "[$subVersionURL $subVersion]" : $subVersion;
355
356 # Look for a localized description
357 if( isset( $descriptionMsg ) ) {
358 $msg = wfMsg( $descriptionMsg );
359 if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
360 $description = $msg;
361 }
362 }
363
364 if ( $haveSubversion ) {
365 $extNameVer = "<tr>
366 <td><em>$extension $version</em></td>
367 <td><em>$subVersion</em></td>";
368 } else {
369 $extNameVer = "<tr>
370 <td colspan=\"2\"><em>$extension $version</em></td>";
371 }
372 $extDescAuthor = "<td>$description</td>
373 <td>" . $this->listToText( (array)$author ) . "</td>
374 </tr>\n";
375 return $ret = $extNameVer . $extDescAuthor;
376 return $ret;
377 }
378
379 /**
380 * @return string
381 */
382 function wgHooks() {
383 global $wgHooks;
384
385 if ( count( $wgHooks ) ) {
386 $myWgHooks = $wgHooks;
387 ksort( $myWgHooks );
388
389 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) .
390 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
391 "<tr>
392 <th>" . wfMsg( 'version-hook-name' ) . "</th>
393 <th>" . wfMsg( 'version-hook-subscribedby' ) . "</th>
394 </tr>\n";
395
396 foreach ( $myWgHooks as $hook => $hooks )
397 $ret .= "<tr>
398 <td>$hook</td>
399 <td>" . $this->listToText( $hooks ) . "</td>
400 </tr>\n";
401
402 $ret .= Xml::closeElement( 'table' );
403 return $ret;
404 } else
405 return '';
406 }
407
408 private function openExtType($text, $name = null) {
409 $opt = array( 'colspan' => 4 );
410 $out = '';
411
412 if(!$this->firstExtOpened) {
413 // Insert a spacing line
414 $out .= '<tr class="sv-space">' . Xml::element( 'td', $opt ) . "</tr>\n";
415 }
416 $this->firstExtOpened = false;
417
418 if($name) { $opt['id'] = "sv-$name"; }
419
420 $out .= "<tr>" . Xml::element( 'th', $opt, $text) . "</tr>\n";
421 return $out;
422 }
423
424 /**
425 * @return string
426 */
427 function IPInfo() {
428 $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) );
429 return "<!-- visited from $ip -->\n" .
430 "<span style='display:none'>visited from $ip</span>";
431 }
432
433 /**
434 * @param array $list
435 * @return string
436 */
437 function listToText( $list ) {
438 $cnt = count( $list );
439
440 if ( $cnt == 1 ) {
441 // Enforce always returning a string
442 return (string)self::arrayToString( $list[0] );
443 } elseif ( $cnt == 0 ) {
444 return '';
445 } else {
446 global $wgLang;
447 sort( $list );
448 return $wgLang->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
449 }
450 }
451
452 /**
453 * @param mixed $list Will convert an array to string if given and return
454 * the paramater unaltered otherwise
455 * @return mixed
456 */
457 static function arrayToString( $list ) {
458 if( is_array( $list ) && count( $list ) == 1 )
459 $list = $list[0];
460 if( is_object( $list ) ) {
461 $class = get_class( $list );
462 return "($class)";
463 } elseif ( !is_array( $list ) ) {
464 return $list;
465 } else {
466 if( is_object( $list[0] ) )
467 $class = get_class( $list[0] );
468 else
469 $class = $list[0];
470 return "($class, {$list[1]})";
471 }
472 }
473
474 /**
475 * Retrieve the revision number of a Subversion working directory.
476 *
477 * @param String $dir Directory of the svn checkout
478 * @param Boolean $coRev optional to return value whether is Last Modified
479 * or Checkout revision number
480 * @param Boolean $extension optional to check the path whether is from
481 * Wikimedia SVN server or not
482 * @param Boolean $relPath optional to get the end part of the checkout path
483 * @return mixed revision number as int, end part of the checkout path,
484 * or false if not a SVN checkout
485 */
486 public static function getSvnRevision( $dir, $coRev = false, $extension = false, $relPath = false) {
487 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
488 $entries = $dir . '/.svn/entries';
489
490 if( !file_exists( $entries ) ) {
491 return false;
492 }
493
494 $content = file( $entries );
495
496 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
497 if( preg_match( '/^<\?xml/', $content[0] ) ) {
498 // subversion is release <= 1.3
499 if( !function_exists( 'simplexml_load_file' ) ) {
500 // We could fall back to expat... YUCK
501 return false;
502 }
503
504 // SimpleXml whines about the xmlns...
505 wfSuppressWarnings();
506 $xml = simplexml_load_file( $entries );
507 wfRestoreWarnings();
508
509 if( $xml ) {
510 foreach( $xml->entry as $entry ) {
511 if( $xml->entry[0]['name'] == '' ) {
512 // The directory entry should always have a revision marker.
513 if( $entry['revision'] ) {
514 return intval( $entry['revision'] );
515 }
516 }
517 }
518 }
519 return false;
520 } else {
521 // subversion is release 1.4 or above
522 if ($relPath) {
523 $endPath = strstr( $content[4], 'tags' );
524 if (!$endPath) {
525 $endPath = strstr( $content[4], 'branches' );
526 if (!$endPath) {
527 $endPath = strstr( $content[4], 'trunk' );
528 if (!$endPath)
529 return false;
530 }
531 }
532 $endPath = trim ( $endPath );
533 if ($extension) {
534 $wmSvnPath = 'svn.wikimedia.org/svnroot/mediawiki';
535 $isWMSvn = strstr($content[5],$wmSvnPath);
536 if (!strcmp($isWMSvn,null)) {
537 return false;
538 } else {
539 $viewvcStart = 'http://svn.wikimedia.org/viewvc/mediawiki/';
540 if (strstr( $content[4], 'trunk' ))
541 $viewvcEnd = '/?pathrev=';
542 else
543 // Avoids 404 error using pathrev when it does not found
544 $viewvcEnd = '/?revision=';
545 $viewvc = $viewvcStart . $endPath . $viewvcEnd;
546 return $viewvc;
547 }
548 }
549 return $endPath;
550 }
551 if ($coRev)
552 // get the directory checkout revsion number
553 return intval( $content[3]) ;
554 else
555 // get the directory last modified revision number
556 return intval( $content[10] );
557 }
558 }
559
560 /**#@-*/
561 }