MediaWiki  REL1_23
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 
00054     public function execute( $par ) {
00055         global $IP, $wgExtensionCredits;
00056 
00057         $this->setHeaders();
00058         $this->outputHeader();
00059         $out = $this->getOutput();
00060         $out->allowClickjacking();
00061 
00062         // Explode the sub page information into useful bits
00063         $parts = explode( '/', (string)$par );
00064         $extNode = null;
00065         if ( isset( $parts[1] ) ) {
00066             $extName = str_replace( '_', ' ', $parts[1] );
00067             // Find it!
00068             foreach ( $wgExtensionCredits as $group => $extensions ) {
00069                 foreach ( $extensions as $ext ) {
00070                     if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
00071                         $extNode = &$ext;
00072                         break 2;
00073                     }
00074                 }
00075             }
00076             if ( !$extNode ) {
00077                 $out->setStatusCode( 404 );
00078             }
00079         } else {
00080             $extName = 'MediaWiki';
00081         }
00082 
00083         // Now figure out what to do
00084         switch ( strtolower( $parts[0] ) ) {
00085             case 'credits':
00086                 $wikiText = '{{int:version-credits-not-found}}';
00087                 if ( $extName === 'MediaWiki' ) {
00088                     $wikiText = file_get_contents( $IP . '/CREDITS' );
00089                 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
00090                     $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
00091                     if ( $file ) {
00092                         $wikiText = file_get_contents( $file );
00093                         if ( substr( $file, -4 ) === '.txt' ) {
00094                             $wikiText = Html::element( 'pre', array(), $wikiText );
00095                         }
00096                     }
00097                 }
00098 
00099                 $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
00100                 $out->addWikiText( $wikiText );
00101                 break;
00102 
00103             case 'license':
00104                 $wikiText = '{{int:version-license-not-found}}';
00105                 if ( $extName === 'MediaWiki' ) {
00106                     $wikiText = file_get_contents( $IP . '/COPYING' );
00107                 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
00108                     $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
00109                     if ( $file ) {
00110                         $wikiText = file_get_contents( $file );
00111                         if ( !isset( $extNode['license-name'] ) ) {
00112                             // If the developer did not explicitly set license-name they probably
00113                             // are unaware that we're now sucking this file in and thus it's probably
00114                             // not wikitext friendly.
00115                             $wikiText = "<pre>$wikiText</pre>";
00116                         }
00117                     }
00118                 }
00119 
00120                 $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
00121                 $out->addWikiText( $wikiText );
00122                 break;
00123 
00124             default:
00125                 $out->addModules( 'mediawiki.special.version' );
00126                 $out->addWikiText(
00127                     $this->getMediaWikiCredits() .
00128                     $this->softwareInformation() .
00129                     $this->getEntryPointInfo()
00130                 );
00131                 $out->addHtml(
00132                     $this->getExtensionCredits() .
00133                     $this->getParserTags() .
00134                     $this->getParserFunctionHooks()
00135                 );
00136                 $out->addWikiText( $this->getWgHooks() );
00137                 $out->addHTML( $this->IPInfo() );
00138 
00139                 break;
00140         }
00141     }
00142 
00148     private static function getMediaWikiCredits() {
00149         $ret = Xml::element(
00150             'h2',
00151             array( 'id' => 'mw-version-license' ),
00152             wfMessage( 'version-license' )->text()
00153         );
00154 
00155         // This text is always left-to-right.
00156         $ret .= '<div class="plainlinks">';
00157         $ret .= "__NOTOC__
00158         " . self::getCopyrightAndAuthorList() . "\n
00159         " . wfMessage( 'version-license-info' )->text();
00160         $ret .= '</div>';
00161 
00162         return str_replace( "\t\t", '', $ret ) . "\n";
00163     }
00164 
00170     public static function getCopyrightAndAuthorList() {
00171         global $wgLang;
00172 
00173         if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
00174             $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' .
00175                 wfMessage( 'version-poweredby-others' )->text() . ']';
00176         } else {
00177             $othersLink = '[[Special:Version/Credits|' .
00178                 wfMessage( 'version-poweredby-others' )->text() . ']]';
00179         }
00180 
00181         $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
00182             wfMessage( 'version-poweredby-translators' )->text() . ']';
00183 
00184         $authorList = array(
00185             'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
00186             'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
00187             'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
00188             'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
00189             'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
00190             'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
00191             'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
00192             'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink,
00193             $translatorsLink
00194         );
00195 
00196         return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
00197             $wgLang->listToText( $authorList ) )->text();
00198     }
00199 
00205     static function softwareInformation() {
00206         $dbr = wfGetDB( DB_SLAVE );
00207 
00208         // Put the software in an array of form 'name' => 'version'. All messages should
00209         // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
00210         // wikimarkup can be used.
00211         $software = array();
00212         $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
00213         $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . PHP_SAPI . ")";
00214         $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
00215 
00216         // Allow a hook to add/remove items.
00217         wfRunHooks( 'SoftwareInfo', array( &$software ) );
00218 
00219         $out = Xml::element(
00220                 'h2',
00221                 array( 'id' => 'mw-version-software' ),
00222                 wfMessage( 'version-software' )->text()
00223             ) .
00224                 Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) .
00225                 "<tr>
00226                     <th>" . wfMessage( 'version-software-product' )->text() . "</th>
00227                     <th>" . wfMessage( 'version-software-version' )->text() . "</th>
00228                 </tr>\n";
00229 
00230         foreach ( $software as $name => $version ) {
00231             $out .= "<tr>
00232                     <td>" . $name . "</td>
00233                     <td dir=\"ltr\">" . $version . "</td>
00234                 </tr>\n";
00235         }
00236 
00237         return $out . Xml::closeElement( 'table' );
00238     }
00239 
00246     public static function getVersion( $flags = '' ) {
00247         global $wgVersion, $IP;
00248         wfProfileIn( __METHOD__ );
00249 
00250         $gitInfo = self::getGitHeadSha1( $IP );
00251         $svnInfo = self::getSvnInfo( $IP );
00252         if ( !$svnInfo && !$gitInfo ) {
00253             $version = $wgVersion;
00254         } elseif ( $gitInfo && $flags === 'nodb' ) {
00255             $shortSha1 = substr( $gitInfo, 0, 7 );
00256             $version = "$wgVersion ($shortSha1)";
00257         } elseif ( $gitInfo ) {
00258             $shortSha1 = substr( $gitInfo, 0, 7 );
00259             $shortSha1 = wfMessage( 'parentheses' )->params( $shortSha1 )->escaped();
00260             $version = "$wgVersion $shortSha1";
00261         } elseif ( $flags === 'nodb' ) {
00262             $version = "$wgVersion (r{$svnInfo['checkout-rev']})";
00263         } else {
00264             $version = $wgVersion . ' ' .
00265                 wfMessage(
00266                     'version-svn-revision',
00267                     isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
00268                     $info['checkout-rev']
00269                 )->text();
00270         }
00271 
00272         wfProfileOut( __METHOD__ );
00273 
00274         return $version;
00275     }
00276 
00285     public static function getVersionLinked() {
00286         global $wgVersion;
00287         wfProfileIn( __METHOD__ );
00288 
00289         $gitVersion = self::getVersionLinkedGit();
00290         if ( $gitVersion ) {
00291             $v = $gitVersion;
00292         } else {
00293             $svnVersion = self::getVersionLinkedSvn();
00294             if ( $svnVersion ) {
00295                 $v = $svnVersion;
00296             } else {
00297                 $v = $wgVersion; // fallback
00298             }
00299         }
00300 
00301         wfProfileOut( __METHOD__ );
00302 
00303         return $v;
00304     }
00305 
00309     private static function getVersionLinkedSvn() {
00310         global $IP;
00311 
00312         $info = self::getSvnInfo( $IP );
00313         if ( !isset( $info['checkout-rev'] ) ) {
00314             return false;
00315         }
00316 
00317         $linkText = wfMessage(
00318             'version-svn-revision',
00319             isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
00320             $info['checkout-rev']
00321         )->text();
00322 
00323         if ( isset( $info['viewvc-url'] ) ) {
00324             $version = "[{$info['viewvc-url']} $linkText]";
00325         } else {
00326             $version = $linkText;
00327         }
00328 
00329         return self::getwgVersionLinked() . " $version";
00330     }
00331 
00335     private static function getwgVersionLinked() {
00336         global $wgVersion;
00337         $versionUrl = "";
00338         if ( wfRunHooks( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) {
00339             $versionParts = array();
00340             preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
00341             $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
00342         }
00343 
00344         return "[$versionUrl $wgVersion]";
00345     }
00346 
00352     private static function getVersionLinkedGit() {
00353         global $IP, $wgLang;
00354 
00355         $gitInfo = new GitInfo( $IP );
00356         $headSHA1 = $gitInfo->getHeadSHA1();
00357         if ( !$headSHA1 ) {
00358             return false;
00359         }
00360 
00361         $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
00362 
00363         $gitHeadUrl = $gitInfo->getHeadViewUrl();
00364         if ( $gitHeadUrl !== false ) {
00365             $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
00366         }
00367 
00368         $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
00369         if ( $gitHeadCommitDate ) {
00370             $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
00371         }
00372 
00373         return self::getwgVersionLinked() . " $shortSHA1";
00374     }
00375 
00388     public static function getExtensionTypes() {
00389         if ( self::$extensionTypes === false ) {
00390             self::$extensionTypes = array(
00391                 'specialpage' => wfMessage( 'version-specialpages' )->text(),
00392                 'parserhook' => wfMessage( 'version-parserhooks' )->text(),
00393                 'variable' => wfMessage( 'version-variables' )->text(),
00394                 'media' => wfMessage( 'version-mediahandlers' )->text(),
00395                 'antispam' => wfMessage( 'version-antispam' )->text(),
00396                 'skin' => wfMessage( 'version-skins' )->text(),
00397                 'api' => wfMessage( 'version-api' )->text(),
00398                 'other' => wfMessage( 'version-other' )->text(),
00399             );
00400 
00401             wfRunHooks( 'ExtensionTypes', array( &self::$extensionTypes ) );
00402         }
00403 
00404         return self::$extensionTypes;
00405     }
00406 
00416     public static function getExtensionTypeName( $type ) {
00417         $types = self::getExtensionTypes();
00418 
00419         return isset( $types[$type] ) ? $types[$type] : $types['other'];
00420     }
00421 
00427     function getExtensionCredits() {
00428         global $wgExtensionCredits;
00429 
00430         if ( !count( $wgExtensionCredits ) ) {
00431             return '';
00432         }
00433 
00434         $extensionTypes = self::getExtensionTypes();
00435 
00439         wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
00440 
00441         $out = Xml::element(
00442                 'h2',
00443                 array( 'id' => 'mw-version-ext' ),
00444                 $this->msg( 'version-extensions' )->text()
00445             ) .
00446             Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ) );
00447 
00448         // Make sure the 'other' type is set to an array.
00449         if ( !array_key_exists( 'other', $wgExtensionCredits ) ) {
00450             $wgExtensionCredits['other'] = array();
00451         }
00452 
00453         // Find all extensions that do not have a valid type and give them the type 'other'.
00454         foreach ( $wgExtensionCredits as $type => $extensions ) {
00455             if ( !array_key_exists( $type, $extensionTypes ) ) {
00456                 $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions );
00457             }
00458         }
00459 
00460         // Loop through the extension categories to display their extensions in the list.
00461         foreach ( $extensionTypes as $type => $message ) {
00462             if ( $type != 'other' ) {
00463                 $out .= $this->getExtensionCategory( $type, $message );
00464             }
00465         }
00466 
00467         // We want the 'other' type to be last in the list.
00468         $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
00469 
00470         $out .= Xml::closeElement( 'table' );
00471 
00472         return $out;
00473     }
00474 
00480     protected function getParserTags() {
00481         global $wgParser;
00482 
00483         $tags = $wgParser->getTags();
00484 
00485         if ( count( $tags ) ) {
00486             $out = Html::rawElement(
00487                 'h2',
00488                 array( 'class' => 'mw-headline' ),
00489                 Linker::makeExternalLink(
00490                     '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
00491                     $this->msg( 'version-parser-extensiontags' )->parse(),
00492                     false /* msg()->parse() already escapes */
00493                 )
00494             );
00495 
00496             array_walk( $tags, function ( &$value ) {
00497                 $value = '&lt;' . htmlentities( $value ) . '&gt;';
00498             } );
00499             $out .= $this->listToText( $tags );
00500         } else {
00501             $out = '';
00502         }
00503 
00504         return $out;
00505     }
00506 
00512     protected function getParserFunctionHooks() {
00513         global $wgParser;
00514 
00515         $fhooks = $wgParser->getFunctionHooks();
00516         if ( count( $fhooks ) ) {
00517             $out = Html::rawElement( 'h2', array( 'class' => 'mw-headline' ), Linker::makeExternalLink(
00518                 '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
00519                 $this->msg( 'version-parser-function-hooks' )->parse(),
00520                 false /* msg()->parse() already escapes */
00521             ) );
00522 
00523             $out .= $this->listToText( $fhooks );
00524         } else {
00525             $out = '';
00526         }
00527 
00528         return $out;
00529     }
00530 
00541     protected function getExtensionCategory( $type, $message ) {
00542         global $wgExtensionCredits;
00543 
00544         $out = '';
00545 
00546         if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
00547             $out .= $this->openExtType( $message, 'credits-' . $type );
00548 
00549             usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
00550 
00551             foreach ( $wgExtensionCredits[$type] as $extension ) {
00552                 $out .= $this->getCreditsForExtension( $extension );
00553             }
00554         }
00555 
00556         return $out;
00557     }
00558 
00565     function compare( $a, $b ) {
00566         if ( $a['name'] === $b['name'] ) {
00567             return 0;
00568         } else {
00569             return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
00570                 ? 1
00571                 : -1;
00572         }
00573     }
00574 
00592     function getCreditsForExtension( array $extension ) {
00593         $out = $this->getOutput();
00594 
00595         // We must obtain the information for all the bits and pieces!
00596         // ... such as extension names and links
00597         $extensionName = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
00598         if ( isset( $extension['url'] ) ) {
00599             $extensionNameLink = Linker::makeExternalLink(
00600                 $extension['url'],
00601                 $extensionName,
00602                 true,
00603                 '',
00604                 array( 'class' => 'mw-version-ext-name' )
00605             );
00606         } else {
00607             $extensionNameLink = $extensionName;
00608         }
00609 
00610         // ... and the version information
00611         // If the extension path is set we will check that directory for GIT and SVN
00612         // metadata in an attempt to extract date and vcs commit metadata.
00613         $canonicalVersion = '&ndash;';
00614         $extensionPath = null;
00615         $vcsVersion = null;
00616         $vcsLink = null;
00617         $vcsDate = null;
00618 
00619         if ( isset( $extension['version'] ) ) {
00620             $canonicalVersion = $out->parseInline( $extension['version'] );
00621         }
00622 
00623         if ( isset( $extension['path'] ) ) {
00624             global $IP;
00625             if ( $this->coreId == '' ) {
00626                 wfDebug( 'Looking up core head id' );
00627                 $coreHeadSHA1 = self::getGitHeadSha1( $IP );
00628                 if ( $coreHeadSHA1 ) {
00629                     $this->coreId = $coreHeadSHA1;
00630                 } else {
00631                     $svnInfo = self::getSvnInfo( $IP );
00632                     if ( $svnInfo !== false ) {
00633                         $this->coreId = $svnInfo['checkout-rev'];
00634                     }
00635                 }
00636             }
00637             $cache = wfGetCache( CACHE_ANYTHING );
00638             $memcKey = wfMemcKey( 'specialversion-ext-version-text', $extension['path'], $this->coreId );
00639             list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
00640 
00641             if ( !$vcsVersion ) {
00642                 wfDebug( "Getting VCS info for extension $extensionName" );
00643                 $extensionPath = dirname( $extension['path'] );
00644                 $gitInfo = new GitInfo( $extensionPath );
00645                 $vcsVersion = $gitInfo->getHeadSHA1();
00646                 if ( $vcsVersion !== false ) {
00647                     $vcsVersion = substr( $vcsVersion, 0, 7 );
00648                     $vcsLink = $gitInfo->getHeadViewUrl();
00649                     $vcsDate = $gitInfo->getHeadCommitDate();
00650                 } else {
00651                     $svnInfo = self::getSvnInfo( $extensionPath );
00652                     if ( $svnInfo !== false ) {
00653                         $vcsVersion = $this->msg( 'version-svn-revision', $svnInfo['checkout-rev'] )->text();
00654                         $vcsLink = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : '';
00655                     }
00656                 }
00657                 $cache->set( $memcKey, array( $vcsVersion, $vcsLink, $vcsDate ), 60 * 60 * 24 );
00658             } else {
00659                 wfDebug( "Pulled VCS info for extension $extensionName from cache" );
00660             }
00661         }
00662 
00663         $versionString = Html::rawElement(
00664             'span',
00665             array( 'class' => 'mw-version-ext-version' ),
00666             $canonicalVersion
00667         );
00668 
00669         if ( $vcsVersion ) {
00670             if ( $vcsLink ) {
00671                 $vcsVerString = Linker::makeExternalLink(
00672                     $vcsLink,
00673                     $this->msg( 'version-version', $vcsVersion ),
00674                     true,
00675                     '',
00676                     array( 'class' => 'mw-version-ext-vcs-version' )
00677                 );
00678             } else {
00679                 $vcsVerString = Html::element( 'span',
00680                     array( 'class' => 'mw-version-ext-vcs-version' ),
00681                     "({$vcsVersion})"
00682                 );
00683             }
00684             $versionString .= " {$vcsVerString}";
00685 
00686             if ( $vcsDate ) {
00687                 $vcsTimeString = Html::element( 'span',
00688                     array( 'class' => 'mw-version-ext-vcs-timestamp' ),
00689                     $this->getLanguage()->timeanddate( $vcsDate )
00690                 );
00691                 $versionString .= " {$vcsTimeString}";
00692             }
00693             $versionString = Html::rawElement( 'span',
00694                 array( 'class' => 'mw-version-ext-meta-version' ),
00695                 $versionString
00696             );
00697         }
00698 
00699         // ... and license information; if a license file exists we
00700         // will link to it
00701         $licenseLink = '';
00702         if ( isset( $extension['license-name'] ) ) {
00703             $licenseLink = Linker::link(
00704                 $this->getPageTitle( 'License/' . $extensionName ),
00705                 $out->parseInline( $extension['license-name'] ),
00706                 array( 'class' => 'mw-version-ext-license' )
00707             );
00708         } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
00709             $licenseLink = Linker::link(
00710                 $this->getPageTitle( 'License/' . $extensionName ),
00711                 $this->msg( 'version-ext-license' ),
00712                 array( 'class' => 'mw-version-ext-license' )
00713             );
00714         }
00715 
00716         // ... and generate the description; which can be a parameterized l10n message
00717         // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
00718         // up string
00719         if ( isset( $extension['descriptionmsg'] ) ) {
00720             // Localized description of extension
00721             $descriptionMsg = $extension['descriptionmsg'];
00722 
00723             if ( is_array( $descriptionMsg ) ) {
00724                 $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
00725                 array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
00726                 array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
00727                 $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
00728             } else {
00729                 $description = $this->msg( $descriptionMsg )->text();
00730             }
00731         } elseif ( isset( $extension['description'] ) ) {
00732             // Non localized version
00733             $description = $extension['description'];
00734         } else {
00735             $description = '';
00736         }
00737         $description = $out->parseInline( $description );
00738 
00739         // ... now get the authors for this extension
00740         $authors = isset( $extension['author'] ) ? $extension['author'] : array();
00741         $authors = $this->listAuthors( $authors, $extensionName, $extensionPath );
00742 
00743         // Finally! Create the table
00744         $html = Html::openElement( 'tr', array(
00745                 'class' => 'mw-version-ext',
00746                 'id' => "mw-version-ext-{$extensionName}"
00747             )
00748         );
00749 
00750         $html .= Html::rawElement( 'td', array(), $extensionNameLink );
00751         $html .= Html::rawElement( 'td', array(), $versionString );
00752         $html .= Html::rawElement( 'td', array(), $licenseLink );
00753         $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-description' ), $description );
00754         $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-authors' ), $authors );
00755 
00756         $html .= Html::closeElement( 'td' );
00757 
00758         return $html;
00759     }
00760 
00766     private function getWgHooks() {
00767         global $wgSpecialVersionShowHooks, $wgHooks;
00768 
00769         if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
00770             $myWgHooks = $wgHooks;
00771             ksort( $myWgHooks );
00772 
00773             $ret = array();
00774             $ret[] = '== {{int:version-hooks}} ==';
00775             $ret[] = Html::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) );
00776             $ret[] = Html::openElement( 'tr' );
00777             $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-name' )->text() );
00778             $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-subscribedby' )->text() );
00779             $ret[] = Html::closeElement( 'tr' );
00780 
00781             foreach ( $myWgHooks as $hook => $hooks ) {
00782                 $ret[] = Html::openElement( 'tr' );
00783                 $ret[] = Html::element( 'td', array(), $hook );
00784                 $ret[] = Html::element( 'td', array(), $this->listToText( $hooks ) );
00785                 $ret[] = Html::closeElement( 'tr' );
00786             }
00787 
00788             $ret[] = Html::closeElement( 'table' );
00789 
00790             return implode( "\n", $ret );
00791         } else {
00792             return '';
00793         }
00794     }
00795 
00796     private function openExtType( $text, $name = null ) {
00797         $out = '';
00798 
00799         $opt = array( 'colspan' => 5 );
00800         if ( $this->firstExtOpened ) {
00801             // Insert a spacing line
00802             $out .= Html::rawElement( 'tr', array( 'class' => 'sv-space' ),
00803                 Html::element( 'td', $opt )
00804             );
00805         }
00806         $this->firstExtOpened = true;
00807 
00808         if ( $name ) {
00809             $opt['id'] = "sv-$name";
00810         }
00811 
00812         $out .= Html::rawElement( 'tr', array(),
00813             Html::element( 'th', $opt, $text )
00814         );
00815 
00816         $out .= Html::openElement( 'tr' );
00817         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00818             $this->msg( 'version-ext-colheader-name' )->text() );
00819         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00820             $this->msg( 'version-ext-colheader-version' )->text() );
00821         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00822             $this->msg( 'version-ext-colheader-license' )->text() );
00823         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00824             $this->msg( 'version-ext-colheader-description' )->text() );
00825         $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
00826             $this->msg( 'version-ext-colheader-credits' )->text() );
00827         $out .= Html::closeElement( 'tr' );
00828 
00829         return $out;
00830     }
00831 
00837     private function IPInfo() {
00838         $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
00839 
00840         return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
00841     }
00842 
00863     function listAuthors( $authors, $extName, $extDir ) {
00864         $hasOthers = false;
00865 
00866         $list = array();
00867         foreach ( (array)$authors as $item ) {
00868             if ( $item == '...' ) {
00869                 $hasOthers = true;
00870 
00871                 if ( $this->getExtAuthorsFileName( $extDir ) ) {
00872                     $text = Linker::link(
00873                         $this->getPageTitle( "Credits/$extName" ),
00874                         $this->msg( 'version-poweredby-others' )->text()
00875                     );
00876                 } else {
00877                     $text = $this->msg( 'version-poweredby-others' )->text();
00878                 }
00879                 $list[] = $text;
00880             } elseif ( substr( $item, -5 ) == ' ...]' ) {
00881                 $hasOthers = true;
00882                 $list[] = $this->getOutput()->parseInline(
00883                     substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
00884                 );
00885             } else {
00886                 $list[] = $this->getOutput()->parseInline( $item );
00887             }
00888         }
00889 
00890         if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
00891             $list[] = $text = Linker::link(
00892                 $this->getPageTitle( "Credits/$extName" ),
00893                 $this->msg( 'version-poweredby-others' )->text()
00894             );
00895         }
00896 
00897         return $this->listToText( $list, false );
00898     }
00899 
00911     public static function getExtAuthorsFileName( $extDir ) {
00912         if ( !$extDir ) {
00913             return false;
00914         }
00915 
00916         foreach ( scandir( $extDir ) as $file ) {
00917             $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
00918             if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt)?$/', $file ) &&
00919                 is_readable( $fullPath ) &&
00920                 is_file( $fullPath )
00921             ) {
00922                 return $fullPath;
00923             }
00924         }
00925 
00926         return false;
00927     }
00928 
00940     public static function getExtLicenseFileName( $extDir ) {
00941         if ( !$extDir ) {
00942             return false;
00943         }
00944 
00945         foreach ( scandir( $extDir ) as $file ) {
00946             $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
00947             if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
00948                 is_readable( $fullPath ) &&
00949                 is_file( $fullPath )
00950             ) {
00951                 return $fullPath;
00952             }
00953         }
00954 
00955         return false;
00956     }
00957 
00966     function listToText( $list, $sort = true ) {
00967         $cnt = count( $list );
00968 
00969         if ( $cnt == 1 ) {
00970             // Enforce always returning a string
00971             return (string)self::arrayToString( $list[0] );
00972         } elseif ( $cnt == 0 ) {
00973             return '';
00974         } else {
00975             if ( $sort ) {
00976                 sort( $list );
00977             }
00978 
00979             return $this->getLanguage()
00980                 ->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
00981         }
00982     }
00983 
00992     public static function arrayToString( $list ) {
00993         if ( is_array( $list ) && count( $list ) == 1 ) {
00994             $list = $list[0];
00995         }
00996         if ( is_object( $list ) ) {
00997             $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
00998 
00999             return $class;
01000         } elseif ( !is_array( $list ) ) {
01001             return $list;
01002         } else {
01003             if ( is_object( $list[0] ) ) {
01004                 $class = get_class( $list[0] );
01005             } else {
01006                 $class = $list[0];
01007             }
01008 
01009             return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
01010         }
01011     }
01012 
01029     public static function getSvnInfo( $dir ) {
01030         // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
01031         $entries = $dir . '/.svn/entries';
01032 
01033         if ( !file_exists( $entries ) ) {
01034             return false;
01035         }
01036 
01037         $lines = file( $entries );
01038         if ( !count( $lines ) ) {
01039             return false;
01040         }
01041 
01042         // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
01043         if ( preg_match( '/^<\?xml/', $lines[0] ) ) {
01044             // subversion is release <= 1.3
01045             if ( !function_exists( 'simplexml_load_file' ) ) {
01046                 // We could fall back to expat... YUCK
01047                 return false;
01048             }
01049 
01050             // SimpleXml whines about the xmlns...
01051             wfSuppressWarnings();
01052             $xml = simplexml_load_file( $entries );
01053             wfRestoreWarnings();
01054 
01055             if ( $xml ) {
01056                 foreach ( $xml->entry as $entry ) {
01057                     if ( $xml->entry[0]['name'] == '' ) {
01058                         // The directory entry should always have a revision marker.
01059                         if ( $entry['revision'] ) {
01060                             return array( 'checkout-rev' => intval( $entry['revision'] ) );
01061                         }
01062                     }
01063                 }
01064             }
01065 
01066             return false;
01067         }
01068 
01069         // Subversion is release 1.4 or above.
01070         if ( count( $lines ) < 11 ) {
01071             return false;
01072         }
01073 
01074         $info = array(
01075             'checkout-rev' => intval( trim( $lines[3] ) ),
01076             'url' => trim( $lines[4] ),
01077             'repo-url' => trim( $lines[5] ),
01078             'directory-rev' => intval( trim( $lines[10] ) )
01079         );
01080 
01081         if ( isset( self::$viewvcUrls[$info['repo-url']] ) ) {
01082             $viewvc = str_replace(
01083                 $info['repo-url'],
01084                 self::$viewvcUrls[$info['repo-url']],
01085                 $info['url']
01086             );
01087 
01088             $viewvc .= '/?pathrev=';
01089             $viewvc .= urlencode( $info['checkout-rev'] );
01090             $info['viewvc-url'] = $viewvc;
01091         }
01092 
01093         return $info;
01094     }
01095 
01103     public static function getSvnRevision( $dir ) {
01104         $info = self::getSvnInfo( $dir );
01105 
01106         if ( $info === false ) {
01107             return false;
01108         } elseif ( isset( $info['checkout-rev'] ) ) {
01109             return $info['checkout-rev'];
01110         } else {
01111             return false;
01112         }
01113     }
01114 
01119     public static function getGitHeadSha1( $dir ) {
01120         $repo = new GitInfo( $dir );
01121 
01122         return $repo->getHeadSHA1();
01123     }
01124 
01129     public function getEntryPointInfo() {
01130         global $wgArticlePath, $wgScriptPath;
01131         $scriptPath = $wgScriptPath ? $wgScriptPath : "/";
01132         $entryPoints = array(
01133             'version-entrypoints-articlepath' => $wgArticlePath,
01134             'version-entrypoints-scriptpath' => $scriptPath,
01135             'version-entrypoints-index-php' => wfScript( 'index' ),
01136             'version-entrypoints-api-php' => wfScript( 'api' ),
01137             'version-entrypoints-load-php' => wfScript( 'load' ),
01138         );
01139 
01140         $language = $this->getLanguage();
01141         $thAttribures = array(
01142             'dir' => $language->getDir(),
01143             'lang' => $language->getCode()
01144         );
01145         $out = Html::element(
01146                 'h2',
01147                 array( 'id' => 'mw-version-entrypoints' ),
01148                 $this->msg( 'version-entrypoints' )->text()
01149             ) .
01150             Html::openElement( 'table',
01151                 array(
01152                     'class' => 'wikitable plainlinks',
01153                     'id' => 'mw-version-entrypoints-table',
01154                     'dir' => 'ltr',
01155                     'lang' => 'en'
01156                 )
01157             ) .
01158             Html::openElement( 'tr' ) .
01159             Html::element(
01160                 'th',
01161                 $thAttribures,
01162                 $this->msg( 'version-entrypoints-header-entrypoint' )->text()
01163             ) .
01164             Html::element(
01165                 'th',
01166                 $thAttribures,
01167                 $this->msg( 'version-entrypoints-header-url' )->text()
01168             ) .
01169             Html::closeElement( 'tr' );
01170 
01171         foreach ( $entryPoints as $message => $value ) {
01172             $url = wfExpandUrl( $value, PROTO_RELATIVE );
01173             $out .= Html::openElement( 'tr' ) .
01174                 // ->text() looks like it should be ->parse(), but this function
01175                 // returns wikitext, not HTML, boo
01176                 Html::rawElement( 'td', array(), $this->msg( $message )->text() ) .
01177                 Html::rawElement( 'td', array(), Html::rawElement( 'code', array(), "[$url $value]" ) ) .
01178                 Html::closeElement( 'tr' );
01179         }
01180 
01181         $out .= Html::closeElement( 'table' );
01182 
01183         return $out;
01184     }
01185 
01186     protected function getGroupName() {
01187         return 'wiki';
01188     }
01189 }