571a010efbf42842632fb472dae64c8488511cbf
[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 * @private
48 */
49
50 /**
51 * @return wiki text showing the license information
52 */
53 static function MediaWikiCredits() {
54 global $wgContLang;
55
56 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) );
57
58 // This text is always left-to-right.
59 $ret .= '<div dir="ltr">';
60 $ret .= "__NOTOC__
61 This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''',
62 copyright © 2001-2009 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
63 Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason,
64 Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor,
65 Aaron Schulz, Andrew Garrett, Raimond Spekking, Alexandre Emsenhuber,
66 Siebrand Mazeland, Chad Horohoe and others.
67
68 MediaWiki is free software; you can redistribute it and/or modify
69 it under the terms of the GNU General Public License as published by
70 the Free Software Foundation; either version 2 of the License, or
71 (at your option) any later version.
72
73 MediaWiki is distributed in the hope that it will be useful,
74 but WITHOUT ANY WARRANTY; without even the implied warranty of
75 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
76 GNU General Public License for more details.
77
78 You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU General Public License]
79 along with this program; if not, write to the Free Software
80 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
81 or [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html read it online].
82 ";
83 $ret .= '</div>';
84
85 return str_replace( "\t\t", '', $ret ) . "\n";
86 }
87
88 /**
89 * @return wiki text showing the third party software versions (apache, php, mysql).
90 */
91 static function softwareInformation() {
92 $dbr = wfGetDB( DB_SLAVE );
93
94 // Put the software in an array of form 'name' => 'version'. All messages should
95 // be loaded here, so feel free to use wfMsg*() in the 'name'. Raw HTML or wikimarkup
96 // can be used
97 $software = array();
98 $software['[http://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
99 $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")";
100 $software[$dbr->getSoftwareLink()] = $dbr->getServerVersion();
101
102 // Allow a hook to add/remove items
103 wfRunHooks( 'SoftwareInfo', array( &$software ) );
104
105 $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) .
106 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-software' ) ) .
107 "<tr>
108 <th>" . wfMsg( 'version-software-product' ) . "</th>
109 <th>" . wfMsg( 'version-software-version' ) . "</th>
110 </tr>\n";
111 foreach( $software as $name => $version ) {
112 $out .= "<tr>
113 <td>" . $name . "</td>
114 <td>" . $version . "</td>
115 </tr>\n";
116 }
117 return $out . Xml::closeElement( 'table' );
118 }
119
120 /**
121 * Return a string of the MediaWiki version with SVN revision if available
122 *
123 * @return mixed
124 */
125 public static function getVersion( $flags = '' ) {
126 global $wgVersion, $IP;
127 wfProfileIn( __METHOD__ );
128 $svn = self::getSvnRevision( $IP, false, false , false );
129 $svnCo = self::getSvnRevision( $IP, true, false , false );
130 if ( !$svn ) {
131 $version = $wgVersion;
132 } elseif( $flags === 'nodb' ) {
133 $version = "$wgVersion (r$svnCo)";
134 } else {
135 $version = $wgVersion . wfMsg( 'version-svn-revision', $svn, $svnCo );
136 }
137 wfProfileOut( __METHOD__ );
138 return $version;
139 }
140
141 /**
142 * Return a string of the MediaWiki version with a link to SVN revision if
143 * available
144 *
145 * @return mixed
146 */
147 public static function getVersionLinked() {
148 global $wgVersion, $IP;
149 wfProfileIn( __METHOD__ );
150 $svn = self::getSvnRevision( $IP, false, false, false );
151 $svnCo = self::getSvnRevision( $IP, true, false, false );
152 $svnDir = self::getSvnRevision( $IP, true, false, true );
153 $viewvcStart = 'http://svn.wikimedia.org/viewvc/mediawiki/';
154 $viewvcEnd = '/?pathrev=';
155 $viewvc = $viewvcStart . $svnDir . $viewvcEnd;
156 $version = $svn ? $wgVersion . " [{$viewvc}{$svnCo} " . wfMsg( 'version-svn-revision', $svn, $svnCo ) . ']' : $wgVersion;
157 wfProfileOut( __METHOD__ );
158 return $version;
159 }
160
161 /** Generate wikitext showing extensions name, URL, author and description */
162 function extensionCredits() {
163 global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions;
164
165 if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) )
166 return '';
167
168 $extensionTypes = array(
169 'specialpage' => wfMsg( 'version-specialpages' ),
170 'parserhook' => wfMsg( 'version-parserhooks' ),
171 'variable' => wfMsg( 'version-variables' ),
172 'media' => wfMsg( 'version-mediahandlers' ),
173 'other' => wfMsg( 'version-other' ),
174 );
175 wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
176
177 $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) .
178 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-ext' ) );
179
180 foreach ( $extensionTypes as $type => $text ) {
181 if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) {
182 $out .= $this->openExtType( $text );
183
184 usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
185
186 foreach ( $wgExtensionCredits[$type] as $extension ) {
187 $version = null;
188 $subVersion = null;
189 $subVersionCo = null;
190 $viewvc = null;
191 if ( isset( $extension['path'] ) ) {
192 $subVersion = self::getSvnRevision(dirname($extension['path']), false, true, false);
193 $subVersionCo = self::getSvnRevision(dirname($extension['path']), true, true, false);
194 $subVersionDir = self::getSvnRevision(dirname($extension['path']), false, true, true);
195 if ($subVersionDir)
196 $viewvc = $subVersionDir . $subVersionCo;
197 }
198 if ( isset( $extension['version'] ) ) {
199 $version = $extension['version'];
200 }
201
202 $out .= $this->formatCredits(
203 isset ( $extension['name'] ) ? $extension['name'] : '',
204 $version,
205 $subVersion,
206 $subVersionCo,
207 $viewvc,
208 isset ( $extension['author'] ) ? $extension['author'] : '',
209 isset ( $extension['url'] ) ? $extension['url'] : null,
210 isset ( $extension['description'] ) ? $extension['description'] : '',
211 isset ( $extension['descriptionmsg'] ) ? $extension['descriptionmsg'] : null
212 );
213 }
214 }
215 }
216
217 if ( count( $wgExtensionFunctions ) ) {
218 $out .= $this->openExtType( wfMsg( 'version-extension-functions' ) );
219 $out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
220 }
221
222 if ( $cnt = count( $tags = $wgParser->getTags() ) ) {
223 for ( $i = 0; $i < $cnt; ++$i )
224 $tags[$i] = "&lt;{$tags[$i]}&gt;";
225 $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ) );
226 $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
227 }
228
229 if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
230 $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ) );
231 $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
232 }
233
234 if ( count( $wgSkinExtensionFunctions ) ) {
235 $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ) );
236 $out .= '<tr><td colspan="4">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n";
237 }
238 $out .= Xml::closeElement( 'table' );
239 return $out;
240 }
241
242 /** Callback to sort extensions by type */
243 function compare( $a, $b ) {
244 global $wgLang;
245 if( $a['name'] === $b['name'] ) {
246 return 0;
247 } else {
248 return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] )
249 ? 1
250 : -1;
251 }
252 }
253
254 function formatCredits( $name, $version = null, $subVersion = null, $subVersionCo = null, $subVersionURL = null, $author = null, $url = null, $description = null, $descriptionMsg = null ) {
255 $haveSubversion = $subVersion;
256 $extension = isset( $url ) ? "[$url $name]" : $name;
257 $version = isset( $version ) ? '<span class="mw-version-ext-version">' . wfMsg( 'version-version', $version ) . '</span>' : '';
258 $subVersion = isset( $subVersion ) ? wfMsg( 'version-svn-revision', $subVersion, $subVersionCo ) : '';
259 $subVersion = isset( $subVersionURL ) ? "[$subVersionURL $subVersion]" : $subVersion;
260
261 # Look for a localized description
262 if( isset( $descriptionMsg ) ) {
263 if( is_array( $descriptionMsg ) ) {
264 $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
265 array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
266 array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
267 $msg = wfMsg( $descriptionMsgKey, $descriptionMsg );
268 } else {
269 $msg = wfMsg( $descriptionMsg );
270 }
271 if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
272 $description = $msg;
273 }
274 }
275
276 if ( $haveSubversion ) {
277 $extNameVer = "<tr>
278 <td><em>$extension $version</em></td>
279 <td><em>$subVersion</em></td>";
280 } else {
281 $extNameVer = "<tr>
282 <td colspan=\"2\"><em>$extension $version</em></td>";
283 }
284 $extDescAuthor = "<td>$description</td>
285 <td>" . $this->listToText( (array)$author ) . "</td>
286 </tr>\n";
287 return $ret = $extNameVer . $extDescAuthor;
288 return $ret;
289 }
290
291 /**
292 * @return string
293 */
294 function wgHooks() {
295 global $wgHooks;
296
297 if ( count( $wgHooks ) ) {
298 $myWgHooks = $wgHooks;
299 ksort( $myWgHooks );
300
301 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) .
302 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
303 "<tr>
304 <th>" . wfMsg( 'version-hook-name' ) . "</th>
305 <th>" . wfMsg( 'version-hook-subscribedby' ) . "</th>
306 </tr>\n";
307
308 foreach ( $myWgHooks as $hook => $hooks )
309 $ret .= "<tr>
310 <td>$hook</td>
311 <td>" . $this->listToText( $hooks ) . "</td>
312 </tr>\n";
313
314 $ret .= Xml::closeElement( 'table' );
315 return $ret;
316 } else
317 return '';
318 }
319
320 private function openExtType($text, $name = null) {
321 $opt = array( 'colspan' => 4 );
322 $out = '';
323
324 if(!$this->firstExtOpened) {
325 // Insert a spacing line
326 $out .= '<tr class="sv-space">' . Xml::element( 'td', $opt ) . "</tr>\n";
327 }
328 $this->firstExtOpened = false;
329
330 if($name) { $opt['id'] = "sv-$name"; }
331
332 $out .= "<tr>" . Xml::element( 'th', $opt, $text) . "</tr>\n";
333 return $out;
334 }
335
336 /**
337 * @return string
338 */
339 function IPInfo() {
340 $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) );
341 return "<!-- visited from $ip -->\n" .
342 "<span style='display:none'>visited from $ip</span>";
343 }
344
345 /**
346 * @param array $list
347 * @return string
348 */
349 function listToText( $list ) {
350 $cnt = count( $list );
351
352 if ( $cnt == 1 ) {
353 // Enforce always returning a string
354 return (string)self::arrayToString( $list[0] );
355 } elseif ( $cnt == 0 ) {
356 return '';
357 } else {
358 global $wgLang;
359 sort( $list );
360 return $wgLang->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
361 }
362 }
363
364 /**
365 * @param mixed $list Will convert an array to string if given and return
366 * the paramater unaltered otherwise
367 * @return mixed
368 */
369 static function arrayToString( $list ) {
370 if( is_array( $list ) && count( $list ) == 1 )
371 $list = $list[0];
372 if( is_object( $list ) ) {
373 $class = get_class( $list );
374 return "($class)";
375 } elseif ( !is_array( $list ) ) {
376 return $list;
377 } else {
378 if( is_object( $list[0] ) )
379 $class = get_class( $list[0] );
380 else
381 $class = $list[0];
382 return "($class, {$list[1]})";
383 }
384 }
385
386 /**
387 * Retrieve the revision number of a Subversion working directory.
388 *
389 * @param String $dir Directory of the svn checkout
390 * @param Boolean $coRev optional to return value whether is Last Modified
391 * or Checkout revision number
392 * @param Boolean $extension optional to check the path whether is from
393 * Wikimedia SVN server or not
394 * @param Boolean $relPath optional to get the end part of the checkout path
395 * @return mixed revision number as int, end part of the checkout path,
396 * or false if not a SVN checkout
397 */
398 public static function getSvnRevision( $dir, $coRev = false, $extension = false, $relPath = false) {
399 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
400 $entries = $dir . '/.svn/entries';
401
402 if( !file_exists( $entries ) ) {
403 return false;
404 }
405
406 $content = file( $entries );
407
408 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
409 if( preg_match( '/^<\?xml/', $content[0] ) ) {
410 // subversion is release <= 1.3
411 if( !function_exists( 'simplexml_load_file' ) ) {
412 // We could fall back to expat... YUCK
413 return false;
414 }
415
416 // SimpleXml whines about the xmlns...
417 wfSuppressWarnings();
418 $xml = simplexml_load_file( $entries );
419 wfRestoreWarnings();
420
421 if( $xml ) {
422 foreach( $xml->entry as $entry ) {
423 if( $xml->entry[0]['name'] == '' ) {
424 // The directory entry should always have a revision marker.
425 if( $entry['revision'] ) {
426 return intval( $entry['revision'] );
427 }
428 }
429 }
430 }
431 return false;
432 } else {
433 // subversion is release 1.4 or above
434 if ($relPath) {
435 $endPath = strstr( $content[4], 'tags' );
436 if (!$endPath) {
437 $endPath = strstr( $content[4], 'branches' );
438 if (!$endPath) {
439 $endPath = strstr( $content[4], 'trunk' );
440 if (!$endPath)
441 return false;
442 }
443 }
444 $endPath = trim ( $endPath );
445 if ($extension) {
446 $wmSvnPath = 'svn.wikimedia.org/svnroot/mediawiki';
447 $isWMSvn = strstr($content[5],$wmSvnPath);
448 if (!strcmp($isWMSvn,null)) {
449 return false;
450 } else {
451 $viewvcStart = 'http://svn.wikimedia.org/viewvc/mediawiki/';
452 if (strstr( $content[4], 'trunk' ))
453 $viewvcEnd = '/?pathrev=';
454 else
455 // Avoids 404 error using pathrev when it does not found
456 $viewvcEnd = '/?revision=';
457 $viewvc = $viewvcStart . $endPath . $viewvcEnd;
458 return $viewvc;
459 }
460 }
461 return $endPath;
462 }
463 if ($coRev)
464 // get the directory checkout revsion number
465 return intval( $content[3]) ;
466 else
467 // get the directory last modified revision number
468 return intval( $content[10] );
469 }
470 }
471
472 /**#@-*/
473 }