Revert Special:Version to known-good state as of r52060, without the ten billion...
[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'] : ''
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 $msg = wfMsg( $descriptionMsg );
263 if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
264 $description = $msg;
265 }
266 }
267
268 if ( $haveSubversion ) {
269 $extNameVer = "<tr>
270 <td><em>$extension $version</em></td>
271 <td><em>$subVersion</em></td>";
272 } else {
273 $extNameVer = "<tr>
274 <td colspan=\"2\"><em>$extension $version</em></td>";
275 }
276 $extDescAuthor = "<td>$description</td>
277 <td>" . $this->listToText( (array)$author ) . "</td>
278 </tr>\n";
279 return $ret = $extNameVer . $extDescAuthor;
280 return $ret;
281 }
282
283 /**
284 * @return string
285 */
286 function wgHooks() {
287 global $wgHooks;
288
289 if ( count( $wgHooks ) ) {
290 $myWgHooks = $wgHooks;
291 ksort( $myWgHooks );
292
293 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) .
294 Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
295 "<tr>
296 <th>" . wfMsg( 'version-hook-name' ) . "</th>
297 <th>" . wfMsg( 'version-hook-subscribedby' ) . "</th>
298 </tr>\n";
299
300 foreach ( $myWgHooks as $hook => $hooks )
301 $ret .= "<tr>
302 <td>$hook</td>
303 <td>" . $this->listToText( $hooks ) . "</td>
304 </tr>\n";
305
306 $ret .= Xml::closeElement( 'table' );
307 return $ret;
308 } else
309 return '';
310 }
311
312 private function openExtType($text, $name = null) {
313 $opt = array( 'colspan' => 4 );
314 $out = '';
315
316 if(!$this->firstExtOpened) {
317 // Insert a spacing line
318 $out .= '<tr class="sv-space">' . Xml::element( 'td', $opt ) . "</tr>\n";
319 }
320 $this->firstExtOpened = false;
321
322 if($name) { $opt['id'] = "sv-$name"; }
323
324 $out .= "<tr>" . Xml::element( 'th', $opt, $text) . "</tr>\n";
325 return $out;
326 }
327
328 /**
329 * @return string
330 */
331 function IPInfo() {
332 $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) );
333 return "<!-- visited from $ip -->\n" .
334 "<span style='display:none'>visited from $ip</span>";
335 }
336
337 /**
338 * @param array $list
339 * @return string
340 */
341 function listToText( $list ) {
342 $cnt = count( $list );
343
344 if ( $cnt == 1 ) {
345 // Enforce always returning a string
346 return (string)self::arrayToString( $list[0] );
347 } elseif ( $cnt == 0 ) {
348 return '';
349 } else {
350 global $wgLang;
351 sort( $list );
352 return $wgLang->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
353 }
354 }
355
356 /**
357 * @param mixed $list Will convert an array to string if given and return
358 * the paramater unaltered otherwise
359 * @return mixed
360 */
361 static function arrayToString( $list ) {
362 if( is_array( $list ) && count( $list ) == 1 )
363 $list = $list[0];
364 if( is_object( $list ) ) {
365 $class = get_class( $list );
366 return "($class)";
367 } elseif ( !is_array( $list ) ) {
368 return $list;
369 } else {
370 if( is_object( $list[0] ) )
371 $class = get_class( $list[0] );
372 else
373 $class = $list[0];
374 return "($class, {$list[1]})";
375 }
376 }
377
378 /**
379 * Retrieve the revision number of a Subversion working directory.
380 *
381 * @param String $dir Directory of the svn checkout
382 * @param Boolean $coRev optional to return value whether is Last Modified
383 * or Checkout revision number
384 * @param Boolean $extension optional to check the path whether is from
385 * Wikimedia SVN server or not
386 * @param Boolean $relPath optional to get the end part of the checkout path
387 * @return mixed revision number as int, end part of the checkout path,
388 * or false if not a SVN checkout
389 */
390 public static function getSvnRevision( $dir, $coRev = false, $extension = false, $relPath = false) {
391 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
392 $entries = $dir . '/.svn/entries';
393
394 if( !file_exists( $entries ) ) {
395 return false;
396 }
397
398 $content = file( $entries );
399
400 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
401 if( preg_match( '/^<\?xml/', $content[0] ) ) {
402 // subversion is release <= 1.3
403 if( !function_exists( 'simplexml_load_file' ) ) {
404 // We could fall back to expat... YUCK
405 return false;
406 }
407
408 // SimpleXml whines about the xmlns...
409 wfSuppressWarnings();
410 $xml = simplexml_load_file( $entries );
411 wfRestoreWarnings();
412
413 if( $xml ) {
414 foreach( $xml->entry as $entry ) {
415 if( $xml->entry[0]['name'] == '' ) {
416 // The directory entry should always have a revision marker.
417 if( $entry['revision'] ) {
418 return intval( $entry['revision'] );
419 }
420 }
421 }
422 }
423 return false;
424 } else {
425 // subversion is release 1.4 or above
426 if ($relPath) {
427 $endPath = strstr( $content[4], 'tags' );
428 if (!$endPath) {
429 $endPath = strstr( $content[4], 'branches' );
430 if (!$endPath) {
431 $endPath = strstr( $content[4], 'trunk' );
432 if (!$endPath)
433 return false;
434 }
435 }
436 $endPath = trim ( $endPath );
437 if ($extension) {
438 $wmSvnPath = 'svn.wikimedia.org/svnroot/mediawiki';
439 $isWMSvn = strstr($content[5],$wmSvnPath);
440 if (!strcmp($isWMSvn,null)) {
441 return false;
442 } else {
443 $viewvcStart = 'http://svn.wikimedia.org/viewvc/mediawiki/';
444 if (strstr( $content[4], 'trunk' ))
445 $viewvcEnd = '/?pathrev=';
446 else
447 // Avoids 404 error using pathrev when it does not found
448 $viewvcEnd = '/?revision=';
449 $viewvc = $viewvcStart . $endPath . $viewvcEnd;
450 return $viewvc;
451 }
452 }
453 return $endPath;
454 }
455 if ($coRev)
456 // get the directory checkout revsion number
457 return intval( $content[3]) ;
458 else
459 // get the directory last modified revision number
460 return intval( $content[10] );
461 }
462 }
463
464 /**#@-*/
465 }