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