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