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