MediaWiki  REL1_24
SpecialVersion.php
Go to the documentation of this file.
00001 <?php
00031 class SpecialVersion extends SpecialPage {
00032     protected $firstExtOpened = false;
00033 
00037     protected $coreId = '';
00038 
00039     protected static $extensionTypes = false;
00040 
00041     protected static $viewvcUrls = array(
00042         'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
00043         'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
00044         'https://svn.wikimedia.org/svnroot/mediawiki' => 'https://svn.wikimedia.org/viewvc/mediawiki',
00045     );
00046 
00047     public function __construct() {
00048         parent::__construct( 'Version' );
00049     }
00050 
00055     public function execute( $par ) {
00056         global $IP, $wgExtensionCredits;
00057 
00058         $this->setHeaders();
00059         $this->outputHeader();
00060         $out = $this->getOutput();
00061         $out->allowClickjacking();
00062 
00063         // Explode the sub page information into useful bits
00064         $parts = explode( '/', (string)$par );
00065         $extNode = null;
00066         if ( isset( $parts[1] ) ) {
00067             $extName = str_replace( '_', ' ', $parts[1] );
00068             // Find it!
00069             foreach ( $wgExtensionCredits as $group => $extensions ) {
00070                 foreach ( $extensions as $ext ) {
00071                     if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
00072                         $extNode = &$ext;
00073                         break 2;
00074                     }
00075                 }
00076             }
00077             if ( !$extNode ) {
00078                 $out->setStatusCode( 404 );
00079             }
00080         } else {
00081             $extName = 'MediaWiki';
00082         }
00083 
00084         // Now figure out what to do
00085         switch ( strtolower( $parts[0] ) ) {
00086             case 'credits':
00087                 $wikiText = '{{int:version-credits-not-found}}';
00088                 if ( $extName === 'MediaWiki' ) {
00089                     $wikiText = file_get_contents( $IP . '/CREDITS' );
00090                 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
00091                     $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
00092                     if ( $file ) {
00093                         $wikiText = file_get_contents( $file );
00094                         if ( substr( $file, -4 ) === '.txt' ) {
00095                             $wikiText = Html::element( 'pre', array(), $wikiText );
00096                         }
00097                     }
00098                 }
00099 
00100                 $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
00101                 $out->addWikiText( $wikiText );
00102                 break;
00103 
00104             case 'license':
00105                 $wikiText = '{{int:version-license-not-found}}';
00106                 if ( $extName === 'MediaWiki' ) {
00107                     $wikiText = file_get_contents( $IP . '/COPYING' );
00108                 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
00109                     $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
00110                     if ( $file ) {
00111                         $wikiText = file_get_contents( $file );
00112                         if ( !isset( $extNode['license-name'] ) ) {
00113                             // If the developer did not explicitly set license-name they probably
00114                             // are unaware that we're now sucking this file in and thus it's probably
00115                             // not wikitext friendly.
00116                             $wikiText = "<pre>$wikiText</pre>";
00117                         }
00118                     }
00119                 }
00120 
00121                 $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
00122                 $out->addWikiText( $wikiText );
00123                 break;
00124 
00125             default:
00126                 $out->addModules( 'mediawiki.special.version' );
00127                 $out->addWikiText(
00128                     $this->getMediaWikiCredits() .
00129                     $this->softwareInformation() .
00130                     $this->getEntryPointInfo()
00131                 );
00132                 $out->addHtml(
00133                     $this->getSkinCredits() .
00134                     $this->getExtensionCredits() .
00135                     $this->getParserTags() .
00136                     $this->getParserFunctionHooks()
00137                 );
00138                 $out->addWikiText( $this->getWgHooks() );
00139                 $out->addHTML( $this->IPInfo() );
00140 
00141                 break;
00142         }
00143     }
00144 
00150     private static function getMediaWikiCredits() {
00151         $ret = Xml::element(
00152             'h2',
00153             array( 'id' => 'mw-version-license' ),
00154             wfMessage( 'version-license' )->text()
00155         );
00156 
00157         // This text is always left-to-right.
00158         $ret .= '<div class="plainlinks">';
00159         $ret .= "__NOTOC__
00160         " . self::getCopyrightAndAuthorList() . "\n
00161         " . wfMessage( 'version-license-info' )->text();
00162         $ret .= '</div>';
00163 
00164         return str_replace( "\t\t", '', $ret ) . "\n";
00165     }
00166 
00172     public static function getCopyrightAndAuthorList() {
00173         global $wgLang;
00174 
00175         if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
00176             $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' .
00177                 wfMessage( 'version-poweredby-others' )->text() . ']';
00178         } else {
00179             $othersLink = '[[Special:Version/Credits|' .
00180                 wfMessage( 'version-poweredby-others' )->text() . ']]';
00181         }
00182 
00183         $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
00184             wfMessage( 'version-poweredby-translators' )->text() . ']';
00185 
00186         $authorList = array(
00187             'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
00188             'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
00189             'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
00190             'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
00191             'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
00192             'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
00193             'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
00194             'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink,
00195             $translatorsLink
00196         );
00197 
00198         return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
00199             $wgLang->listToText( $authorList ) )->text();
00200     }
00201 
00207     public static function softwareInformation() {
00208         $dbr = wfGetDB( DB_SLAVE );
00209 
00210         // Put the software in an array of form 'name' => 'version'. All messages should
00211         // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
00212         // wikimarkup can be used.
00213         $software = array();
00214         $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
00215         if ( wfIsHHVM() ) {
00216             $software['[http://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")";
00217         } else {
00218             $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")";
00219         }
00220         $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
00221 
00222         // Allow a hook to add/remove items.
00223         wfRunHooks( 'SoftwareInfo', array( &$software ) );
00224 
00225         $out = Xml::element(
00226                 'h2',
00227                 array( 'id' => 'mw-version-software' ),
00228                 wfMessage( 'version-software' )->text()
00229             ) .
00230                 Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) .
00231                 "<tr>
00232                     <th>" . wfMessage( 'version-software-product' )->text() . "</th>
00233                     <th>" . wfMessage( 'version-software-version' )->text() . "</th>
00234                 </tr>\n";
00235 
00236         foreach ( $software as $name => $version ) {
00237             $out .= "<tr>
00238                     <td>" . $name . "</td>
00239                     <td dir=\"ltr\">" . $version . "</td>
00240                 </tr>\n";
00241         }
00242 
00243         return $out . Xml::closeElement( 'table' );
00244     }
00245 
00252     public static function getVersion( $flags = '' ) {
00253         global $wgVersion, $IP;
00254         wfProfileIn( __METHOD__ );
00255 
00256         $gitInfo = self::getGitHeadSha1( $IP );
00257         $svnInfo = self::getSvnInfo( $IP );
00258         if ( !$svnInfo && !$gitInfo ) {
00259             $version = $wgVersion;
00260         } elseif ( $gitInfo && $flags === 'nodb' ) {
00261             $shortSha1 = substr( $gitInfo, 0, 7 );
00262             $version = "$wgVersion ($shortSha1)";
00263         } elseif ( $gitInfo ) {
00264             $shortSha1 = substr( $gitInfo, 0, 7 );
00265             $shortSha1 = wfMessage( 'parentheses' )->params( $shortSha1 )->escaped();
00266             $version = "$wgVersion $shortSha1";
00267         } elseif ( $flags === 'nodb' ) {
00268             $version = "$wgVersion (r{$svnInfo['checkout-rev']})";
00269         } else {
00270             $version = $wgVersion . ' ' .
00271                 wfMessage(
00272                     'version-svn-revision',
00273                     isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
00274                     $info['checkout-rev']
00275                 )->text();
00276         }
00277 
00278         wfProfileOut( __METHOD__ );
00279 
00280         return $version;
00281     }
00282 
00291     public static function getVersionLinked() {
00292         global $wgVersion;
00293         wfProfileIn( __METHOD__ );
00294 
00295         $gitVersion = self::getVersionLinkedGit();
00296         if ( $gitVersion ) {
00297             $v = $gitVersion;
00298         } else {
00299             $svnVersion = self::getVersionLinkedSvn();
00300             if ( $svnVersion ) {
00301                 $v = $svnVersion;
00302             } else {
00303                 $v = $wgVersion; // fallback
00304             }
00305         }
00306 
00307         wfProfileOut( __METHOD__ );
00308 
00309         return $v;
00310     }
00311 
00315     private static function getVersionLinkedSvn() {
00316         global $IP;
00317 
00318         $info = self::getSvnInfo( $IP );
00319         if ( !isset( $info['checkout-rev'] ) ) {
00320             return false;
00321         }
00322 
00323         $linkText = wfMessage(
00324             'version-svn-revision',
00325             isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
00326             $info['checkout-rev']
00327         )->text();
00328 
00329         if ( isset( $info['viewvc-url'] ) ) {
00330             $version = "[{$info['viewvc-url']} $linkText]";
00331         } else {
00332             $version = $linkText;
00333         }
00334 
00335         return self::getwgVersionLinked() . " $version";
00336     }
00337 
00341     private static function getwgVersionLinked() {
00342         global $wgVersion;
00343         $versionUrl = "";
00344         if ( wfRunHooks( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) {
00345             $versionParts = array();
00346             preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
00347             $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
00348         }
00349 
00350         return "[$versionUrl $wgVersion]";
00351     }
00352 
00358     private static function getVersionLinkedGit() {
00359         global $IP, $wgLang;
00360 
00361         $gitInfo = new GitInfo( $IP );
00362         $headSHA1 = $gitInfo->getHeadSHA1();
00363         if ( !$headSHA1 ) {
00364             return false;
00365         }
00366 
00367         $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
00368 
00369         $gitHeadUrl = $gitInfo->getHeadViewUrl();
00370         if ( $gitHeadUrl !== false ) {
00371             $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
00372         }
00373 
00374         $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
00375         if ( $gitHeadCommitDate ) {
00376             $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
00377         }
00378 
00379         return self::getwgVersionLinked() . " $shortSHA1";
00380     }
00381 
00392     public static function getExtensionTypes() {
00393         if ( self::$extensionTypes === false ) {
00394             self::$extensionTypes = array(
00395                 'specialpage' => wfMessage( 'version-specialpages' )->text(),
00396                 'parserhook' => wfMessage( 'version-parserhooks' )->text(),
00397                 'variable' => wfMessage( 'version-variables' )->text(),
00398                 'media' => wfMessage( 'version-mediahandlers' )->text(),
00399                 'antispam' => wfMessage( 'version-antispam' )->text(),
00400                 'skin' => wfMessage( 'version-skins' )->text(),
00401                 'api' => wfMessage( 'version-api' )->text(),
00402                 'other' => wfMessage( 'version-other' )->text(),
00403             );
00404 
00405             wfRunHooks( 'ExtensionTypes', array( &self::$extensionTypes ) );
00406         }
00407 
00408         return self::$extensionTypes;
00409     }
00410 
00420     public static function getExtensionTypeName( $type ) {
00421         $types = self::getExtensionTypes();
00422 
00423         return isset( $types[$type] ) ? $types[$type] : $types['other'];
00424     }
00425 
00431     public function getExtensionCredits() {
00432         global $wgExtensionCredits;
00433 
00434         if (
00435             count( $wgExtensionCredits ) === 0 ||
00436             // Skins are displayed separately, see getSkinCredits()
00437             ( count( $wgExtensionCredits ) === 1 && isset( $wgExtensionCredits['skin'] ) )
00438         ) {
00439             return '';
00440         }
00441 
00442         $extensionTypes = self::getExtensionTypes();
00443 
00444         $out = Xml::element(
00445                 'h2',
00446                 array( 'id' => 'mw-version-ext' ),
00447                 $this->msg( 'version-extensions' )->text()
00448             ) .
00449             Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ) );
00450 
00451         // Make sure the 'other' type is set to an array.
00452         if ( !array_key_exists( 'other', $wgExtensionCredits ) ) {
00453             $wgExtensionCredits['other'] = array();
00454         }
00455 
00456         // Find all extensions that do not have a valid type and give them the type 'other'.
00457         foreach ( $wgExtensionCredits as $type => $extensions ) {
00458             if ( !array_key_exists( $type, $extensionTypes ) ) {
00459                 $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions );
00460             }
00461         }
00462 
00463         $this->firstExtOpened = false;
00464         // Loop through the extension categories to display their extensions in the list.
00465         foreach ( $extensionTypes as $type => $message ) {
00466             // Skins have a separate section
00467             if ( $type !== 'other' && $type !== 'skin' ) {
00468                 $out .= $this->getExtensionCategory( $type, $message );
00469             }
00470         }
00471 
00472         // We want the 'other' type to be last in the list.
00473         $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
00474 
00475         $out .= Xml::closeElement( 'table' );
00476 
00477         return $out;
00478     }
00479 
00485     public function getSkinCredits() {
00486         global $wgExtensionCredits;
00487         if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) {
00488             return '';
00489         }
00490 
00491         $out = Xml::element(
00492                 'h2',
00493                 array( 'id' => 'mw-version-skin' ),
00494                 $this->msg( 'version-skins' )->text()
00495             ) .
00496             Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ) );
00497 
00498         $this->firstExtOpened = false;
00499         $out .= $this->getExtensionCategory( 'skin', null );
00500 
00501         $out .= Xml::closeElement( 'table' );
00502 
00503         return $out;
00504     }
00505 
00511     protected function getParserTags() {
00512         global $wgParser;
00513 
00514         $tags = $wgParser->getTags();
00515 
00516         if ( count( $tags ) ) {
00517             $out = Html::rawElement(
00518                 'h2',
00519                 array( 'class' => 'mw-headline' ),
00520                 Linker::makeExternalLink(
00521                     '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
00522                     $this->msg( 'version-parser-extensiontags' )->parse(),
00523                     false /* msg()->parse() already escapes */
00524                 )
00525             );
00526 
00527             array_walk( $tags, function ( &$value ) {
00528                 $value = '&lt;' . htmlspecialchars( $value ) . '&gt;';
00529             } );
00530             $out .= $this->listToText( $tags );
00531         } else {
00532             $out = '';
00533         }
00534 
00535         return $out;
00536     }
00537 
00543     protected function getParserFunctionHooks() {
00544         global $wgParser;
00545 
00546         $fhooks = $wgParser->getFunctionHooks();
00547         if ( count( $fhooks ) ) {
00548             $out = Html::rawElement( 'h2', array( 'class' => 'mw-headline' ), Linker::makeExternalLink(
00549                 '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
00550                 $this->msg( 'version-parser-function-hooks' )->parse(),
00551                 false /* msg()->parse() already escapes */
00552             ) );
00553 
00554             $out .= $this->listToText( $fhooks );
00555         } else {
00556             $out = '';
00557         }
00558 
00559         return $out;
00560     }
00561 
00572     protected function getExtensionCategory( $type, $message ) {
00573         global $wgExtensionCredits;
00574 
00575         $out = '';
00576 
00577         if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
00578             $out .= $this->openExtType( $message, 'credits-' . $type );
00579 
00580             usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
00581 
00582             foreach ( $wgExtensionCredits[$type] as $extension ) {
00583                 $out .= $this->getCreditsForExtension( $extension );
00584             }
00585         }
00586 
00587         return $out;
00588     }
00589 
00596     public function compare( $a, $b ) {
00597         if ( $a['name'] === $b['name'] ) {
00598             return 0;
00599         } else {
00600             return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
00601                 ? 1
00602                 : -1;
00603         }
00604     }
00605 
00623     public function getCreditsForExtension( array $extension ) {
00624         $out = $this->getOutput();
00625 
00626         // We must obtain the information for all the bits and pieces!
00627         // ... such as extension names and links
00628         if ( isset( $extension['namemsg'] ) ) {
00629             // Localized name of extension
00630             $extensionName = $this->msg( $extension['namemsg'] )->text();
00631         } elseif ( isset( $extension['name'] ) ) {
00632             // Non localized version
00633             $extensionName = $extension['name'];
00634         } else {
00635             $extensionName = $this->msg( 'version-no-ext-name' )->text();
00636         }
00637 
00638         if ( isset( $extension['url'] ) ) {
00639             $extensionNameLink = Linker::makeExternalLink(
00640                 $extension['url'],
00641                 $extensionName,
00642                 true,
00643                 '',
00644                 array( 'class' => 'mw-version-ext-name' )
00645             );
00646         } else {
00647             $extensionNameLink = $extensionName;
00648         }
00649 
00650         // ... and the version information
00651         // If the extension path is set we will check that directory for GIT and SVN
00652         // metadata in an attempt to extract date and vcs commit metadata.
00653         $canonicalVersion = '&ndash;';
00654         $extensionPath = null;
00655         $vcsVersion = null;
00656         $vcsLink = null;
00657         $vcsDate = null;
00658 
00659         if ( isset( $extension['version'] ) ) {
00660             $canonicalVersion = $out->parseInline( $extension['version'] );
00661         }
00662 
00663         if ( isset( $extension['path'] ) ) {
00664             global $IP;
00665             $extensionPath = dirname( $extension['path'] );
00666             if ( $this->coreId == '' ) {
00667                 wfDebug( 'Looking up core head id' );
00668                 $coreHeadSHA1 = self::getGitHeadSha1( $IP );
00669                 if ( $coreHeadSHA1 ) {
00670                     $this->coreId = $coreHeadSHA1;
00671                 } else {
00672                     $svnInfo = self::getSvnInfo( $IP );
00673                     if ( $svnInfo !== false ) {
00674                         $this->coreId = $svnInfo['checkout-rev'];
00675                     }
00676                 }
00677             }
00678             $cache = wfGetCache( CACHE_ANYTHING );
00679             $memcKey = wfMemcKey( 'specialversion-ext-version-text', $extension['path'], $this->coreId );
00680             list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
00681 
00682             if ( !$vcsVersion ) {
00683                 wfDebug( "Getting VCS info for extension $extensionName" );
00684                 $gitInfo = new GitInfo( $extensionPath );
00685                 $vcsVersion = $gitInfo->getHeadSHA1();
00686                 if ( $vcsVersion !== false ) {
00687                     $vcsVersion = substr( $vcsVersion, 0, 7 );
00688                     $vcsLink = $gitInfo->getHeadViewUrl();
00689                     $vcsDate = $gitInfo->getHeadCommitDate();
00690                 } else {
00691                     $svnInfo = self::getSvnInfo( $extensionPath );
00692                     if ( $svnInfo !== false ) {
00693                         $vcsVersion = $this->msg( 'version-svn-revision', $svnInfo['checkout-rev'] )->text();
00694                         $vcsLink = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : '';
00695                     }
00696                 }
00697                 $cache->set( $memcKey, array( $vcsVersion, $vcsLink, $vcsDate ), 60 * 60 * 24 );
00698             } else {
00699                 wfDebug( "Pulled VCS info for extension $extensionName from cache" );
00700             }
00701         }
00702 
00703         $versionString = Html::rawElement(
00704             'span',
00705             array( 'class' => 'mw-version-ext-version' ),
00706             $canonicalVersion
00707         );
00708 
00709         if ( $vcsVersion ) {
00710             if ( $vcsLink ) {
00711                 $vcsVerString = Linker::makeExternalLink(
00712                     $vcsLink,
00713                     $this->msg( 'version-version', $vcsVersion ),
00714                     true,
00715                     '',
00716                     array( 'class' => 'mw-version-ext-vcs-version' )
00717                 );
00718             } else {
00719                 $vcsVerString = Html::element( 'span',
00720                     array( 'class' => 'mw-version-ext-vcs-version' ),
00721                     "({$vcsVersion})"
00722                 );
00723             }
00724             $versionString .= " {$vcsVerString}";
00725 
00726             if ( $vcsDate ) {
00727                 $vcsTimeString = Html::element( 'span',
00728                     array( 'class' => 'mw-version-ext-vcs-timestamp' ),
00729                     $this->getLanguage()->timeanddate( $vcsDate, true )
00730                 );
00731                 $versionString .= " {$vcsTimeString}";
00732             }
00733             $versionString = Html::rawElement( 'span',
00734                 array( 'class' => 'mw-version-ext-meta-version' ),
00735                 $versionString
00736             );
00737         }
00738 
00739         // ... and license information; if a license file exists we
00740         // will link to it
00741         $licenseLink = '';
00742         if ( isset( $extension['license-name'] ) ) {
00743             $licenseLink = Linker::link(
00744                 $this->getPageTitle( 'License/' . $extensionName ),
00745                 $out->parseInline( $extension['license-name'] ),
00746                 array( 'class' => 'mw-version-ext-license' )
00747             );
00748         } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
00749             $licenseLink = Linker::link(
00750                 $this->getPageTitle( 'License/' . $extensionName ),
00751                 $this->msg( 'version-ext-license' ),
00752                 array( 'class' => 'mw-version-ext-license' )
00753             );
00754         }
00755 
00756         // ... and generate the description; which can be a parameterized l10n message
00757         // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
00758         // up string
00759         if ( isset( $extension['descriptionmsg'] ) ) {
00760             // Localized description of extension
00761             $descriptionMsg = $extension['descriptionmsg'];
00762 
00763             if ( is_array( $descriptionMsg ) ) {
00764                 $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
00765                 array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
00766                 array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
00767                 $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
00768             } else {
00769                 $description = $this->msg( $descriptionMsg )->text();
00770             }
00771         } elseif ( isset( $extension['description'] ) ) {
00772             // Non localized version
00773             $description = $extension['description'];
00774         } else {
00775             $description = '';
00776         }
00777         $description = $out->parseInline( $description );
00778 
00779         // ... now get the authors for this extension
00780         $authors = isset( $extension['author'] ) ? $extension['author'] : array();
00781         $authors = $this->listAuthors( $authors, $extensionName, $extensionPath );
00782 
00783         // Finally! Create the table
00784         $html = Html::openElement( 'tr', array(
00785                 'class' => 'mw-version-ext',
00786                 'id' => "mw-version-ext-{$extensionName}"
00787             )
00788         );
00789 
00790         $html .= Html::rawElement( 'td', array(), $extensionNameLink );
00791         $html .= Html::rawElement( 'td', array(), $versionString );
00792         $html .= Html::rawElement( 'td', array(), $licenseLink );
00793         $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-description' ), $description );
00794         $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-authors' ), $authors );
00795 
00796         $html .= Html::closeElement( 'td' );
00797 
00798         return $html;
00799     }
00800 
00806     private function getWgHooks() {
00807         global $wgSpecialVersionShowHooks, $wgHooks;
00808 
00809         if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
00810             $myWgHooks = $wgHooks;
00811             ksort( $myWgHooks );
00812 
00813             $ret = array();
00814             $ret[] = '== {{int:version-hooks}} ==';
00815             $ret[] = Html::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) );
00816             $ret[] = Html::openElement( 'tr' );
00817             $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-name' )->text() );
00818             $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-subscribedby' )->text() );
00819             $ret[] = Html::closeElement( 'tr' );
00820 
00821             foreach ( $myWgHooks as $hook => $hooks ) {
00822                 $ret[] = Html::openElement( 'tr' );
00823                 $ret[] = Html::element( 'td', array(), $hook );
00824                 $ret[] = Html::element( 'td', array(), $this->listToText( $hooks ) );
00825                 $ret[] = Html::closeElement( 'tr' );
00826             }
00827 
00828             $ret[] = Html::closeElement( 'table' );
00829 
00830             return implode( "\n", $ret );
00831         } else {
00832             return '';
00833         }
00834     }
00835 
00836     private function openExtType( $text = null, $name = null ) {
00837         $out = '';
00838 
00839         $opt = array( 'colspan' => 5 );
00840         if ( $this->firstExtOpened ) {
00841             // Insert a spacing line
00842             $out .= Html::rawElement( 'tr', array( 'class' => 'sv-space' ),
00843                 Html::element( 'td', $opt )
00844             );
00845         }
00846         $this->firstExtOpened = true;
00847 
00848         if ( $name ) {
00849             $opt['id'] = "sv-$name";
00850         }
00851 
00852         if ( $text !== null ) {
00853             $out .= Html::rawElement( 'tr', array(),
00854                 Html::element( 'th', $opt, $text )
00855             );
00856         }
00857 
00858         $firstHeadingMsg = ( $name === 'credits-skin' )
00859             ? 'version-skin-colheader-name'
00860             : 'version-ext-colheader-name';
00861         $out .= Html::openElement( 'tr' );
00862         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00863             $this->msg( $firstHeadingMsg )->text() );
00864         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00865             $this->msg( 'version-ext-colheader-version' )->text() );
00866         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00867             $this->msg( 'version-ext-colheader-license' )->text() );
00868         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00869             $this->msg( 'version-ext-colheader-description' )->text() );
00870         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00871             $this->msg( 'version-ext-colheader-credits' )->text() );
00872         $out .= Html::closeElement( 'tr' );
00873 
00874         return $out;
00875     }
00876 
00882     private function IPInfo() {
00883         $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
00884 
00885         return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
00886     }
00887 
00908     public function listAuthors( $authors, $extName, $extDir ) {
00909         $hasOthers = false;
00910 
00911         $list = array();
00912         foreach ( (array)$authors as $item ) {
00913             if ( $item == '...' ) {
00914                 $hasOthers = true;
00915 
00916                 if ( $this->getExtAuthorsFileName( $extDir ) ) {
00917                     $text = Linker::link(
00918                         $this->getPageTitle( "Credits/$extName" ),
00919                         $this->msg( 'version-poweredby-others' )->text()
00920                     );
00921                 } else {
00922                     $text = $this->msg( 'version-poweredby-others' )->text();
00923                 }
00924                 $list[] = $text;
00925             } elseif ( substr( $item, -5 ) == ' ...]' ) {
00926                 $hasOthers = true;
00927                 $list[] = $this->getOutput()->parseInline(
00928                     substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
00929                 );
00930             } else {
00931                 $list[] = $this->getOutput()->parseInline( $item );
00932             }
00933         }
00934 
00935         if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
00936             $list[] = $text = Linker::link(
00937                 $this->getPageTitle( "Credits/$extName" ),
00938                 $this->msg( 'version-poweredby-others' )->text()
00939             );
00940         }
00941 
00942         return $this->listToText( $list, false );
00943     }
00944 
00956     public static function getExtAuthorsFileName( $extDir ) {
00957         if ( !$extDir ) {
00958             return false;
00959         }
00960 
00961         foreach ( scandir( $extDir ) as $file ) {
00962             $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
00963             if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt)?$/', $file ) &&
00964                 is_readable( $fullPath ) &&
00965                 is_file( $fullPath )
00966             ) {
00967                 return $fullPath;
00968             }
00969         }
00970 
00971         return false;
00972     }
00973 
00985     public static function getExtLicenseFileName( $extDir ) {
00986         if ( !$extDir ) {
00987             return false;
00988         }
00989 
00990         foreach ( scandir( $extDir ) as $file ) {
00991             $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
00992             if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
00993                 is_readable( $fullPath ) &&
00994                 is_file( $fullPath )
00995             ) {
00996                 return $fullPath;
00997             }
00998         }
00999 
01000         return false;
01001     }
01002 
01011     public function listToText( $list, $sort = true ) {
01012         if ( !count( $list ) ) {
01013             return '';
01014         }
01015         if ( $sort ) {
01016             sort( $list );
01017         }
01018 
01019         return $this->getLanguage()
01020             ->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
01021     }
01022 
01031     public static function arrayToString( $list ) {
01032         if ( is_array( $list ) && count( $list ) == 1 ) {
01033             $list = $list[0];
01034         }
01035         if ( is_object( $list ) ) {
01036             $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
01037 
01038             return $class;
01039         } elseif ( !is_array( $list ) ) {
01040             return $list;
01041         } else {
01042             if ( is_object( $list[0] ) ) {
01043                 $class = get_class( $list[0] );
01044             } else {
01045                 $class = $list[0];
01046             }
01047 
01048             return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
01049         }
01050     }
01051 
01068     public static function getSvnInfo( $dir ) {
01069         // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
01070         $entries = $dir . '/.svn/entries';
01071 
01072         if ( !file_exists( $entries ) ) {
01073             return false;
01074         }
01075 
01076         $lines = file( $entries );
01077         if ( !count( $lines ) ) {
01078             return false;
01079         }
01080 
01081         // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
01082         if ( preg_match( '/^<\?xml/', $lines[0] ) ) {
01083             // subversion is release <= 1.3
01084             if ( !function_exists( 'simplexml_load_file' ) ) {
01085                 // We could fall back to expat... YUCK
01086                 return false;
01087             }
01088 
01089             // SimpleXml whines about the xmlns...
01090             wfSuppressWarnings();
01091             $xml = simplexml_load_file( $entries );
01092             wfRestoreWarnings();
01093 
01094             if ( $xml ) {
01095                 foreach ( $xml->entry as $entry ) {
01096                     if ( $xml->entry[0]['name'] == '' ) {
01097                         // The directory entry should always have a revision marker.
01098                         if ( $entry['revision'] ) {
01099                             return array( 'checkout-rev' => intval( $entry['revision'] ) );
01100                         }
01101                     }
01102                 }
01103             }
01104 
01105             return false;
01106         }
01107 
01108         // Subversion is release 1.4 or above.
01109         if ( count( $lines ) < 11 ) {
01110             return false;
01111         }
01112 
01113         $info = array(
01114             'checkout-rev' => intval( trim( $lines[3] ) ),
01115             'url' => trim( $lines[4] ),
01116             'repo-url' => trim( $lines[5] ),
01117             'directory-rev' => intval( trim( $lines[10] ) )
01118         );
01119 
01120         if ( isset( self::$viewvcUrls[$info['repo-url']] ) ) {
01121             $viewvc = str_replace(
01122                 $info['repo-url'],
01123                 self::$viewvcUrls[$info['repo-url']],
01124                 $info['url']
01125             );
01126 
01127             $viewvc .= '/?pathrev=';
01128             $viewvc .= urlencode( $info['checkout-rev'] );
01129             $info['viewvc-url'] = $viewvc;
01130         }
01131 
01132         return $info;
01133     }
01134 
01142     public static function getSvnRevision( $dir ) {
01143         $info = self::getSvnInfo( $dir );
01144 
01145         if ( $info === false ) {
01146             return false;
01147         } elseif ( isset( $info['checkout-rev'] ) ) {
01148             return $info['checkout-rev'];
01149         } else {
01150             return false;
01151         }
01152     }
01153 
01158     public static function getGitHeadSha1( $dir ) {
01159         $repo = new GitInfo( $dir );
01160 
01161         return $repo->getHeadSHA1();
01162     }
01163 
01168     public static function getGitCurrentBranch( $dir ) {
01169         $repo = new GitInfo( $dir );
01170         return $repo->getCurrentBranch();
01171     }
01172 
01177     public function getEntryPointInfo() {
01178         global $wgArticlePath, $wgScriptPath;
01179         $scriptPath = $wgScriptPath ? $wgScriptPath : "/";
01180         $entryPoints = array(
01181             'version-entrypoints-articlepath' => $wgArticlePath,
01182             'version-entrypoints-scriptpath' => $scriptPath,
01183             'version-entrypoints-index-php' => wfScript( 'index' ),
01184             'version-entrypoints-api-php' => wfScript( 'api' ),
01185             'version-entrypoints-load-php' => wfScript( 'load' ),
01186         );
01187 
01188         $language = $this->getLanguage();
01189         $thAttribures = array(
01190             'dir' => $language->getDir(),
01191             'lang' => $language->getCode()
01192         );
01193         $out = Html::element(
01194                 'h2',
01195                 array( 'id' => 'mw-version-entrypoints' ),
01196                 $this->msg( 'version-entrypoints' )->text()
01197             ) .
01198             Html::openElement( 'table',
01199                 array(
01200                     'class' => 'wikitable plainlinks',
01201                     'id' => 'mw-version-entrypoints-table',
01202                     'dir' => 'ltr',
01203                     'lang' => 'en'
01204                 )
01205             ) .
01206             Html::openElement( 'tr' ) .
01207             Html::element(
01208                 'th',
01209                 $thAttribures,
01210                 $this->msg( 'version-entrypoints-header-entrypoint' )->text()
01211             ) .
01212             Html::element(
01213                 'th',
01214                 $thAttribures,
01215                 $this->msg( 'version-entrypoints-header-url' )->text()
01216             ) .
01217             Html::closeElement( 'tr' );
01218 
01219         foreach ( $entryPoints as $message => $value ) {
01220             $url = wfExpandUrl( $value, PROTO_RELATIVE );
01221             $out .= Html::openElement( 'tr' ) .
01222                 // ->text() looks like it should be ->parse(), but this function
01223                 // returns wikitext, not HTML, boo
01224                 Html::rawElement( 'td', array(), $this->msg( $message )->text() ) .
01225                 Html::rawElement( 'td', array(), Html::rawElement( 'code', array(), "[$url $value]" ) ) .
01226                 Html::closeElement( 'tr' );
01227         }
01228 
01229         $out .= Html::closeElement( 'table' );
01230 
01231         return $out;
01232     }
01233 
01234     protected function getGroupName() {
01235         return 'wiki';
01236     }
01237 }