123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239 |
- <?php
- /**
- * Implements Special:Version
- *
- * Copyright © 2005 Ævar Arnfjörð Bjarmason
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
- use MediaWiki\MediaWikiServices;
- /**
- * Give information about the version of MediaWiki, PHP, the DB and extensions
- *
- * @ingroup SpecialPage
- */
- class SpecialVersion extends SpecialPage {
- /**
- * @var bool
- */
- protected $firstExtOpened = false;
- /**
- * @var string The current rev id/SHA hash of MediaWiki core
- */
- protected $coreId = '';
- /**
- * @var string[]|false Lazy initialized key/value with message content
- */
- protected static $extensionTypes = false;
- public function __construct() {
- parent::__construct( 'Version' );
- }
- /**
- * main()
- * @param string|null $par
- */
- public function execute( $par ) {
- global $IP;
- $config = $this->getConfig();
- $extensionCredits = $config->get( 'ExtensionCredits' );
- $this->setHeaders();
- $this->outputHeader();
- $out = $this->getOutput();
- $out->allowClickjacking();
- // Explode the sub page information into useful bits
- $parts = explode( '/', (string)$par );
- $extNode = null;
- if ( isset( $parts[1] ) ) {
- $extName = str_replace( '_', ' ', $parts[1] );
- // Find it!
- foreach ( $extensionCredits as $group => $extensions ) {
- foreach ( $extensions as $ext ) {
- if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
- $extNode = &$ext;
- break 2;
- }
- }
- }
- if ( !$extNode ) {
- $out->setStatusCode( 404 );
- }
- } else {
- $extName = 'MediaWiki';
- }
- // Now figure out what to do
- switch ( strtolower( $parts[0] ) ) {
- case 'credits':
- $out->addModuleStyles( 'mediawiki.special.version' );
- $wikiText = '{{int:version-credits-not-found}}';
- if ( $extName === 'MediaWiki' ) {
- $wikiText = file_get_contents( $IP . '/CREDITS' );
- // Put the contributor list into columns
- $wikiText = str_replace(
- [ '<!-- BEGIN CONTRIBUTOR LIST -->', '<!-- END CONTRIBUTOR LIST -->' ],
- [ '<div class="mw-version-credits">', '</div>' ],
- $wikiText );
- } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
- $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
- if ( $file ) {
- $wikiText = file_get_contents( $file );
- if ( substr( $file, -4 ) === '.txt' ) {
- $wikiText = Html::element(
- 'pre',
- [
- 'lang' => 'en',
- 'dir' => 'ltr',
- ],
- $wikiText
- );
- }
- }
- }
- $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
- $out->addWikiTextAsInterface( $wikiText );
- break;
- case 'license':
- $wikiText = '{{int:version-license-not-found}}';
- if ( $extName === 'MediaWiki' ) {
- $wikiText = file_get_contents( $IP . '/COPYING' );
- } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
- $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
- if ( $file ) {
- $wikiText = file_get_contents( $file );
- $wikiText = Html::element(
- 'pre',
- [
- 'lang' => 'en',
- 'dir' => 'ltr',
- ],
- $wikiText
- );
- }
- }
- $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
- $out->addWikiTextAsInterface( $wikiText );
- break;
- default:
- $out->addModuleStyles( 'mediawiki.special.version' );
- $out->addWikiTextAsInterface(
- $this->getMediaWikiCredits() .
- $this->softwareInformation() .
- $this->getEntryPointInfo()
- );
- $out->addHTML(
- $this->getSkinCredits() .
- $this->getExtensionCredits() .
- $this->getExternalLibraries() .
- $this->getParserTags() .
- $this->getParserFunctionHooks()
- );
- $out->addWikiTextAsInterface( $this->getWgHooks() );
- $out->addHTML( $this->IPInfo() );
- break;
- }
- }
- /**
- * Returns wiki text showing the license information.
- *
- * @return string
- */
- private static function getMediaWikiCredits() {
- $ret = Xml::element(
- 'h2',
- [ 'id' => 'mw-version-license' ],
- wfMessage( 'version-license' )->text()
- );
- // This text is always left-to-right.
- $ret .= '<div class="plainlinks">';
- $ret .= "__NOTOC__
- " . self::getCopyrightAndAuthorList() . "\n
- " . '<div class="mw-version-license-info">' .
- wfMessage( 'version-license-info' )->text() .
- '</div>';
- $ret .= '</div>';
- return str_replace( "\t\t", '', $ret ) . "\n";
- }
- /**
- * Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text
- *
- * @return string
- */
- public static function getCopyrightAndAuthorList() {
- global $wgLang;
- if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
- $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
- wfMessage( 'version-poweredby-others' )->text() . ']';
- } else {
- $othersLink = '[[Special:Version/Credits|' .
- wfMessage( 'version-poweredby-others' )->text() . ']]';
- }
- $translatorsLink = '[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
- wfMessage( 'version-poweredby-translators' )->text() . ']';
- $authorList = [
- 'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
- 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
- 'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
- 'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
- 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
- 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
- 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
- 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
- 'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
- 'Kunal Mehta', 'James D. Forrester', 'Brian Wolff', 'Adam Shorland',
- $othersLink, $translatorsLink
- ];
- return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
- $wgLang->listToText( $authorList ) )->text();
- }
- /**
- * @since 1.34
- *
- * @return array
- */
- public static function getSoftwareInformation() {
- $dbr = wfGetDB( DB_REPLICA );
- // Put the software in an array of form 'name' => 'version'. All messages should
- // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
- // wikimarkup can be used.
- $software = [
- '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked()
- ];
- if ( wfIsHHVM() ) {
- $software['[https://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")";
- } else {
- $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")";
- }
- $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
- if ( defined( 'INTL_ICU_VERSION' ) ) {
- $software['[http://site.icu-project.org/ ICU]'] = INTL_ICU_VERSION;
- }
- // Allow a hook to add/remove items.
- Hooks::run( 'SoftwareInfo', [ &$software ] );
- return $software;
- }
- /**
- * Returns HTML showing the third party software versions (apache, php, mysql).
- *
- * @return string HTML table
- */
- public static function softwareInformation() {
- $out = Xml::element(
- 'h2',
- [ 'id' => 'mw-version-software' ],
- wfMessage( 'version-software' )->text()
- ) .
- Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] ) .
- "<tr>
- <th>" . wfMessage( 'version-software-product' )->text() . "</th>
- <th>" . wfMessage( 'version-software-version' )->text() . "</th>
- </tr>\n";
- foreach ( self::getSoftwareInformation() as $name => $version ) {
- $out .= "<tr>
- <td>" . $name . "</td>
- <td dir=\"ltr\">" . $version . "</td>
- </tr>\n";
- }
- return $out . Xml::closeElement( 'table' );
- }
- /**
- * Return a string of the MediaWiki version with Git revision if available.
- *
- * @param string $flags
- * @param Language|string|null $lang
- * @return mixed
- */
- public static function getVersion( $flags = '', $lang = null ) {
- global $wgVersion, $IP;
- $gitInfo = self::getGitHeadSha1( $IP );
- if ( !$gitInfo ) {
- $version = $wgVersion;
- } elseif ( $flags === 'nodb' ) {
- $shortSha1 = substr( $gitInfo, 0, 7 );
- $version = "$wgVersion ($shortSha1)";
- } else {
- $shortSha1 = substr( $gitInfo, 0, 7 );
- $msg = wfMessage( 'parentheses' );
- if ( $lang !== null ) {
- $msg->inLanguage( $lang );
- }
- $shortSha1 = $msg->params( $shortSha1 )->escaped();
- $version = "$wgVersion $shortSha1";
- }
- return $version;
- }
- /**
- * Return a wikitext-formatted string of the MediaWiki version with a link to
- * the Git SHA1 of head if available.
- * The fallback is just $wgVersion
- *
- * @return mixed
- */
- public static function getVersionLinked() {
- global $wgVersion;
- $gitVersion = self::getVersionLinkedGit();
- if ( $gitVersion ) {
- $v = $gitVersion;
- } else {
- $v = $wgVersion; // fallback
- }
- return $v;
- }
- /**
- * @return string
- */
- private static function getwgVersionLinked() {
- global $wgVersion;
- $versionUrl = "";
- if ( Hooks::run( 'SpecialVersionVersionUrl', [ $wgVersion, &$versionUrl ] ) ) {
- $versionParts = [];
- preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
- $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
- }
- return "[$versionUrl $wgVersion]";
- }
- /**
- * @since 1.22 Returns the HEAD date in addition to the sha1 and link
- * @return bool|string Global wgVersion + HEAD sha1 stripped to the first 7 chars
- * with link and date, or false on failure
- */
- private static function getVersionLinkedGit() {
- global $IP, $wgLang;
- $gitInfo = new GitInfo( $IP );
- $headSHA1 = $gitInfo->getHeadSHA1();
- if ( !$headSHA1 ) {
- return false;
- }
- $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
- $gitHeadUrl = $gitInfo->getHeadViewUrl();
- if ( $gitHeadUrl !== false ) {
- $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
- }
- $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
- if ( $gitHeadCommitDate ) {
- $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
- }
- return self::getwgVersionLinked() . " $shortSHA1";
- }
- /**
- * Returns an array with the base extension types.
- * Type is stored as array key, the message as array value.
- *
- * TODO: ideally this would return all extension types.
- *
- * @since 1.17
- *
- * @return string[]
- */
- public static function getExtensionTypes() {
- if ( self::$extensionTypes === false ) {
- self::$extensionTypes = [
- 'specialpage' => wfMessage( 'version-specialpages' )->text(),
- 'editor' => wfMessage( 'version-editors' )->text(),
- 'parserhook' => wfMessage( 'version-parserhooks' )->text(),
- 'variable' => wfMessage( 'version-variables' )->text(),
- 'media' => wfMessage( 'version-mediahandlers' )->text(),
- 'antispam' => wfMessage( 'version-antispam' )->text(),
- 'skin' => wfMessage( 'version-skins' )->text(),
- 'api' => wfMessage( 'version-api' )->text(),
- 'other' => wfMessage( 'version-other' )->text(),
- ];
- Hooks::run( 'ExtensionTypes', [ &self::$extensionTypes ] );
- }
- return self::$extensionTypes;
- }
- /**
- * Returns the internationalized name for an extension type.
- *
- * @since 1.17
- *
- * @param string $type
- *
- * @return string
- */
- public static function getExtensionTypeName( $type ) {
- $types = self::getExtensionTypes();
- return $types[$type] ?? $types['other'];
- }
- /**
- * Generate wikitext showing the name, URL, author and description of each extension.
- *
- * @return string Wikitext
- */
- public function getExtensionCredits() {
- $config = $this->getConfig();
- $extensionCredits = $config->get( 'ExtensionCredits' );
- if (
- count( $extensionCredits ) === 0 ||
- // Skins are displayed separately, see getSkinCredits()
- ( count( $extensionCredits ) === 1 && isset( $extensionCredits['skin'] ) )
- ) {
- return '';
- }
- $extensionTypes = self::getExtensionTypes();
- $out = Xml::element(
- 'h2',
- [ 'id' => 'mw-version-ext' ],
- $this->msg( 'version-extensions' )->text()
- ) .
- Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ] );
- // Make sure the 'other' type is set to an array.
- if ( !array_key_exists( 'other', $extensionCredits ) ) {
- $extensionCredits['other'] = [];
- }
- // Find all extensions that do not have a valid type and give them the type 'other'.
- foreach ( $extensionCredits as $type => $extensions ) {
- if ( !array_key_exists( $type, $extensionTypes ) ) {
- $extensionCredits['other'] = array_merge( $extensionCredits['other'], $extensions );
- }
- }
- $this->firstExtOpened = false;
- // Loop through the extension categories to display their extensions in the list.
- foreach ( $extensionTypes as $type => $message ) {
- // Skins have a separate section
- if ( $type !== 'other' && $type !== 'skin' ) {
- $out .= $this->getExtensionCategory( $type, $message );
- }
- }
- // We want the 'other' type to be last in the list.
- $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
- $out .= Xml::closeElement( 'table' );
- return $out;
- }
- /**
- * Generate wikitext showing the name, URL, author and description of each skin.
- *
- * @return string Wikitext
- */
- public function getSkinCredits() {
- global $wgExtensionCredits;
- if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) {
- return '';
- }
- $out = Xml::element(
- 'h2',
- [ 'id' => 'mw-version-skin' ],
- $this->msg( 'version-skins' )->text()
- ) .
- Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ] );
- $this->firstExtOpened = false;
- $out .= $this->getExtensionCategory( 'skin', null );
- $out .= Xml::closeElement( 'table' );
- return $out;
- }
- /**
- * Generate an HTML table for external libraries that are installed
- *
- * @return string
- */
- protected function getExternalLibraries() {
- global $IP;
- $path = "$IP/vendor/composer/installed.json";
- if ( !file_exists( $path ) ) {
- return '';
- }
- $installed = new ComposerInstalled( $path );
- $out = Html::element(
- 'h2',
- [ 'id' => 'mw-version-libraries' ],
- $this->msg( 'version-libraries' )->text()
- );
- $out .= Html::openElement(
- 'table',
- [ 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' ]
- );
- $out .= Html::openElement( 'tr' )
- . Html::element( 'th', [], $this->msg( 'version-libraries-library' )->text() )
- . Html::element( 'th', [], $this->msg( 'version-libraries-version' )->text() )
- . Html::element( 'th', [], $this->msg( 'version-libraries-license' )->text() )
- . Html::element( 'th', [], $this->msg( 'version-libraries-description' )->text() )
- . Html::element( 'th', [], $this->msg( 'version-libraries-authors' )->text() )
- . Html::closeElement( 'tr' );
- foreach ( $installed->getInstalledDependencies() as $name => $info ) {
- if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
- // Skip any extensions or skins since they'll be listed
- // in their proper section
- continue;
- }
- $authors = array_map( function ( $arr ) {
- // If a homepage is set, link to it
- if ( isset( $arr['homepage'] ) ) {
- return "[{$arr['homepage']} {$arr['name']}]";
- }
- return $arr['name'];
- }, $info['authors'] );
- $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
- // We can safely assume that the libraries' names and descriptions
- // are written in English and aren't going to be translated,
- // so set appropriate lang and dir attributes
- $out .= Html::openElement( 'tr' )
- . Html::rawElement(
- 'td',
- [],
- Linker::makeExternalLink(
- "https://packagist.org/packages/$name", $name,
- true, '',
- [ 'class' => 'mw-version-library-name' ]
- )
- )
- . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
- . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['licenses'] ) )
- . Html::element( 'td', [ 'lang' => 'en', 'dir' => 'ltr' ], $info['description'] )
- . Html::rawElement( 'td', [], $authors )
- . Html::closeElement( 'tr' );
- }
- $out .= Html::closeElement( 'table' );
- return $out;
- }
- /**
- * Obtains a list of installed parser tags and the associated H2 header
- *
- * @return string HTML output
- */
- protected function getParserTags() {
- $tags = MediaWikiServices::getInstance()->getParser()->getTags();
- if ( count( $tags ) ) {
- $out = Html::rawElement(
- 'h2',
- [
- 'class' => 'mw-headline plainlinks',
- 'id' => 'mw-version-parser-extensiontags',
- ],
- Linker::makeExternalLink(
- 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
- $this->msg( 'version-parser-extensiontags' )->parse(),
- false /* msg()->parse() already escapes */
- )
- );
- array_walk( $tags, function ( &$value ) {
- // Bidirectional isolation improves readability in RTL wikis
- $value = Html::element(
- 'bdi',
- // Prevent < and > from slipping to another line
- [
- 'style' => 'white-space: nowrap;',
- ],
- "<$value>"
- );
- } );
- $out .= $this->listToText( $tags );
- } else {
- $out = '';
- }
- return $out;
- }
- /**
- * Obtains a list of installed parser function hooks and the associated H2 header
- *
- * @return string HTML output
- */
- protected function getParserFunctionHooks() {
- $fhooks = MediaWikiServices::getInstance()->getParser()->getFunctionHooks();
- if ( count( $fhooks ) ) {
- $out = Html::rawElement(
- 'h2',
- [
- 'class' => 'mw-headline plainlinks',
- 'id' => 'mw-version-parser-function-hooks',
- ],
- Linker::makeExternalLink(
- 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
- $this->msg( 'version-parser-function-hooks' )->parse(),
- false /* msg()->parse() already escapes */
- )
- );
- $out .= $this->listToText( $fhooks );
- } else {
- $out = '';
- }
- return $out;
- }
- /**
- * Creates and returns the HTML for a single extension category.
- *
- * @since 1.17
- *
- * @param string $type
- * @param string $message
- *
- * @return string
- */
- protected function getExtensionCategory( $type, $message ) {
- $config = $this->getConfig();
- $extensionCredits = $config->get( 'ExtensionCredits' );
- $out = '';
- if ( array_key_exists( $type, $extensionCredits ) && count( $extensionCredits[$type] ) > 0 ) {
- $out .= $this->openExtType( $message, 'credits-' . $type );
- usort( $extensionCredits[$type], [ $this, 'compare' ] );
- foreach ( $extensionCredits[$type] as $extension ) {
- $out .= $this->getCreditsForExtension( $type, $extension );
- }
- }
- return $out;
- }
- /**
- * Callback to sort extensions by type.
- * @param array $a
- * @param array $b
- * @return int
- */
- public function compare( $a, $b ) {
- return $this->getLanguage()->lc( $a['name'] ) <=> $this->getLanguage()->lc( $b['name'] );
- }
- /**
- * Creates and formats a version line for a single extension.
- *
- * Information for five columns will be created. Parameters required in the
- * $extension array for part rendering are indicated in ()
- * - The name of (name), and URL link to (url), the extension
- * - Official version number (version) and if available version control system
- * revision (path), link, and date
- * - If available the short name of the license (license-name) and a link
- * to ((LICENSE)|(COPYING))(\.txt)? if it exists.
- * - Description of extension (descriptionmsg or description)
- * - List of authors (author) and link to a ((AUTHORS)|(CREDITS))(\.txt)? file if it exists
- *
- * @param string $type Category name of the extension
- * @param array $extension
- *
- * @return string Raw HTML
- */
- public function getCreditsForExtension( $type, array $extension ) {
- $out = $this->getOutput();
- // We must obtain the information for all the bits and pieces!
- // ... such as extension names and links
- if ( isset( $extension['namemsg'] ) ) {
- // Localized name of extension
- $extensionName = $this->msg( $extension['namemsg'] )->text();
- } elseif ( isset( $extension['name'] ) ) {
- // Non localized version
- $extensionName = $extension['name'];
- } else {
- $extensionName = $this->msg( 'version-no-ext-name' )->text();
- }
- if ( isset( $extension['url'] ) ) {
- $extensionNameLink = Linker::makeExternalLink(
- $extension['url'],
- $extensionName,
- true,
- '',
- [ 'class' => 'mw-version-ext-name' ]
- );
- } else {
- $extensionNameLink = htmlspecialchars( $extensionName );
- }
- // ... and the version information
- // If the extension path is set we will check that directory for GIT
- // metadata in an attempt to extract date and vcs commit metadata.
- $canonicalVersion = '–';
- $extensionPath = null;
- $vcsVersion = null;
- $vcsLink = null;
- $vcsDate = null;
- if ( isset( $extension['version'] ) ) {
- $canonicalVersion = $out->parseInlineAsInterface( $extension['version'] );
- }
- if ( isset( $extension['path'] ) ) {
- global $IP;
- $extensionPath = dirname( $extension['path'] );
- if ( $this->coreId == '' ) {
- wfDebug( 'Looking up core head id' );
- $coreHeadSHA1 = self::getGitHeadSha1( $IP );
- if ( $coreHeadSHA1 ) {
- $this->coreId = $coreHeadSHA1;
- }
- }
- $cache = wfGetCache( CACHE_ANYTHING );
- $memcKey = $cache->makeKey(
- 'specialversion-ext-version-text', $extension['path'], $this->coreId
- );
- list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
- if ( !$vcsVersion ) {
- wfDebug( "Getting VCS info for extension {$extension['name']}" );
- $gitInfo = new GitInfo( $extensionPath );
- $vcsVersion = $gitInfo->getHeadSHA1();
- if ( $vcsVersion !== false ) {
- $vcsVersion = substr( $vcsVersion, 0, 7 );
- $vcsLink = $gitInfo->getHeadViewUrl();
- $vcsDate = $gitInfo->getHeadCommitDate();
- }
- $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
- } else {
- wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
- }
- }
- $versionString = Html::rawElement(
- 'span',
- [ 'class' => 'mw-version-ext-version' ],
- $canonicalVersion
- );
- if ( $vcsVersion ) {
- if ( $vcsLink ) {
- $vcsVerString = Linker::makeExternalLink(
- $vcsLink,
- $this->msg( 'version-version', $vcsVersion ),
- true,
- '',
- [ 'class' => 'mw-version-ext-vcs-version' ]
- );
- } else {
- $vcsVerString = Html::element( 'span',
- [ 'class' => 'mw-version-ext-vcs-version' ],
- "({$vcsVersion})"
- );
- }
- $versionString .= " {$vcsVerString}";
- if ( $vcsDate ) {
- $vcsTimeString = Html::element( 'span',
- [ 'class' => 'mw-version-ext-vcs-timestamp' ],
- $this->getLanguage()->timeanddate( $vcsDate, true )
- );
- $versionString .= " {$vcsTimeString}";
- }
- $versionString = Html::rawElement( 'span',
- [ 'class' => 'mw-version-ext-meta-version' ],
- $versionString
- );
- }
- // ... and license information; if a license file exists we
- // will link to it
- $licenseLink = '';
- if ( isset( $extension['name'] ) ) {
- $licenseName = null;
- if ( isset( $extension['license-name'] ) ) {
- $licenseName = new HtmlArmor( $out->parseInlineAsInterface( $extension['license-name'] ) );
- } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
- $licenseName = $this->msg( 'version-ext-license' )->text();
- }
- if ( $licenseName !== null ) {
- $licenseLink = $this->getLinkRenderer()->makeLink(
- $this->getPageTitle( 'License/' . $extension['name'] ),
- $licenseName,
- [
- 'class' => 'mw-version-ext-license',
- 'dir' => 'auto',
- ]
- );
- }
- }
- // ... and generate the description; which can be a parameterized l10n message
- // in the form [ <msgname>, <parameter>, <parameter>... ] or just a straight
- // up string
- if ( isset( $extension['descriptionmsg'] ) ) {
- // Localized description of extension
- $descriptionMsg = $extension['descriptionmsg'];
- if ( is_array( $descriptionMsg ) ) {
- $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
- array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
- array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
- $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
- } else {
- $description = $this->msg( $descriptionMsg )->text();
- }
- } elseif ( isset( $extension['description'] ) ) {
- // Non localized version
- $description = $extension['description'];
- } else {
- $description = '';
- }
- $description = $out->parseInlineAsInterface( $description );
- // ... now get the authors for this extension
- $authors = $extension['author'] ?? [];
- $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
- // Finally! Create the table
- $html = Html::openElement( 'tr', [
- 'class' => 'mw-version-ext',
- 'id' => Sanitizer::escapeIdForAttribute( 'mw-version-ext-' . $type . '-' . $extension['name'] )
- ]
- );
- $html .= Html::rawElement( 'td', [], $extensionNameLink );
- $html .= Html::rawElement( 'td', [], $versionString );
- $html .= Html::rawElement( 'td', [], $licenseLink );
- $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-description' ], $description );
- $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-authors' ], $authors );
- $html .= Html::closeElement( 'tr' );
- return $html;
- }
- /**
- * Generate wikitext showing hooks in $wgHooks.
- *
- * @return string Wikitext
- */
- private function getWgHooks() {
- global $wgSpecialVersionShowHooks, $wgHooks;
- if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
- $myWgHooks = $wgHooks;
- ksort( $myWgHooks );
- $ret = [];
- $ret[] = '== {{int:version-hooks}} ==';
- $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
- $ret[] = Html::openElement( 'tr' );
- $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
- $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
- $ret[] = Html::closeElement( 'tr' );
- foreach ( $myWgHooks as $hook => $hooks ) {
- $ret[] = Html::openElement( 'tr' );
- $ret[] = Html::element( 'td', [], $hook );
- $ret[] = Html::element( 'td', [], $this->listToText( $hooks ) );
- $ret[] = Html::closeElement( 'tr' );
- }
- $ret[] = Html::closeElement( 'table' );
- return implode( "\n", $ret );
- }
- return '';
- }
- private function openExtType( $text = null, $name = null ) {
- $out = '';
- $opt = [ 'colspan' => 5 ];
- if ( $this->firstExtOpened ) {
- // Insert a spacing line
- $out .= Html::rawElement( 'tr', [ 'class' => 'sv-space' ],
- Html::element( 'td', $opt )
- );
- }
- $this->firstExtOpened = true;
- if ( $name ) {
- $opt['id'] = "sv-$name";
- }
- if ( $text !== null ) {
- $out .= Html::rawElement( 'tr', [],
- Html::element( 'th', $opt, $text )
- );
- }
- $firstHeadingMsg = ( $name === 'credits-skin' )
- ? 'version-skin-colheader-name'
- : 'version-ext-colheader-name';
- $out .= Html::openElement( 'tr' );
- $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
- $this->msg( $firstHeadingMsg )->text() );
- $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
- $this->msg( 'version-ext-colheader-version' )->text() );
- $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
- $this->msg( 'version-ext-colheader-license' )->text() );
- $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
- $this->msg( 'version-ext-colheader-description' )->text() );
- $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
- $this->msg( 'version-ext-colheader-credits' )->text() );
- $out .= Html::closeElement( 'tr' );
- return $out;
- }
- /**
- * Get information about client's IP address.
- *
- * @return string HTML fragment
- */
- private function IPInfo() {
- $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
- return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
- }
- /**
- * Return a formatted unsorted list of authors
- *
- * 'And Others'
- * If an item in the $authors array is '...' it is assumed to indicate an
- * 'and others' string which will then be linked to an ((AUTHORS)|(CREDITS))(\.txt)?
- * file if it exists in $dir.
- *
- * Similarly an entry ending with ' ...]' is assumed to be a link to an
- * 'and others' page.
- *
- * If no '...' string variant is found, but an authors file is found an
- * 'and others' will be added to the end of the credits.
- *
- * @param string|array $authors
- * @param string|bool $extName Name of the extension for link creation,
- * false if no links should be created
- * @param string $extDir Path to the extension root directory
- *
- * @return string HTML fragment
- */
- public function listAuthors( $authors, $extName, $extDir ) {
- $hasOthers = false;
- $linkRenderer = $this->getLinkRenderer();
- $list = [];
- $authors = (array)$authors;
- // Special case: if the authors array has only one item and it is "...",
- // it should not be rendered as the "version-poweredby-others" i18n msg,
- // but rather as "version-poweredby-various" i18n msg instead.
- if ( count( $authors ) === 1 && $authors[0] === '...' ) {
- // Link to the extension's or skin's AUTHORS or CREDITS file, if there is
- // such a file; otherwise just return the i18n msg as-is
- if ( $extName && $this->getExtAuthorsFileName( $extDir ) ) {
- return $linkRenderer->makeLink(
- $this->getPageTitle( "Credits/$extName" ),
- $this->msg( 'version-poweredby-various' )->text()
- );
- } else {
- return $this->msg( 'version-poweredby-various' )->escaped();
- }
- }
- // Otherwise, if we have an actual array that has more than one item,
- // process each array item as usual
- foreach ( $authors as $item ) {
- if ( $item == '...' ) {
- $hasOthers = true;
- if ( $extName && $this->getExtAuthorsFileName( $extDir ) ) {
- $text = $linkRenderer->makeLink(
- $this->getPageTitle( "Credits/$extName" ),
- $this->msg( 'version-poweredby-others' )->text()
- );
- } else {
- $text = $this->msg( 'version-poweredby-others' )->escaped();
- }
- $list[] = $text;
- } elseif ( substr( $item, -5 ) == ' ...]' ) {
- $hasOthers = true;
- $list[] = $this->getOutput()->parseInlineAsInterface(
- substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
- );
- } else {
- $list[] = $this->getOutput()->parseInlineAsInterface( $item );
- }
- }
- if ( $extName && !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
- $list[] = $text = $linkRenderer->makeLink(
- $this->getPageTitle( "Credits/$extName" ),
- $this->msg( 'version-poweredby-others' )->text()
- );
- }
- return $this->listToText( $list, false );
- }
- /**
- * Obtains the full path of an extensions authors or credits file if
- * one exists.
- *
- * @param string $extDir Path to the extensions root directory
- *
- * @since 1.23
- *
- * @return bool|string False if no such file exists, otherwise returns
- * a path to it.
- */
- public static function getExtAuthorsFileName( $extDir ) {
- if ( !$extDir ) {
- return false;
- }
- foreach ( scandir( $extDir ) as $file ) {
- $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
- if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt|\.wiki|\.mediawiki)?$/', $file ) &&
- is_readable( $fullPath ) &&
- is_file( $fullPath )
- ) {
- return $fullPath;
- }
- }
- return false;
- }
- /**
- * Obtains the full path of an extensions copying or license file if
- * one exists.
- *
- * @param string $extDir Path to the extensions root directory
- *
- * @since 1.23
- *
- * @return bool|string False if no such file exists, otherwise returns
- * a path to it.
- */
- public static function getExtLicenseFileName( $extDir ) {
- if ( !$extDir ) {
- return false;
- }
- foreach ( scandir( $extDir ) as $file ) {
- $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
- if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
- is_readable( $fullPath ) &&
- is_file( $fullPath )
- ) {
- return $fullPath;
- }
- }
- return false;
- }
- /**
- * Convert an array of items into a list for display.
- *
- * @param array $list List of elements to display
- * @param bool $sort Whether to sort the items in $list
- *
- * @return string
- */
- public function listToText( $list, $sort = true ) {
- if ( !count( $list ) ) {
- return '';
- }
- if ( $sort ) {
- sort( $list );
- }
- return $this->getLanguage()
- ->listToText( array_map( [ __CLASS__, 'arrayToString' ], $list ) );
- }
- /**
- * Convert an array or object to a string for display.
- *
- * @param mixed $list Will convert an array to string if given and return
- * the parameter unaltered otherwise
- *
- * @return mixed
- */
- public static function arrayToString( $list ) {
- if ( is_array( $list ) && count( $list ) == 1 ) {
- $list = $list[0];
- }
- if ( $list instanceof Closure ) {
- // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
- return 'Closure';
- } elseif ( is_object( $list ) ) {
- $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
- return $class;
- } elseif ( !is_array( $list ) ) {
- return $list;
- } else {
- if ( is_object( $list[0] ) ) {
- $class = get_class( $list[0] );
- } else {
- $class = $list[0];
- }
- return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
- }
- }
- /**
- * @param string $dir Directory of the git checkout
- * @return bool|string Sha1 of commit HEAD points to
- */
- public static function getGitHeadSha1( $dir ) {
- $repo = new GitInfo( $dir );
- return $repo->getHeadSHA1();
- }
- /**
- * @param string $dir Directory of the git checkout
- * @return bool|string Branch currently checked out
- */
- public static function getGitCurrentBranch( $dir ) {
- $repo = new GitInfo( $dir );
- return $repo->getCurrentBranch();
- }
- /**
- * Get the list of entry points and their URLs
- * @return string Wikitext
- */
- public function getEntryPointInfo() {
- $config = $this->getConfig();
- $scriptPath = $config->get( 'ScriptPath' ) ?: '/';
- $entryPoints = [
- 'version-entrypoints-articlepath' => $config->get( 'ArticlePath' ),
- 'version-entrypoints-scriptpath' => $scriptPath,
- 'version-entrypoints-index-php' => wfScript( 'index' ),
- 'version-entrypoints-api-php' => wfScript( 'api' ),
- 'version-entrypoints-load-php' => wfScript( 'load' ),
- ];
- $language = $this->getLanguage();
- $thAttribures = [
- 'dir' => $language->getDir(),
- 'lang' => $language->getHtmlCode()
- ];
- $out = Html::element(
- 'h2',
- [ 'id' => 'mw-version-entrypoints' ],
- $this->msg( 'version-entrypoints' )->text()
- ) .
- Html::openElement( 'table',
- [
- 'class' => 'wikitable plainlinks',
- 'id' => 'mw-version-entrypoints-table',
- 'dir' => 'ltr',
- 'lang' => 'en'
- ]
- ) .
- Html::openElement( 'tr' ) .
- Html::element(
- 'th',
- $thAttribures,
- $this->msg( 'version-entrypoints-header-entrypoint' )->text()
- ) .
- Html::element(
- 'th',
- $thAttribures,
- $this->msg( 'version-entrypoints-header-url' )->text()
- ) .
- Html::closeElement( 'tr' );
- foreach ( $entryPoints as $message => $value ) {
- $url = wfExpandUrl( $value, PROTO_RELATIVE );
- $out .= Html::openElement( 'tr' ) .
- // ->plain() looks like it should be ->parse(), but this function
- // returns wikitext, not HTML, boo
- Html::rawElement( 'td', [], $this->msg( $message )->plain() ) .
- Html::rawElement( 'td', [], Html::rawElement( 'code', [], "[$url $value]" ) ) .
- Html::closeElement( 'tr' );
- }
- $out .= Html::closeElement( 'table' );
- return $out;
- }
- protected function getGroupName() {
- return 'wiki';
- }
- }
|