|
MediaWiki
REL1_23
|
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 = '<' . htmlentities( $value ) . '>'; 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 = '–'; 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 }