4 * Give information about the version of MediaWiki, PHP, the DB and extensions
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
12 class SpecialVersion
extends SpecialPage
{
13 private $firstExtOpened = true;
15 function __construct(){
16 parent
::__construct( 'Version' );
22 function execute( $par ) {
23 global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks, $wgContLang;
24 $wgMessageCache->loadAllMessages();
27 $this->outputHeader();
29 if( $wgContLang->isRTL() ) {
30 $wgOut->addHTML( '<div dir="rtl">' );
32 $wgOut->addHTML( '<div dir="ltr">' );
35 $this->MediaWikiCredits() .
36 $this->softwareInformation() .
37 $this->extensionCredits();
38 if ( $wgSpecialVersionShowHooks ) {
39 $text .= $this->wgHooks();
41 $wgOut->addWikiText( $text );
42 $wgOut->addHTML( $this->IPInfo() );
43 $wgOut->addHTML( '</div>' );
47 * execuate command for output
48 * @param string command
49 * @return string output
51 static function execOutput( $cmd ) {
53 exec( $cmd.' 2>&1', $out );
55 return implode("\n", $out );
63 * @return wiki text showing the license information
65 static function MediaWikiCredits() {
68 $ret = Xml
::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) );
70 // This text is always left-to-right.
71 $ret .= '<div dir="ltr">';
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.
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.
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.
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].
96 return str_replace( "\t\t", '', $ret ) . "\n";
100 * @return wiki text showing the third party software versions (apache, php, mysql).
102 static function softwareInformation() {
103 global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgDiff3, $wgDiff, $wgUseTeX;
104 global $wgAllowTitlesInSVG, $wgSVGConverter, $wgSVGConverters, $wgSVGConverterPath;
105 $dbr = wfGetDB( DB_SLAVE
);
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
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();
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;
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;
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;
142 if( function_exists( 'gd_info' ) ) {
144 if ( strstr( $gdInfo['GD Version'], 'bundled' ) != false ) {
145 $gd_URL = 'http://www.php.net/gd';
148 $gd_URL = 'http://www.libgd.org';
150 $software['[' . $gd_URL . ' GD library]'] = $gdInfo['GD Version'];
154 // Look for SVG converter and print the version info
155 if ( $wgAllowTitlesInSVG ) {
156 $swSVGConvName = $wgSVGConverter;
157 $haveSVGConvVer = false;
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 . '"';
167 if ( !strcmp( $wgSVGConverter, 'ImageMagick') ) {
168 // Get version info for ImageMagick
169 if ( file_exists( $execBinPath ) )
170 $swSVGConvInfo = self
::execOutput( $execBinPath . ' -version' );
171 else if ( file_exists( trim( $execFullPath, '"' ) ) ||
( file_exists( trim( $execFullPath, '"' ) . '.exe' ) ) )
172 $swSVGConvInfo = self
::execOutput( $execFullPath . ' -version' );
173 list( $head, $tail ) = explode( 'ImageMagick', $swSVGConvInfo );
174 list( $swSVGConvVer ) = explode('http://www.imagemagick.org', $tail );
175 $swSVGConvURL = 'http://www.imagemagick.org/';
176 $haveSVGConvVer = true;
177 } else if ( strstr ($execFullPath, 'rsvg') != false ) {
178 // Get version info for rsvg
179 if ( file_exists( $execBinPath ) )
180 $swSVGConvInfo = self
::execOutput( $execBinPath . ' -v' );
181 else if ( file_exists( trim( $execFullPath, '"' ) ) ||
( file_exists( trim( $execFullPath, '"' ) . '.exe' ) ) )
182 $swSVGConvInfo = self
::execOutput( $execFullPath . ' -v' );
183 $swSVGConvLine = explode("\n",$swSVGConvInfo ,2);
184 $swSVGConvVer = $swSVGConvLine[0];
185 $swSVGConvURL = 'http://librsvg.sourceforge.net/';
186 $haveSVGConvVer = true;
187 } else if ( strstr ($execFullPath, 'inkscape') != false ) {
188 // Get version info for Inkscape
189 if ( file_exists( $execBinPath ) )
190 $swSVGConvInfo = self
::execOutput( $execBinPath . ' -z -V' );
191 else if ( file_exists( trim( $execFullPath, '"' ) ) ||
( file_exists( trim( $execFullPath, '"' ) . '.exe' ) ) )
192 $swSVGConvInfo = self
::execOutput( $execFullPath . ' -z -V' );
193 $swSVGConvLine = explode("\n",$swSVGConvInfo ,2);
194 $swSVGConvVer = ltrim( $swSVGConvLine[0], 'Inkscape ' );
195 $swSVGConvURL = 'http://www.inkscape.org/';
196 $swSVGConvName = ucfirst( $wgSVGConverter );
197 $haveSVGConvVer = true;
199 if ( $haveSVGConvVer )
200 $software["[$swSVGConvURL $swSVGConvName]"] = $swSVGConvVer;
203 // Look for TeX support and print the software version info
205 $binPath = '/usr/bin/';
208 'gs' => 'Ghostscript',
211 'imagemagick' => 'ImageMagick',
214 'ocaml' => 'http://caml.inria.fr/',
215 'gs' => 'http://www.ghostscript.com/',
216 'dvips' => 'http://www.radicaleye.com/dvips.html',
217 'latex' => 'http://www.latex-project.org/',
218 'imagemagick' => 'http://www.imagemagick.org/',
225 'imagemagick' => 'convert',
227 $swMathParam = Array(
228 'ocaml' => '-version',
232 'imagemagick' => '-version',
234 foreach ( $swMathExec as $swMath => $swMathCmd ) {
236 if ( file_exists( $binPath . 'whereis' ) ) {
237 $swWhereIsInfo = self
::execOutput( $binPath . 'whereis -b ' . $swMathCmd );
238 $swWhereIsLine = explode( "\n", $swWhereIsInfo, 2);
239 $swWhereIsFirstLine = $swWhereIsLine[0];
240 $swWhereIsBinPath = explode( ' ', $swWhereIsFirstLine, 3);
241 if ( count( $swWhereIsBinPath ) > 1 )
242 $wBinPath = dirname( $swWhereIsBinPath[1] );
244 $swPathLine = explode( ';', $_SERVER['PATH'] );
245 $swPathFound = false;
246 foreach( $swPathLine as $swPathDir ) {
247 if ( file_exists( $swPathDir . '/' . $swMathCmd . '.exe' ) && ($swPathFound === false) ) {
248 $wBinPath = $swPathDir . '/';
253 if ( file_exists( $binPath . $swMathCmd ) ||
file_exists( $wBinPath . $swMathCmd ) ) {
254 $swMathInfo = self
::execOutput( $swMathCmd . ' ' . $swMathParam[$swMath] );
255 $swMathLine = explode( "\n", $swMathInfo, 2);
256 $swMathVerInfo = $swMathLine[0];
257 if ( !strcmp( $swMath, 'gs' ) )
258 $swMathVerInfo = str_replace( 'GPL Ghostscript ', '', $swMathVerInfo );
259 else if ( !strcmp( $swMath, 'dvips' ) ) {
260 $swMathVerParts = explode( ' ' , $swMathVerInfo );
261 $swMathVerInfo = $swMathVerParts[3];
262 } else if ( !strcmp( $swMath, 'imagemagick' ) ) {
263 list( $head, $tail ) = explode( 'ImageMagick', $swMathVerInfo );
264 list( $swMathVerInfo ) = explode('http://www.imagemagick.org', $tail );
266 $swMathVer[$swMath] = trim( $swMathVerInfo );
267 $software["[$swMathURL[$swMath] $swMathName[$swMath]]"] = $swMathVer[$swMath];
272 // Allow a hook to add/remove items
273 wfRunHooks( 'SoftwareInfo', array( &$software ) );
275 $out = Xml
::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) .
276 Xml
::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-software' ) ) .
278 <th>" . wfMsg( 'version-software-product' ) . "</th>
279 <th>" . wfMsg( 'version-software-version' ) . "</th>
281 foreach( $software as $name => $version ) {
283 <td>" . $name . "</td>
284 <td>" . $version . "</td>
287 return $out . Xml
::closeElement( 'table' );
291 * Return a string of the MediaWiki version with SVN revision if available
295 public static function getVersion( $flags = '' ) {
296 global $wgVersion, $IP;
297 wfProfileIn( __METHOD__
);
298 $svn = self
::getSvnRevision( $IP, false, false , false );
299 $svnCo = self
::getSvnRevision( $IP, true, false , false );
301 $version = $wgVersion;
302 } elseif( $flags === 'nodb' ) {
303 $version = "$wgVersion (r$svnCo)";
305 $version = $wgVersion . wfMsg( 'version-svn-revision', $svn, $svnCo );
307 wfProfileOut( __METHOD__
);
312 * Return a string of the MediaWiki version with a link to SVN revision if
317 public static function getVersionLinked() {
318 global $wgVersion, $IP;
319 wfProfileIn( __METHOD__
);
320 $svn = self
::getSvnRevision( $IP, false, false, false );
321 $svnCo = self
::getSvnRevision( $IP, true, false, false );
322 $svnDir = self
::getSvnRevision( $IP, true, false, true );
323 $viewvcStart = 'http://svn.wikimedia.org/viewvc/mediawiki/';
324 $viewvcEnd = '/?pathrev=';
325 $viewvc = $viewvcStart . $svnDir . $viewvcEnd;
326 $version = $svn ?
$wgVersion . " [{$viewvc}{$svnCo} " . wfMsg( 'version-svn-revision', $svn, $svnCo ) . ']' : $wgVersion;
327 wfProfileOut( __METHOD__
);
331 /** Generate wikitext showing extensions name, URL, author and description */
332 function extensionCredits() {
333 global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions;
335 if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) )
338 $extensionTypes = array(
339 'specialpage' => wfMsg( 'version-specialpages' ),
340 'parserhook' => wfMsg( 'version-parserhooks' ),
341 'variable' => wfMsg( 'version-variables' ),
342 'media' => wfMsg( 'version-mediahandlers' ),
343 'other' => wfMsg( 'version-other' ),
345 wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
347 $out = Xml
::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) .
348 Xml
::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-ext' ) );
350 foreach ( $extensionTypes as $type => $text ) {
351 if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) {
352 $out .= $this->openExtType( $text );
354 usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
356 foreach ( $wgExtensionCredits[$type] as $extension ) {
359 $subVersionCo = null;
361 if ( isset( $extension['path'] ) ) {
362 $subVersion = self
::getSvnRevision(dirname($extension['path']), false, true, false);
363 $subVersionCo = self
::getSvnRevision(dirname($extension['path']), true, true, false);
364 $subVersionDir = self
::getSvnRevision(dirname($extension['path']), false, true, true);
366 $viewvc = $subVersionDir . $subVersionCo;
368 if ( isset( $extension['version'] ) ) {
369 $version = $extension['version'];
372 $out .= $this->formatCredits(
373 isset ( $extension['name'] ) ?
$extension['name'] : '',
378 isset ( $extension['author'] ) ?
$extension['author'] : '',
379 isset ( $extension['url'] ) ?
$extension['url'] : null,
380 isset ( $extension['description'] ) ?
$extension['description'] : '',
381 isset ( $extension['descriptionmsg'] ) ?
$extension['descriptionmsg'] : null
387 if ( count( $wgExtensionFunctions ) ) {
388 $out .= $this->openExtType( wfMsg( 'version-extension-functions' ) );
389 $out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
392 if ( $cnt = count( $tags = $wgParser->getTags() ) ) {
393 for ( $i = 0; $i < $cnt; ++
$i )
394 $tags[$i] = "<{$tags[$i]}>";
395 $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ) );
396 $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
399 if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
400 $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ) );
401 $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
404 if ( count( $wgSkinExtensionFunctions ) ) {
405 $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ) );
406 $out .= '<tr><td colspan="4">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n";
408 $out .= Xml
::closeElement( 'table' );
412 /** Callback to sort extensions by type */
413 function compare( $a, $b ) {
415 if( $a['name'] === $b['name'] ) {
418 return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] )
424 function formatCredits( $name, $version = null, $subVersion = null, $subVersionCo = null, $subVersionURL = null, $author = null, $url = null, $description = null, $descriptionMsg = null ) {
425 $haveSubversion = $subVersion;
426 $extension = isset( $url ) ?
"[$url $name]" : $name;
427 $version = isset( $version ) ?
wfMsg( 'version-version', $version ) : '';
428 $subVersion = isset( $subVersion ) ?
wfMsg( 'version-svn-revision', $subVersion, $subVersionCo ) : '';
429 $subVersion = isset( $subVersionURL ) ?
"[$subVersionURL $subVersion]" : $subVersion;
431 # Look for a localized description
432 if( isset( $descriptionMsg ) ) {
433 $msg = wfMsg( $descriptionMsg );
434 if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
439 if ( $haveSubversion ) {
441 <td><em>$extension $version</em></td>
442 <td><em>$subVersion</em></td>";
445 <td colspan=\"2\"><em>$extension $version</em></td>";
447 $extDescAuthor = "<td>$description</td>
448 <td>" . $this->listToText( (array)$author ) . "</td>
450 return $ret = $extNameVer . $extDescAuthor;
460 if ( count( $wgHooks ) ) {
461 $myWgHooks = $wgHooks;
464 $ret = Xml
::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) .
465 Xml
::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
467 <th>" . wfMsg( 'version-hook-name' ) . "</th>
468 <th>" . wfMsg( 'version-hook-subscribedby' ) . "</th>
471 foreach ( $myWgHooks as $hook => $hooks )
474 <td>" . $this->listToText( $hooks ) . "</td>
477 $ret .= Xml
::closeElement( 'table' );
483 private function openExtType($text, $name = null) {
484 $opt = array( 'colspan' => 4 );
487 if(!$this->firstExtOpened
) {
488 // Insert a spacing line
489 $out .= '<tr class="sv-space">' . Xml
::element( 'td', $opt ) . "</tr>\n";
491 $this->firstExtOpened
= false;
493 if($name) { $opt['id'] = "sv-$name"; }
495 $out .= "<tr>" . Xml
::element( 'th', $opt, $text) . "</tr>\n";
503 $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) );
504 return "<!-- visited from $ip -->\n" .
505 "<span style='display:none'>visited from $ip</span>";
512 function listToText( $list ) {
513 $cnt = count( $list );
516 // Enforce always returning a string
517 return (string)self
::arrayToString( $list[0] );
518 } elseif ( $cnt == 0 ) {
523 return $wgLang->listToText( array_map( array( __CLASS__
, 'arrayToString' ), $list ) );
528 * @param mixed $list Will convert an array to string if given and return
529 * the paramater unaltered otherwise
532 static function arrayToString( $list ) {
533 if( is_array( $list ) && count( $list ) == 1 )
535 if( is_object( $list ) ) {
536 $class = get_class( $list );
538 } elseif ( !is_array( $list ) ) {
541 if( is_object( $list[0] ) )
542 $class = get_class( $list[0] );
545 return "($class, {$list[1]})";
550 * Retrieve the revision number of a Subversion working directory.
552 * @param String $dir Directory of the svn checkout
553 * @param Boolean $coRev optional to return value whether is Last Modified
554 * or Checkout revision number
555 * @param Boolean $extension optional to check the path whether is from
556 * Wikimedia SVN server or not
557 * @param Boolean $relPath optional to get the end part of the checkout path
558 * @return mixed revision number as int, end part of the checkout path,
559 * or false if not a SVN checkout
561 public static function getSvnRevision( $dir, $coRev = false, $extension = false, $relPath = false) {
562 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
563 $entries = $dir . '/.svn/entries';
565 if( !file_exists( $entries ) ) {
569 $content = file( $entries );
571 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
572 if( preg_match( '/^<\?xml/', $content[0] ) ) {
573 // subversion is release <= 1.3
574 if( !function_exists( 'simplexml_load_file' ) ) {
575 // We could fall back to expat... YUCK
579 // SimpleXml whines about the xmlns...
580 wfSuppressWarnings();
581 $xml = simplexml_load_file( $entries );
585 foreach( $xml->entry
as $entry ) {
586 if( $xml->entry
[0]['name'] == '' ) {
587 // The directory entry should always have a revision marker.
588 if( $entry['revision'] ) {
589 return intval( $entry['revision'] );
596 // subversion is release 1.4 or above
598 $endPath = strstr( $content[4], 'tags' );
600 $endPath = strstr( $content[4], 'branches' );
602 $endPath = strstr( $content[4], 'trunk' );
607 $endPath = trim ( $endPath );
609 $wmSvnPath = 'svn.wikimedia.org/svnroot/mediawiki';
610 $isWMSvn = strstr($content[5],$wmSvnPath);
611 if (!strcmp($isWMSvn,null)) {
614 $viewvcStart = 'http://svn.wikimedia.org/viewvc/mediawiki/';
615 if (strstr( $content[4], 'trunk' ))
616 $viewvcEnd = '/?pathrev=';
618 // Avoids 404 error using pathrev when it does not found
619 $viewvcEnd = '/?revision=';
620 $viewvc = $viewvcStart . $endPath . $viewvcEnd;
627 // get the directory checkout revsion number
628 return intval( $content[3]) ;
630 // get the directory last modified revision number
631 return intval( $content[10] );