* (bug 18242) Show the Subversion revision number per extensions in Special:Version...
[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;
24 $wgMessageCache->loadAllMessages();
25
26 $this->setHeaders();
27 $this->outputHeader();
28
29 $wgOut->addHTML( '<div dir="ltr">' );
30 $text =
31 $this->MediaWikiCredits() .
32 $this->softwareInformation() .
33 $this->extensionCredits();
34 if ( $wgSpecialVersionShowHooks ) {
35 $text .= $this->wgHooks();
36 }
37 $wgOut->addWikiText( $text );
38 $wgOut->addHTML( $this->IPInfo() );
39 $wgOut->addHTML( '</div>' );
40 }
41
42 /**#@+
43 * @private
44 */
45
46 /**
47 * @return wiki text showing the license information
48 */
49 static function MediaWikiCredits() {
50 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) ) .
51 "__NOTOC__
52 This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''',
53 copyright (C) 2001-2009 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
54 Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason,
55 Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor,
56 Aaron Schulz and others.
57
58 MediaWiki is free software; you can redistribute it and/or modify
59 it under the terms of the GNU General Public License as published by
60 the Free Software Foundation; either version 2 of the License, or
61 (at your option) any later version.
62
63 MediaWiki is distributed in the hope that it will be useful,
64 but WITHOUT ANY WARRANTY; without even the implied warranty of
65 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
66 GNU General Public License for more details.
67
68 You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU General Public License]
69 along with this program; if not, write to the Free Software
70 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
71 or [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html read it online].
72 ";
73
74 return str_replace( "\t\t", '', $ret ) . "\n";
75 }
76
77 /**
78 * @return wiki text showing the third party software versions (apache, php, mysql).
79 */
80 static function softwareInformation() {
81 $dbr = wfGetDB( DB_SLAVE );
82
83 // Put the software in an array of form 'name' => 'version'. All messages should
84 // be loaded here, so feel free to use wfMsg*() in the 'name'. Raw HTML or wikimarkup
85 // can be used
86 $software = array();
87 $software['[http://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
88 $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")";
89 $software[$dbr->getSoftwareLink()] = $dbr->getServerVersion();
90
91 // Allow a hook to add/remove items
92 wfRunHooks( 'SoftwareInfo', array( &$software ) );
93
94 $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) .
95 Xml::openElement( 'table', array( 'id' => 'sv-software' ) ) .
96 "<tr>
97 <th>" . wfMsg( 'version-software-product' ) . "</th>
98 <th>" . wfMsg( 'version-software-version' ) . "</th>
99 </tr>\n";
100 foreach( $software as $name => $version ) {
101 $out .= "<tr>
102 <td>" . $name . "</td>
103 <td>" . $version . "</td>
104 </tr>\n";
105 }
106 return $out . Xml::closeElement( 'table' );
107 }
108
109 /**
110 * Return a string of the MediaWiki version with SVN revision if available
111 *
112 * @return mixed
113 */
114 public static function getVersion() {
115 global $wgVersion, $IP;
116 wfProfileIn( __METHOD__ );
117 $svn = self::getSvnRevision( $IP, false );
118 $version = $svn ? "$wgVersion (r$svn)" : $wgVersion;
119 wfProfileOut( __METHOD__ );
120 return $version;
121 }
122
123 /**
124 * Return a string of the MediaWiki version with a link to SVN revision if
125 * available
126 *
127 * @return mixed
128 */
129 public static function getVersionLinked() {
130 global $wgVersion, $IP;
131 wfProfileIn( __METHOD__ );
132 $svn = self::getSvnRevision( $IP, false );
133 $viewvc = 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/?pathrev=';
134 $version = $svn ? "$wgVersion ([{$viewvc}{$svn} r$svn])" : $wgVersion;
135 wfProfileOut( __METHOD__ );
136 return $version;
137 }
138
139 /** Generate wikitext showing extensions name, URL, author and description */
140 function extensionCredits() {
141 global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions;
142
143 if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) )
144 return '';
145
146 $extensionTypes = array(
147 'specialpage' => wfMsg( 'version-specialpages' ),
148 'parserhook' => wfMsg( 'version-parserhooks' ),
149 'variable' => wfMsg( 'version-variables' ),
150 'media' => wfMsg( 'version-mediahandlers' ),
151 'other' => wfMsg( 'version-other' ),
152 );
153 wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
154
155 $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) .
156 Xml::openElement( 'table', array( 'id' => 'sv-ext' ) );
157
158 foreach ( $extensionTypes as $type => $text ) {
159 if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) {
160 $out .= $this->openExtType( $text );
161
162 usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
163
164 foreach ( $wgExtensionCredits[$type] as $extension ) {
165 $version = null;
166 $subVersion = null;
167 if (isset($extension['path']))
168 $subVersion = self::getSvnRevision(dirname($extension['path']), true);
169 if ( isset( $extension['version'] ) ) {
170 $version = $extension['version'];
171 }
172
173 $out .= $this->formatCredits(
174 isset ( $extension['name'] ) ? $extension['name'] : '',
175 $version,
176 $subVersion,
177 isset ( $extension['author'] ) ? $extension['author'] : '',
178 isset ( $extension['url'] ) ? $extension['url'] : null,
179 isset ( $extension['description'] ) ? $extension['description'] : '',
180 isset ( $extension['descriptionmsg'] ) ? $extension['descriptionmsg'] : ''
181 );
182 }
183 }
184 }
185
186 if ( count( $wgExtensionFunctions ) ) {
187 $out .= $this->openExtType( wfMsg( 'version-extension-functions' ) );
188 $out .= '<tr><td colspan="3">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
189 }
190
191 if ( $cnt = count( $tags = $wgParser->getTags() ) ) {
192 for ( $i = 0; $i < $cnt; ++$i )
193 $tags[$i] = "&lt;{$tags[$i]}&gt;";
194 $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ) );
195 $out .= '<tr><td colspan="3">' . $this->listToText( $tags ). "</td></tr>\n";
196 }
197
198 if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
199 $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ) );
200 $out .= '<tr><td colspan="3">' . $this->listToText( $fhooks ) . "</td></tr>\n";
201 }
202
203 if ( count( $wgSkinExtensionFunctions ) ) {
204 $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ) );
205 $out .= '<tr><td colspan="3">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n";
206 }
207 $out .= Xml::closeElement( 'table' );
208 return $out;
209 }
210
211 /** Callback to sort extensions by type */
212 function compare( $a, $b ) {
213 global $wgLang;
214 if( $a['name'] === $b['name'] ) {
215 return 0;
216 } else {
217 return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] )
218 ? 1
219 : -1;
220 }
221 }
222
223 function formatCredits( $name, $version = null, $subVersion = null, $author = null, $url = null, $description = null, $descriptionMsg = null ) {
224 $extension = isset( $url ) ? "[$url $name]" : $name;
225 $version = isset( $version ) ? "(" . wfMsg( 'version-version' ) . " $version)" : '';
226 $subVersion = isset( $subVersion ) ? "(" . wfMsg( 'version-revision' ) . " r$subVersion)" : '';
227
228 # Look for a localized description
229 if( isset( $descriptionMsg ) ) {
230 $msg = wfMsg( $descriptionMsg );
231 if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
232 $description = $msg;
233 }
234 }
235
236 return "<tr>
237 <td><em>$extension $version $subVersion</em></td>
238 <td>$description</td>
239 <td>" . $this->listToText( (array)$author ) . "</td>
240 </tr>\n";
241 }
242
243 /**
244 * @return string
245 */
246 function wgHooks() {
247 global $wgHooks;
248
249 if ( count( $wgHooks ) ) {
250 $myWgHooks = $wgHooks;
251 ksort( $myWgHooks );
252
253 $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) .
254 Xml::openElement( 'table', array( 'id' => 'sv-hooks' ) ) .
255 "<tr>
256 <th>" . wfMsg( 'version-hook-name' ) . "</th>
257 <th>" . wfMsg( 'version-hook-subscribedby' ) . "</th>
258 </tr>\n";
259
260 foreach ( $myWgHooks as $hook => $hooks )
261 $ret .= "<tr>
262 <td>$hook</td>
263 <td>" . $this->listToText( $hooks ) . "</td>
264 </tr>\n";
265
266 $ret .= Xml::closeElement( 'table' );
267 return $ret;
268 } else
269 return '';
270 }
271
272 private function openExtType($text, $name = null) {
273 $opt = array( 'colspan' => 3 );
274 $out = '';
275
276 if(!$this->firstExtOpened) {
277 // Insert a spacing line
278 $out .= '<tr class="sv-space">' . Xml::element( 'td', $opt ) . "</tr>\n";
279 }
280 $this->firstExtOpened = false;
281
282 if($name) { $opt['id'] = "sv-$name"; }
283
284 $out .= "<tr>" . Xml::element( 'th', $opt, $text) . "</tr>\n";
285 return $out;
286 }
287
288 /**
289 * @return string
290 */
291 function IPInfo() {
292 $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) );
293 return "<!-- visited from $ip -->\n" .
294 "<span style='display:none'>visited from $ip</span>";
295 }
296
297 /**
298 * @param array $list
299 * @return string
300 */
301 function listToText( $list ) {
302 $cnt = count( $list );
303
304 if ( $cnt == 1 ) {
305 // Enforce always returning a string
306 return (string)self::arrayToString( $list[0] );
307 } elseif ( $cnt == 0 ) {
308 return '';
309 } else {
310 global $wgLang;
311 sort( $list );
312 return $wgLang->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
313 }
314 }
315
316 /**
317 * @param mixed $list Will convert an array to string if given and return
318 * the paramater unaltered otherwise
319 * @return mixed
320 */
321 static function arrayToString( $list ) {
322 if( is_array( $list ) && count( $list ) == 1 )
323 $list = $list[0];
324 if( is_object( $list ) ) {
325 $class = get_class( $list );
326 return "($class)";
327 } elseif ( !is_array( $list ) ) {
328 return $list;
329 } else {
330 if( is_object( $list[0] ) )
331 $class = get_class( $list[0] );
332 else
333 $class = $list[0];
334 return "($class, {$list[1]})";
335 }
336 }
337
338 /**
339 * Retrieve the revision number of a Subversion working directory.
340 *
341 * @param string $dir
342 * @return mixed revision number as int, or false if not a SVN checkout
343 */
344 public static function getSvnRevision( $dir , $extension = false) {
345 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
346 $entries = $dir . '/.svn/entries';
347
348 if( !file_exists( $entries ) ) {
349 return false;
350 }
351
352 $content = file( $entries );
353
354 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
355 if( preg_match( '/^<\?xml/', $content[0] ) ) {
356 // subversion is release <= 1.3
357 if( !function_exists( 'simplexml_load_file' ) ) {
358 // We could fall back to expat... YUCK
359 return false;
360 }
361
362 // SimpleXml whines about the xmlns...
363 wfSuppressWarnings();
364 $xml = simplexml_load_file( $entries );
365 wfRestoreWarnings();
366
367 if( $xml ) {
368 foreach( $xml->entry as $entry ) {
369 if( $xml->entry[0]['name'] == '' ) {
370 // The directory entry should always have a revision marker.
371 if( $entry['revision'] ) {
372 return intval( $entry['revision'] );
373 }
374 }
375 }
376 }
377 return false;
378 } else {
379 // subversion is release 1.4 or above
380 if ($extension)
381 // get the last file revsion number
382 return intval( $content[10]) ;
383 else
384 // get the directory revsion number
385 return intval( $content[3] );
386 }
387 }
388
389 /**#@-*/
390 }