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