[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/specials/ -> SpecialVersion.php (source)

   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 = '&lt;' . htmlspecialchars( $value ) . '&gt;';
 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 = '&ndash;';
 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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1