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