[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Implements Special:Version 4 * 5 * Copyright © 2005 Ævar Arnfjörð Bjarmason 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with this program; if not, write to the Free Software Foundation, Inc., 19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 * http://www.gnu.org/copyleft/gpl.html 21 * 22 * @file 23 * @ingroup SpecialPage 24 */ 25 26 /** 27 * Give information about the version of MediaWiki, PHP, the DB and extensions 28 * 29 * @ingroup SpecialPage 30 */ 31 class SpecialVersion extends SpecialPage { 32 protected $firstExtOpened = false; 33 34 /** 35 * Stores the current rev id/SHA hash of MediaWiki core 36 */ 37 protected $coreId = ''; 38 39 protected static $extensionTypes = false; 40 41 protected static $viewvcUrls = array( 42 'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', 43 'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', 44 'https://svn.wikimedia.org/svnroot/mediawiki' => 'https://svn.wikimedia.org/viewvc/mediawiki', 45 ); 46 47 public function __construct() { 48 parent::__construct( 'Version' ); 49 } 50 51 /** 52 * main() 53 * @param string|null $par 54 */ 55 public function execute( $par ) { 56 global $IP, $wgExtensionCredits; 57 58 $this->setHeaders(); 59 $this->outputHeader(); 60 $out = $this->getOutput(); 61 $out->allowClickjacking(); 62 63 // Explode the sub page information into useful bits 64 $parts = explode( '/', (string)$par ); 65 $extNode = null; 66 if ( isset( $parts[1] ) ) { 67 $extName = str_replace( '_', ' ', $parts[1] ); 68 // Find it! 69 foreach ( $wgExtensionCredits as $group => $extensions ) { 70 foreach ( $extensions as $ext ) { 71 if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) { 72 $extNode = &$ext; 73 break 2; 74 } 75 } 76 } 77 if ( !$extNode ) { 78 $out->setStatusCode( 404 ); 79 } 80 } else { 81 $extName = 'MediaWiki'; 82 } 83 84 // Now figure out what to do 85 switch ( strtolower( $parts[0] ) ) { 86 case 'credits': 87 $wikiText = '{{int:version-credits-not-found}}'; 88 if ( $extName === 'MediaWiki' ) { 89 $wikiText = file_get_contents( $IP . '/CREDITS' ); 90 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) { 91 $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) ); 92 if ( $file ) { 93 $wikiText = file_get_contents( $file ); 94 if ( substr( $file, -4 ) === '.txt' ) { 95 $wikiText = Html::element( 'pre', array(), $wikiText ); 96 } 97 } 98 } 99 100 $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) ); 101 $out->addWikiText( $wikiText ); 102 break; 103 104 case 'license': 105 $wikiText = '{{int:version-license-not-found}}'; 106 if ( $extName === 'MediaWiki' ) { 107 $wikiText = file_get_contents( $IP . '/COPYING' ); 108 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) { 109 $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) ); 110 if ( $file ) { 111 $wikiText = file_get_contents( $file ); 112 if ( !isset( $extNode['license-name'] ) ) { 113 // If the developer did not explicitly set license-name they probably 114 // are unaware that we're now sucking this file in and thus it's probably 115 // not wikitext friendly. 116 $wikiText = "<pre>$wikiText</pre>"; 117 } 118 } 119 } 120 121 $out->setPageTitle( $this->msg( 'version-license-title', $extName ) ); 122 $out->addWikiText( $wikiText ); 123 break; 124 125 default: 126 $out->addModules( 'mediawiki.special.version' ); 127 $out->addWikiText( 128 $this->getMediaWikiCredits() . 129 $this->softwareInformation() . 130 $this->getEntryPointInfo() 131 ); 132 $out->addHtml( 133 $this->getSkinCredits() . 134 $this->getExtensionCredits() . 135 $this->getParserTags() . 136 $this->getParserFunctionHooks() 137 ); 138 $out->addWikiText( $this->getWgHooks() ); 139 $out->addHTML( $this->IPInfo() ); 140 141 break; 142 } 143 } 144 145 /** 146 * Returns wiki text showing the license information. 147 * 148 * @return string 149 */ 150 private static function getMediaWikiCredits() { 151 $ret = Xml::element( 152 'h2', 153 array( 'id' => 'mw-version-license' ), 154 wfMessage( 'version-license' )->text() 155 ); 156 157 // This text is always left-to-right. 158 $ret .= '<div class="plainlinks">'; 159 $ret .= "__NOTOC__ 160 " . self::getCopyrightAndAuthorList() . "\n 161 " . wfMessage( 'version-license-info' )->text(); 162 $ret .= '</div>'; 163 164 return str_replace( "\t\t", '', $ret ) . "\n"; 165 } 166 167 /** 168 * Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text 169 * 170 * @return string 171 */ 172 public static function getCopyrightAndAuthorList() { 173 global $wgLang; 174 175 if ( defined( 'MEDIAWIKI_INSTALL' ) ) { 176 $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' . 177 wfMessage( 'version-poweredby-others' )->text() . ']'; 178 } else { 179 $othersLink = '[[Special:Version/Credits|' . 180 wfMessage( 'version-poweredby-others' )->text() . ']]'; 181 } 182 183 $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' . 184 wfMessage( 'version-poweredby-translators' )->text() . ']'; 185 186 $authorList = array( 187 'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker', 188 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason', 189 'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan', 190 'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking', 191 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe', 192 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed', 193 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso', 194 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink, 195 $translatorsLink 196 ); 197 198 return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ), 199 $wgLang->listToText( $authorList ) )->text(); 200 } 201 202 /** 203 * Returns wiki text showing the third party software versions (apache, php, mysql). 204 * 205 * @return string 206 */ 207 public static function softwareInformation() { 208 $dbr = wfGetDB( DB_SLAVE ); 209 210 // Put the software in an array of form 'name' => 'version'. All messages should 211 // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or 212 // wikimarkup can be used. 213 $software = array(); 214 $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked(); 215 if ( wfIsHHVM() ) { 216 $software['[http://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")"; 217 } else { 218 $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")"; 219 } 220 $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo(); 221 222 // Allow a hook to add/remove items. 223 wfRunHooks( 'SoftwareInfo', array( &$software ) ); 224 225 $out = Xml::element( 226 'h2', 227 array( 'id' => 'mw-version-software' ), 228 wfMessage( 'version-software' )->text() 229 ) . 230 Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) . 231 "<tr> 232 <th>" . wfMessage( 'version-software-product' )->text() . "</th> 233 <th>" . wfMessage( 'version-software-version' )->text() . "</th> 234 </tr>\n"; 235 236 foreach ( $software as $name => $version ) { 237 $out .= "<tr> 238 <td>" . $name . "</td> 239 <td dir=\"ltr\">" . $version . "</td> 240 </tr>\n"; 241 } 242 243 return $out . Xml::closeElement( 'table' ); 244 } 245 246 /** 247 * Return a string of the MediaWiki version with SVN revision if available. 248 * 249 * @param string $flags 250 * @return mixed 251 */ 252 public static function getVersion( $flags = '' ) { 253 global $wgVersion, $IP; 254 wfProfileIn( __METHOD__ ); 255 256 $gitInfo = self::getGitHeadSha1( $IP ); 257 $svnInfo = self::getSvnInfo( $IP ); 258 if ( !$svnInfo && !$gitInfo ) { 259 $version = $wgVersion; 260 } elseif ( $gitInfo && $flags === 'nodb' ) { 261 $shortSha1 = substr( $gitInfo, 0, 7 ); 262 $version = "$wgVersion ($shortSha1)"; 263 } elseif ( $gitInfo ) { 264 $shortSha1 = substr( $gitInfo, 0, 7 ); 265 $shortSha1 = wfMessage( 'parentheses' )->params( $shortSha1 )->escaped(); 266 $version = "$wgVersion $shortSha1"; 267 } elseif ( $flags === 'nodb' ) { 268 $version = "$wgVersion (r{$svnInfo['checkout-rev']})"; 269 } else { 270 $version = $wgVersion . ' ' . 271 wfMessage( 272 'version-svn-revision', 273 isset( $info['directory-rev'] ) ? $info['directory-rev'] : '', 274 $info['checkout-rev'] 275 )->text(); 276 } 277 278 wfProfileOut( __METHOD__ ); 279 280 return $version; 281 } 282 283 /** 284 * Return a wikitext-formatted string of the MediaWiki version with a link to 285 * the SVN revision or the git SHA1 of head if available. 286 * Git is prefered over Svn 287 * The fallback is just $wgVersion 288 * 289 * @return mixed 290 */ 291 public static function getVersionLinked() { 292 global $wgVersion; 293 wfProfileIn( __METHOD__ ); 294 295 $gitVersion = self::getVersionLinkedGit(); 296 if ( $gitVersion ) { 297 $v = $gitVersion; 298 } else { 299 $svnVersion = self::getVersionLinkedSvn(); 300 if ( $svnVersion ) { 301 $v = $svnVersion; 302 } else { 303 $v = $wgVersion; // fallback 304 } 305 } 306 307 wfProfileOut( __METHOD__ ); 308 309 return $v; 310 } 311 312 /** 313 * @return string Global wgVersion + a link to subversion revision of svn BASE 314 */ 315 private static function getVersionLinkedSvn() { 316 global $IP; 317 318 $info = self::getSvnInfo( $IP ); 319 if ( !isset( $info['checkout-rev'] ) ) { 320 return false; 321 } 322 323 $linkText = wfMessage( 324 'version-svn-revision', 325 isset( $info['directory-rev'] ) ? $info['directory-rev'] : '', 326 $info['checkout-rev'] 327 )->text(); 328 329 if ( isset( $info['viewvc-url'] ) ) { 330 $version = "[{$info['viewvc-url']} $linkText]"; 331 } else { 332 $version = $linkText; 333 } 334 335 return self::getwgVersionLinked() . " $version"; 336 } 337 338 /** 339 * @return string 340 */ 341 private static function getwgVersionLinked() { 342 global $wgVersion; 343 $versionUrl = ""; 344 if ( wfRunHooks( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) { 345 $versionParts = array(); 346 preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts ); 347 $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}"; 348 } 349 350 return "[$versionUrl $wgVersion]"; 351 } 352 353 /** 354 * @since 1.22 Returns the HEAD date in addition to the sha1 and link 355 * @return bool|string Global wgVersion + HEAD sha1 stripped to the first 7 chars 356 * with link and date, or false on failure 357 */ 358 private static function getVersionLinkedGit() { 359 global $IP, $wgLang; 360 361 $gitInfo = new GitInfo( $IP ); 362 $headSHA1 = $gitInfo->getHeadSHA1(); 363 if ( !$headSHA1 ) { 364 return false; 365 } 366 367 $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')'; 368 369 $gitHeadUrl = $gitInfo->getHeadViewUrl(); 370 if ( $gitHeadUrl !== false ) { 371 $shortSHA1 = "[$gitHeadUrl $shortSHA1]"; 372 } 373 374 $gitHeadCommitDate = $gitInfo->getHeadCommitDate(); 375 if ( $gitHeadCommitDate ) { 376 $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true ); 377 } 378 379 return self::getwgVersionLinked() . " $shortSHA1"; 380 } 381 382 /** 383 * Returns an array with the base extension types. 384 * Type is stored as array key, the message as array value. 385 * 386 * TODO: ideally this would return all extension types. 387 * 388 * @since 1.17 389 * 390 * @return array 391 */ 392 public static function getExtensionTypes() { 393 if ( self::$extensionTypes === false ) { 394 self::$extensionTypes = array( 395 'specialpage' => wfMessage( 'version-specialpages' )->text(), 396 'parserhook' => wfMessage( 'version-parserhooks' )->text(), 397 'variable' => wfMessage( 'version-variables' )->text(), 398 'media' => wfMessage( 'version-mediahandlers' )->text(), 399 'antispam' => wfMessage( 'version-antispam' )->text(), 400 'skin' => wfMessage( 'version-skins' )->text(), 401 'api' => wfMessage( 'version-api' )->text(), 402 'other' => wfMessage( 'version-other' )->text(), 403 ); 404 405 wfRunHooks( 'ExtensionTypes', array( &self::$extensionTypes ) ); 406 } 407 408 return self::$extensionTypes; 409 } 410 411 /** 412 * Returns the internationalized name for an extension type. 413 * 414 * @since 1.17 415 * 416 * @param string $type 417 * 418 * @return string 419 */ 420 public static function getExtensionTypeName( $type ) { 421 $types = self::getExtensionTypes(); 422 423 return isset( $types[$type] ) ? $types[$type] : $types['other']; 424 } 425 426 /** 427 * Generate wikitext showing the name, URL, author and description of each extension. 428 * 429 * @return string Wikitext 430 */ 431 public function getExtensionCredits() { 432 global $wgExtensionCredits; 433 434 if ( 435 count( $wgExtensionCredits ) === 0 || 436 // Skins are displayed separately, see getSkinCredits() 437 ( count( $wgExtensionCredits ) === 1 && isset( $wgExtensionCredits['skin'] ) ) 438 ) { 439 return ''; 440 } 441 442 $extensionTypes = self::getExtensionTypes(); 443 444 $out = Xml::element( 445 'h2', 446 array( 'id' => 'mw-version-ext' ), 447 $this->msg( 'version-extensions' )->text() 448 ) . 449 Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ) ); 450 451 // Make sure the 'other' type is set to an array. 452 if ( !array_key_exists( 'other', $wgExtensionCredits ) ) { 453 $wgExtensionCredits['other'] = array(); 454 } 455 456 // Find all extensions that do not have a valid type and give them the type 'other'. 457 foreach ( $wgExtensionCredits as $type => $extensions ) { 458 if ( !array_key_exists( $type, $extensionTypes ) ) { 459 $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions ); 460 } 461 } 462 463 $this->firstExtOpened = false; 464 // Loop through the extension categories to display their extensions in the list. 465 foreach ( $extensionTypes as $type => $message ) { 466 // Skins have a separate section 467 if ( $type !== 'other' && $type !== 'skin' ) { 468 $out .= $this->getExtensionCategory( $type, $message ); 469 } 470 } 471 472 // We want the 'other' type to be last in the list. 473 $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] ); 474 475 $out .= Xml::closeElement( 'table' ); 476 477 return $out; 478 } 479 480 /** 481 * Generate wikitext showing the name, URL, author and description of each skin. 482 * 483 * @return string Wikitext 484 */ 485 public function getSkinCredits() { 486 global $wgExtensionCredits; 487 if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) { 488 return ''; 489 } 490 491 $out = Xml::element( 492 'h2', 493 array( 'id' => 'mw-version-skin' ), 494 $this->msg( 'version-skins' )->text() 495 ) . 496 Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ) ); 497 498 $this->firstExtOpened = false; 499 $out .= $this->getExtensionCategory( 'skin', null ); 500 501 $out .= Xml::closeElement( 'table' ); 502 503 return $out; 504 } 505 506 /** 507 * Obtains a list of installed parser tags and the associated H2 header 508 * 509 * @return string HTML output 510 */ 511 protected function getParserTags() { 512 global $wgParser; 513 514 $tags = $wgParser->getTags(); 515 516 if ( count( $tags ) ) { 517 $out = Html::rawElement( 518 'h2', 519 array( 'class' => 'mw-headline' ), 520 Linker::makeExternalLink( 521 '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions', 522 $this->msg( 'version-parser-extensiontags' )->parse(), 523 false /* msg()->parse() already escapes */ 524 ) 525 ); 526 527 array_walk( $tags, function ( &$value ) { 528 $value = '<' . htmlspecialchars( $value ) . '>'; 529 } ); 530 $out .= $this->listToText( $tags ); 531 } else { 532 $out = ''; 533 } 534 535 return $out; 536 } 537 538 /** 539 * Obtains a list of installed parser function hooks and the associated H2 header 540 * 541 * @return string HTML output 542 */ 543 protected function getParserFunctionHooks() { 544 global $wgParser; 545 546 $fhooks = $wgParser->getFunctionHooks(); 547 if ( count( $fhooks ) ) { 548 $out = Html::rawElement( 'h2', array( 'class' => 'mw-headline' ), Linker::makeExternalLink( 549 '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions', 550 $this->msg( 'version-parser-function-hooks' )->parse(), 551 false /* msg()->parse() already escapes */ 552 ) ); 553 554 $out .= $this->listToText( $fhooks ); 555 } else { 556 $out = ''; 557 } 558 559 return $out; 560 } 561 562 /** 563 * Creates and returns the HTML for a single extension category. 564 * 565 * @since 1.17 566 * 567 * @param string $type 568 * @param string $message 569 * 570 * @return string 571 */ 572 protected function getExtensionCategory( $type, $message ) { 573 global $wgExtensionCredits; 574 575 $out = ''; 576 577 if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) { 578 $out .= $this->openExtType( $message, 'credits-' . $type ); 579 580 usort( $wgExtensionCredits[$type], array( $this, 'compare' ) ); 581 582 foreach ( $wgExtensionCredits[$type] as $extension ) { 583 $out .= $this->getCreditsForExtension( $extension ); 584 } 585 } 586 587 return $out; 588 } 589 590 /** 591 * Callback to sort extensions by type. 592 * @param array $a 593 * @param array $b 594 * @return int 595 */ 596 public function compare( $a, $b ) { 597 if ( $a['name'] === $b['name'] ) { 598 return 0; 599 } else { 600 return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] ) 601 ? 1 602 : -1; 603 } 604 } 605 606 /** 607 * Creates and formats a version line for a single extension. 608 * 609 * Information for five columns will be created. Parameters required in the 610 * $extension array for part rendering are indicated in () 611 * - The name of (name), and URL link to (url), the extension 612 * - Official version number (version) and if available version control system 613 * revision (path), link, and date 614 * - If available the short name of the license (license-name) and a linke 615 * to ((LICENSE)|(COPYING))(\.txt)? if it exists. 616 * - Description of extension (descriptionmsg or description) 617 * - List of authors (author) and link to a ((AUTHORS)|(CREDITS))(\.txt)? file if it exists 618 * 619 * @param array $extension 620 * 621 * @return string Raw HTML 622 */ 623 public function getCreditsForExtension( array $extension ) { 624 $out = $this->getOutput(); 625 626 // We must obtain the information for all the bits and pieces! 627 // ... such as extension names and links 628 if ( isset( $extension['namemsg'] ) ) { 629 // Localized name of extension 630 $extensionName = $this->msg( $extension['namemsg'] )->text(); 631 } elseif ( isset( $extension['name'] ) ) { 632 // Non localized version 633 $extensionName = $extension['name']; 634 } else { 635 $extensionName = $this->msg( 'version-no-ext-name' )->text(); 636 } 637 638 if ( isset( $extension['url'] ) ) { 639 $extensionNameLink = Linker::makeExternalLink( 640 $extension['url'], 641 $extensionName, 642 true, 643 '', 644 array( 'class' => 'mw-version-ext-name' ) 645 ); 646 } else { 647 $extensionNameLink = $extensionName; 648 } 649 650 // ... and the version information 651 // If the extension path is set we will check that directory for GIT and SVN 652 // metadata in an attempt to extract date and vcs commit metadata. 653 $canonicalVersion = '–'; 654 $extensionPath = null; 655 $vcsVersion = null; 656 $vcsLink = null; 657 $vcsDate = null; 658 659 if ( isset( $extension['version'] ) ) { 660 $canonicalVersion = $out->parseInline( $extension['version'] ); 661 } 662 663 if ( isset( $extension['path'] ) ) { 664 global $IP; 665 $extensionPath = dirname( $extension['path'] ); 666 if ( $this->coreId == '' ) { 667 wfDebug( 'Looking up core head id' ); 668 $coreHeadSHA1 = self::getGitHeadSha1( $IP ); 669 if ( $coreHeadSHA1 ) { 670 $this->coreId = $coreHeadSHA1; 671 } else { 672 $svnInfo = self::getSvnInfo( $IP ); 673 if ( $svnInfo !== false ) { 674 $this->coreId = $svnInfo['checkout-rev']; 675 } 676 } 677 } 678 $cache = wfGetCache( CACHE_ANYTHING ); 679 $memcKey = wfMemcKey( 'specialversion-ext-version-text', $extension['path'], $this->coreId ); 680 list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey ); 681 682 if ( !$vcsVersion ) { 683 wfDebug( "Getting VCS info for extension $extensionName" ); 684 $gitInfo = new GitInfo( $extensionPath ); 685 $vcsVersion = $gitInfo->getHeadSHA1(); 686 if ( $vcsVersion !== false ) { 687 $vcsVersion = substr( $vcsVersion, 0, 7 ); 688 $vcsLink = $gitInfo->getHeadViewUrl(); 689 $vcsDate = $gitInfo->getHeadCommitDate(); 690 } else { 691 $svnInfo = self::getSvnInfo( $extensionPath ); 692 if ( $svnInfo !== false ) { 693 $vcsVersion = $this->msg( 'version-svn-revision', $svnInfo['checkout-rev'] )->text(); 694 $vcsLink = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : ''; 695 } 696 } 697 $cache->set( $memcKey, array( $vcsVersion, $vcsLink, $vcsDate ), 60 * 60 * 24 ); 698 } else { 699 wfDebug( "Pulled VCS info for extension $extensionName from cache" ); 700 } 701 } 702 703 $versionString = Html::rawElement( 704 'span', 705 array( 'class' => 'mw-version-ext-version' ), 706 $canonicalVersion 707 ); 708 709 if ( $vcsVersion ) { 710 if ( $vcsLink ) { 711 $vcsVerString = Linker::makeExternalLink( 712 $vcsLink, 713 $this->msg( 'version-version', $vcsVersion ), 714 true, 715 '', 716 array( 'class' => 'mw-version-ext-vcs-version' ) 717 ); 718 } else { 719 $vcsVerString = Html::element( 'span', 720 array( 'class' => 'mw-version-ext-vcs-version' ), 721 "({$vcsVersion})" 722 ); 723 } 724 $versionString .= " {$vcsVerString}"; 725 726 if ( $vcsDate ) { 727 $vcsTimeString = Html::element( 'span', 728 array( 'class' => 'mw-version-ext-vcs-timestamp' ), 729 $this->getLanguage()->timeanddate( $vcsDate, true ) 730 ); 731 $versionString .= " {$vcsTimeString}"; 732 } 733 $versionString = Html::rawElement( 'span', 734 array( 'class' => 'mw-version-ext-meta-version' ), 735 $versionString 736 ); 737 } 738 739 // ... and license information; if a license file exists we 740 // will link to it 741 $licenseLink = ''; 742 if ( isset( $extension['license-name'] ) ) { 743 $licenseLink = Linker::link( 744 $this->getPageTitle( 'License/' . $extensionName ), 745 $out->parseInline( $extension['license-name'] ), 746 array( 'class' => 'mw-version-ext-license' ) 747 ); 748 } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) { 749 $licenseLink = Linker::link( 750 $this->getPageTitle( 'License/' . $extensionName ), 751 $this->msg( 'version-ext-license' ), 752 array( 'class' => 'mw-version-ext-license' ) 753 ); 754 } 755 756 // ... and generate the description; which can be a parameterized l10n message 757 // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight 758 // up string 759 if ( isset( $extension['descriptionmsg'] ) ) { 760 // Localized description of extension 761 $descriptionMsg = $extension['descriptionmsg']; 762 763 if ( is_array( $descriptionMsg ) ) { 764 $descriptionMsgKey = $descriptionMsg[0]; // Get the message key 765 array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only 766 array_map( "htmlspecialchars", $descriptionMsg ); // For sanity 767 $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text(); 768 } else { 769 $description = $this->msg( $descriptionMsg )->text(); 770 } 771 } elseif ( isset( $extension['description'] ) ) { 772 // Non localized version 773 $description = $extension['description']; 774 } else { 775 $description = ''; 776 } 777 $description = $out->parseInline( $description ); 778 779 // ... now get the authors for this extension 780 $authors = isset( $extension['author'] ) ? $extension['author'] : array(); 781 $authors = $this->listAuthors( $authors, $extensionName, $extensionPath ); 782 783 // Finally! Create the table 784 $html = Html::openElement( 'tr', array( 785 'class' => 'mw-version-ext', 786 'id' => "mw-version-ext-{$extensionName}" 787 ) 788 ); 789 790 $html .= Html::rawElement( 'td', array(), $extensionNameLink ); 791 $html .= Html::rawElement( 'td', array(), $versionString ); 792 $html .= Html::rawElement( 'td', array(), $licenseLink ); 793 $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-description' ), $description ); 794 $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-authors' ), $authors ); 795 796 $html .= Html::closeElement( 'td' ); 797 798 return $html; 799 } 800 801 /** 802 * Generate wikitext showing hooks in $wgHooks. 803 * 804 * @return string Wikitext 805 */ 806 private function getWgHooks() { 807 global $wgSpecialVersionShowHooks, $wgHooks; 808 809 if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) { 810 $myWgHooks = $wgHooks; 811 ksort( $myWgHooks ); 812 813 $ret = array(); 814 $ret[] = '== {{int:version-hooks}} =='; 815 $ret[] = Html::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ); 816 $ret[] = Html::openElement( 'tr' ); 817 $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-name' )->text() ); 818 $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-subscribedby' )->text() ); 819 $ret[] = Html::closeElement( 'tr' ); 820 821 foreach ( $myWgHooks as $hook => $hooks ) { 822 $ret[] = Html::openElement( 'tr' ); 823 $ret[] = Html::element( 'td', array(), $hook ); 824 $ret[] = Html::element( 'td', array(), $this->listToText( $hooks ) ); 825 $ret[] = Html::closeElement( 'tr' ); 826 } 827 828 $ret[] = Html::closeElement( 'table' ); 829 830 return implode( "\n", $ret ); 831 } else { 832 return ''; 833 } 834 } 835 836 private function openExtType( $text = null, $name = null ) { 837 $out = ''; 838 839 $opt = array( 'colspan' => 5 ); 840 if ( $this->firstExtOpened ) { 841 // Insert a spacing line 842 $out .= Html::rawElement( 'tr', array( 'class' => 'sv-space' ), 843 Html::element( 'td', $opt ) 844 ); 845 } 846 $this->firstExtOpened = true; 847 848 if ( $name ) { 849 $opt['id'] = "sv-$name"; 850 } 851 852 if ( $text !== null ) { 853 $out .= Html::rawElement( 'tr', array(), 854 Html::element( 'th', $opt, $text ) 855 ); 856 } 857 858 $firstHeadingMsg = ( $name === 'credits-skin' ) 859 ? 'version-skin-colheader-name' 860 : 'version-ext-colheader-name'; 861 $out .= Html::openElement( 'tr' ); 862 $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ), 863 $this->msg( $firstHeadingMsg )->text() ); 864 $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ), 865 $this->msg( 'version-ext-colheader-version' )->text() ); 866 $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ), 867 $this->msg( 'version-ext-colheader-license' )->text() ); 868 $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ), 869 $this->msg( 'version-ext-colheader-description' )->text() ); 870 $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ), 871 $this->msg( 'version-ext-colheader-credits' )->text() ); 872 $out .= Html::closeElement( 'tr' ); 873 874 return $out; 875 } 876 877 /** 878 * Get information about client's IP address. 879 * 880 * @return string HTML fragment 881 */ 882 private function IPInfo() { 883 $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) ); 884 885 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>"; 886 } 887 888 /** 889 * Return a formatted unsorted list of authors 890 * 891 * 'And Others' 892 * If an item in the $authors array is '...' it is assumed to indicate an 893 * 'and others' string which will then be linked to an ((AUTHORS)|(CREDITS))(\.txt)? 894 * file if it exists in $dir. 895 * 896 * Similarly an entry ending with ' ...]' is assumed to be a link to an 897 * 'and others' page. 898 * 899 * If no '...' string variant is found, but an authors file is found an 900 * 'and others' will be added to the end of the credits. 901 * 902 * @param string|array $authors 903 * @param string $extName Name of the extension for link creation 904 * @param string $extDir Path to the extension root directory 905 * 906 * @return string HTML fragment 907 */ 908 public function listAuthors( $authors, $extName, $extDir ) { 909 $hasOthers = false; 910 911 $list = array(); 912 foreach ( (array)$authors as $item ) { 913 if ( $item == '...' ) { 914 $hasOthers = true; 915 916 if ( $this->getExtAuthorsFileName( $extDir ) ) { 917 $text = Linker::link( 918 $this->getPageTitle( "Credits/$extName" ), 919 $this->msg( 'version-poweredby-others' )->text() 920 ); 921 } else { 922 $text = $this->msg( 'version-poweredby-others' )->text(); 923 } 924 $list[] = $text; 925 } elseif ( substr( $item, -5 ) == ' ...]' ) { 926 $hasOthers = true; 927 $list[] = $this->getOutput()->parseInline( 928 substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]" 929 ); 930 } else { 931 $list[] = $this->getOutput()->parseInline( $item ); 932 } 933 } 934 935 if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) { 936 $list[] = $text = Linker::link( 937 $this->getPageTitle( "Credits/$extName" ), 938 $this->msg( 'version-poweredby-others' )->text() 939 ); 940 } 941 942 return $this->listToText( $list, false ); 943 } 944 945 /** 946 * Obtains the full path of an extensions authors or credits file if 947 * one exists. 948 * 949 * @param string $extDir Path to the extensions root directory 950 * 951 * @since 1.23 952 * 953 * @return bool|string False if no such file exists, otherwise returns 954 * a path to it. 955 */ 956 public static function getExtAuthorsFileName( $extDir ) { 957 if ( !$extDir ) { 958 return false; 959 } 960 961 foreach ( scandir( $extDir ) as $file ) { 962 $fullPath = $extDir . DIRECTORY_SEPARATOR . $file; 963 if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt)?$/', $file ) && 964 is_readable( $fullPath ) && 965 is_file( $fullPath ) 966 ) { 967 return $fullPath; 968 } 969 } 970 971 return false; 972 } 973 974 /** 975 * Obtains the full path of an extensions copying or license file if 976 * one exists. 977 * 978 * @param string $extDir Path to the extensions root directory 979 * 980 * @since 1.23 981 * 982 * @return bool|string False if no such file exists, otherwise returns 983 * a path to it. 984 */ 985 public static function getExtLicenseFileName( $extDir ) { 986 if ( !$extDir ) { 987 return false; 988 } 989 990 foreach ( scandir( $extDir ) as $file ) { 991 $fullPath = $extDir . DIRECTORY_SEPARATOR . $file; 992 if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) && 993 is_readable( $fullPath ) && 994 is_file( $fullPath ) 995 ) { 996 return $fullPath; 997 } 998 } 999 1000 return false; 1001 } 1002 1003 /** 1004 * Convert an array of items into a list for display. 1005 * 1006 * @param array $list List of elements to display 1007 * @param bool $sort Whether to sort the items in $list 1008 * 1009 * @return string 1010 */ 1011 public function listToText( $list, $sort = true ) { 1012 if ( !count( $list ) ) { 1013 return ''; 1014 } 1015 if ( $sort ) { 1016 sort( $list ); 1017 } 1018 1019 return $this->getLanguage() 1020 ->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) ); 1021 } 1022 1023 /** 1024 * Convert an array or object to a string for display. 1025 * 1026 * @param mixed $list Will convert an array to string if given and return 1027 * the paramater unaltered otherwise 1028 * 1029 * @return mixed 1030 */ 1031 public static function arrayToString( $list ) { 1032 if ( is_array( $list ) && count( $list ) == 1 ) { 1033 $list = $list[0]; 1034 } 1035 if ( is_object( $list ) ) { 1036 $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped(); 1037 1038 return $class; 1039 } elseif ( !is_array( $list ) ) { 1040 return $list; 1041 } else { 1042 if ( is_object( $list[0] ) ) { 1043 $class = get_class( $list[0] ); 1044 } else { 1045 $class = $list[0]; 1046 } 1047 1048 return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped(); 1049 } 1050 } 1051 1052 /** 1053 * Get an associative array of information about a given path, from its .svn 1054 * subdirectory. Returns false on error, such as if the directory was not 1055 * checked out with subversion. 1056 * 1057 * Returned keys are: 1058 * Required: 1059 * checkout-rev The revision which was checked out 1060 * Optional: 1061 * directory-rev The revision when the directory was last modified 1062 * url The subversion URL of the directory 1063 * repo-url The base URL of the repository 1064 * viewvc-url A ViewVC URL pointing to the checked-out revision 1065 * @param string $dir 1066 * @return array|bool 1067 */ 1068 public static function getSvnInfo( $dir ) { 1069 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html 1070 $entries = $dir . '/.svn/entries'; 1071 1072 if ( !file_exists( $entries ) ) { 1073 return false; 1074 } 1075 1076 $lines = file( $entries ); 1077 if ( !count( $lines ) ) { 1078 return false; 1079 } 1080 1081 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4) 1082 if ( preg_match( '/^<\?xml/', $lines[0] ) ) { 1083 // subversion is release <= 1.3 1084 if ( !function_exists( 'simplexml_load_file' ) ) { 1085 // We could fall back to expat... YUCK 1086 return false; 1087 } 1088 1089 // SimpleXml whines about the xmlns... 1090 wfSuppressWarnings(); 1091 $xml = simplexml_load_file( $entries ); 1092 wfRestoreWarnings(); 1093 1094 if ( $xml ) { 1095 foreach ( $xml->entry as $entry ) { 1096 if ( $xml->entry[0]['name'] == '' ) { 1097 // The directory entry should always have a revision marker. 1098 if ( $entry['revision'] ) { 1099 return array( 'checkout-rev' => intval( $entry['revision'] ) ); 1100 } 1101 } 1102 } 1103 } 1104 1105 return false; 1106 } 1107 1108 // Subversion is release 1.4 or above. 1109 if ( count( $lines ) < 11 ) { 1110 return false; 1111 } 1112 1113 $info = array( 1114 'checkout-rev' => intval( trim( $lines[3] ) ), 1115 'url' => trim( $lines[4] ), 1116 'repo-url' => trim( $lines[5] ), 1117 'directory-rev' => intval( trim( $lines[10] ) ) 1118 ); 1119 1120 if ( isset( self::$viewvcUrls[$info['repo-url']] ) ) { 1121 $viewvc = str_replace( 1122 $info['repo-url'], 1123 self::$viewvcUrls[$info['repo-url']], 1124 $info['url'] 1125 ); 1126 1127 $viewvc .= '/?pathrev='; 1128 $viewvc .= urlencode( $info['checkout-rev'] ); 1129 $info['viewvc-url'] = $viewvc; 1130 } 1131 1132 return $info; 1133 } 1134 1135 /** 1136 * Retrieve the revision number of a Subversion working directory. 1137 * 1138 * @param string $dir Directory of the svn checkout 1139 * 1140 * @return int Revision number 1141 */ 1142 public static function getSvnRevision( $dir ) { 1143 $info = self::getSvnInfo( $dir ); 1144 1145 if ( $info === false ) { 1146 return false; 1147 } elseif ( isset( $info['checkout-rev'] ) ) { 1148 return $info['checkout-rev']; 1149 } else { 1150 return false; 1151 } 1152 } 1153 1154 /** 1155 * @param string $dir Directory of the git checkout 1156 * @return bool|string Sha1 of commit HEAD points to 1157 */ 1158 public static function getGitHeadSha1( $dir ) { 1159 $repo = new GitInfo( $dir ); 1160 1161 return $repo->getHeadSHA1(); 1162 } 1163 1164 /** 1165 * @param string $dir Directory of the git checkout 1166 * @return bool|string Branch currently checked out 1167 */ 1168 public static function getGitCurrentBranch( $dir ) { 1169 $repo = new GitInfo( $dir ); 1170 return $repo->getCurrentBranch(); 1171 } 1172 1173 /** 1174 * Get the list of entry points and their URLs 1175 * @return string Wikitext 1176 */ 1177 public function getEntryPointInfo() { 1178 global $wgArticlePath, $wgScriptPath; 1179 $scriptPath = $wgScriptPath ? $wgScriptPath : "/"; 1180 $entryPoints = array( 1181 'version-entrypoints-articlepath' => $wgArticlePath, 1182 'version-entrypoints-scriptpath' => $scriptPath, 1183 'version-entrypoints-index-php' => wfScript( 'index' ), 1184 'version-entrypoints-api-php' => wfScript( 'api' ), 1185 'version-entrypoints-load-php' => wfScript( 'load' ), 1186 ); 1187 1188 $language = $this->getLanguage(); 1189 $thAttribures = array( 1190 'dir' => $language->getDir(), 1191 'lang' => $language->getCode() 1192 ); 1193 $out = Html::element( 1194 'h2', 1195 array( 'id' => 'mw-version-entrypoints' ), 1196 $this->msg( 'version-entrypoints' )->text() 1197 ) . 1198 Html::openElement( 'table', 1199 array( 1200 'class' => 'wikitable plainlinks', 1201 'id' => 'mw-version-entrypoints-table', 1202 'dir' => 'ltr', 1203 'lang' => 'en' 1204 ) 1205 ) . 1206 Html::openElement( 'tr' ) . 1207 Html::element( 1208 'th', 1209 $thAttribures, 1210 $this->msg( 'version-entrypoints-header-entrypoint' )->text() 1211 ) . 1212 Html::element( 1213 'th', 1214 $thAttribures, 1215 $this->msg( 'version-entrypoints-header-url' )->text() 1216 ) . 1217 Html::closeElement( 'tr' ); 1218 1219 foreach ( $entryPoints as $message => $value ) { 1220 $url = wfExpandUrl( $value, PROTO_RELATIVE ); 1221 $out .= Html::openElement( 'tr' ) . 1222 // ->text() looks like it should be ->parse(), but this function 1223 // returns wikitext, not HTML, boo 1224 Html::rawElement( 'td', array(), $this->msg( $message )->text() ) . 1225 Html::rawElement( 'td', array(), Html::rawElement( 'code', array(), "[$url $value]" ) ) . 1226 Html::closeElement( 'tr' ); 1227 } 1228 1229 $out .= Html::closeElement( 'table' ); 1230 1231 return $out; 1232 } 1233 1234 protected function getGroupName() { 1235 return 'wiki'; 1236 } 1237 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |