15use MediaWiki\Html\TocGeneratorTrait;
29use Symfony\Component\Yaml\Yaml;
40 use TocGeneratorTrait;
57 parent::__construct(
'Version' );
70 $credits[$credit[
'type']][] = $credit;
79 $config = $this->getConfig();
80 $credits = self::getCredits( ExtensionRegistry::getInstance(), $config );
83 $this->outputHeader();
84 $out = $this->getOutput();
85 $out->getMetadata()->setPreventClickjacking(
false );
88 $parts = explode(
'/', (
string)$par );
90 if ( isset( $parts[1] ) ) {
91 $extName = str_replace(
'_',
' ', $parts[1] );
93 foreach ( $credits as $extensions ) {
94 foreach ( $extensions as $ext ) {
95 if ( isset( $ext[
'name'] ) && ( $ext[
'name'] === $extName ) ) {
102 $out->setStatusCode( 404 );
105 $extName =
'MediaWiki';
109 switch ( strtolower( $parts[0] ) ) {
111 $out->addModuleStyles(
'mediawiki.special' );
113 $wikiText =
'{{int:version-credits-not-found}}';
114 if ( $extName ===
'MediaWiki' ) {
115 $wikiText = file_get_contents( MW_INSTALL_PATH .
'/CREDITS' );
117 $wikiText = str_replace(
118 [
'<!-- BEGIN CONTRIBUTOR LIST -->',
'<!-- END CONTRIBUTOR LIST -->' ],
119 [
'<div class="mw-version-credits">',
'</div>' ],
122 } elseif ( ( $extNode !==
null ) && isset( $extNode[
'path'] ) ) {
123 $file = ExtensionInfo::getAuthorsFileName( dirname( $extNode[
'path'] ) );
125 $wikiText = file_get_contents( $file );
126 if ( str_ends_with( $file,
'.txt' ) ) {
139 $out->setPageTitleMsg( $this->msg(
'version-credits-title' )->plaintextParams( $extName ) );
140 $out->addWikiTextAsInterface( $wikiText );
144 $out->setPageTitleMsg( $this->msg(
'version-license-title' )->plaintextParams( $extName ) );
146 $licenseFound =
false;
148 if ( $extName ===
'MediaWiki' ) {
149 $out->addWikiTextAsInterface(
150 file_get_contents( MW_INSTALL_PATH .
'/COPYING' )
152 $licenseFound =
true;
153 } elseif ( ( $extNode !==
null ) && isset( $extNode[
'path'] ) ) {
154 $files = ExtensionInfo::getLicenseFileNames( dirname( $extNode[
'path'] ) );
156 $licenseFound =
true;
157 foreach ( $files as $file ) {
158 $out->addWikiTextAsInterface(
165 file_get_contents( $file )
171 if ( !$licenseFound ) {
172 $out->addWikiTextAsInterface(
'{{int:version-license-not-found}}' );
177 $out->addModuleStyles(
'mediawiki.special' );
179 $out->addHTML( $this->getMediaWikiCredits() );
183 $this->softwareInformation(),
184 $this->getEntryPointInfo(),
185 $this->getSkinCredits( $credits ),
186 $this->getExtensionCredits( $credits ),
187 $this->getLibraries( $credits ),
188 $this->getParserTags(),
189 $this->getParserFunctionHooks(),
190 $this->getParsoidModules(),
196 $out->addTOCPlaceholder( $this->getTocData() );
199 foreach ( $sections as $content ) {
200 $out->addHTML( $content );
212 private function getMediaWikiCredits() {
217 [
'id' =>
'mw-version-license' ],
218 $this->msg(
'version-license' )->text()
221 $ret .= Html::rawElement(
'div', [
'class' =>
'plainlinks' ],
222 $this->msg(
new RawMessage( self::getCopyrightAndAuthorList() ) )->parseAsBlock() .
223 Html::rawElement(
'div', [
'class' =>
'mw-version-license-info' ],
224 $this->msg(
'version-license-info' )->parseAsBlock()
238 if ( defined(
'MEDIAWIKI_INSTALL' ) ) {
239 $othersLink =
'[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
240 wfMessage(
'version-poweredby-others' )->plain() .
']';
242 $othersLink =
'[[Special:Version/Credits|' .
243 wfMessage(
'version-poweredby-others' )->plain() .
']]';
246 $translatorsLink =
'[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
247 wfMessage(
'version-poweredby-translators' )->plain() .
']';
250 'Magnus Manske',
'Brooke Vibber',
'Lee Daniel Crocker',
251 'Tim Starling',
'Erik Möller',
'Gabriel Wicke',
'Ævar Arnfjörð Bjarmason',
252 'Niklas Laxström',
'Domas Mituzas',
'Rob Church',
'Yuri Astrakhan',
253 'Aryeh Gregor',
'Aaron Schulz',
'Andrew Garrett',
'Raimond Spekking',
254 'Alexandre Emsenhuber',
'Siebrand Mazeland',
'Chad Horohoe',
255 'Roan Kattouw',
'Trevor Parscal',
'Bryan Tong Minh',
'Sam Reed',
256 'Victor Vasiliev',
'Rotem Liss',
'Platonides',
'Antoine Musso',
257 'Timo Tijhof',
'Daniel Kinzler',
'Jeroen De Dauw',
'Brad Jorsch',
258 'Bartosz Dziewoński',
'Ed Sanders',
'Moriel Schottlender',
259 'Kunal Mehta',
'James D. Forrester',
'Brian Wolff',
'Adam Shorland',
260 'DannyS712',
'Ori Livneh',
'Max Semenik',
'Amir Sarabadani',
261 'Derk-Jan Hartman',
'Petr Pchelko',
'Umherirrender',
'C. Scott Ananian',
262 'fomafix',
'Thiemo Kreuz',
'Gergő Tisza',
'Volker E.',
263 'Jack Phoenix',
'Isarra Yos',
264 $othersLink, $translatorsLink
267 return wfMessage(
'version-poweredby-credits', MWTimestamp::getLocalInstance()->format(
'Y' ),
276 private function getSoftwareInformation() {
277 $dbr = $this->dbProvider->getReplicaDatabase();
283 '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked(),
284 '[https://php.net/ PHP]' => PHP_VERSION .
" (" . PHP_SAPI .
")",
285 '[https://icu.unicode.org/ ICU]' => INTL_ICU_VERSION,
286 $dbr->getSoftwareLink() => $dbr->getServerInfo(),
290 if ( phpversion(
"wikidiff2" ) ) {
291 $software[
'[https://www.mediawiki.org/wiki/Wikidiff2 wikidiff2]' ] = phpversion(
"wikidiff2" );
295 $this->getHookRunner()->onSoftwareInfo( $software );
305 private function softwareInformation() {
306 $this->addTocSection(
id:
'mw-version-software', msg:
'version-software' );
310 [
'id' =>
'mw-version-software' ],
311 $this->msg(
'version-software' )->text()
314 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-software' ] );
316 $out .= $this->getTableHeaderHtml( [
317 $this->msg(
'version-software-product' )->text(),
318 $this->msg(
'version-software-version' )->text()
321 foreach ( $this->getSoftwareInformation() as $name => $version ) {
322 $out .= Html::rawElement(
325 Html::rawElement(
'td', [], $this->msg(
new RawMessage( $name ) )->parse() ) .
326 Html::rawElement(
'td', [
'dir' =>
'ltr' ], $this->msg(
new RawMessage( $version ) )->parse() )
330 $out .= Html::closeElement(
'table' );
344 public static function getVersion( $flags =
'', $lang =
null ) {
345 $gitInfo = GitInfo::repo()->getHeadSHA1();
348 } elseif ( $flags ===
'nodb' ) {
349 $shortSha1 = substr( $gitInfo, 0, 7 );
352 $shortSha1 = substr( $gitInfo, 0, 7 );
354 if ( $lang !==
null ) {
355 $msg->inLanguage( $lang );
357 $shortSha1 = $msg->params( $shortSha1 )->text();
372 return self::getVersionLinkedGit() ?:
MW_VERSION;
378 private static function getMWVersionLinked() {
381 if ( $hookRunner->onSpecialVersionVersionUrl(
MW_VERSION, $versionUrl ) ) {
383 preg_match(
"/^(\d+\.\d+)/",
MW_VERSION, $versionParts );
384 $versionUrl =
"https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
387 return '[' . $versionUrl .
' ' .
MW_VERSION .
']';
395 private static function getVersionLinkedGit() {
398 $gitInfo =
new GitInfo( MW_INSTALL_PATH );
399 $headSHA1 = $gitInfo->getHeadSHA1();
404 $shortSHA1 =
'(' . substr( $headSHA1, 0, 7 ) .
')';
406 $gitHeadUrl = $gitInfo->getHeadViewUrl();
407 if ( $gitHeadUrl !==
false ) {
408 $shortSHA1 =
"[$gitHeadUrl $shortSHA1]";
411 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
412 if ( $gitHeadCommitDate ) {
416 return self::getMWVersionLinked() .
" $shortSHA1";
429 if ( self::$extensionTypes === false ) {
430 self::$extensionTypes = [
431 'specialpage' =>
wfMessage(
'version-specialpages' )->text(),
432 'editor' =>
wfMessage(
'version-editors' )->text(),
433 'parserhook' =>
wfMessage(
'version-parserhooks' )->text(),
434 'variable' =>
wfMessage(
'version-variables' )->text(),
435 'media' =>
wfMessage(
'version-mediahandlers' )->text(),
436 'antispam' =>
wfMessage(
'version-antispam' )->text(),
437 'skin' =>
wfMessage(
'version-skins' )->text(),
438 'api' =>
wfMessage(
'version-api' )->text(),
439 'other' =>
wfMessage(
'version-other' )->text(),
443 ->onExtensionTypes( self::$extensionTypes );
446 return self::$extensionTypes;
459 $types = self::getExtensionTypes();
461 return $types[$type] ?? $types[
'other'];
470 private function getExtensionCredits( array $credits ) {
471 $extensionTypes = self::getExtensionTypes();
473 $this->addTocSection(
id:
'mw-version-ext', msg:
'version-extensions' );
475 $out = Html::element(
477 [
'id' =>
'mw-version-ext' ],
478 $this->msg(
'version-extensions' )->text()
484 ( count( $credits ) === 1 && isset( $credits[
'skin'] ) )
486 $out .= Html::element(
489 $this->msg(
'version-extensions-no-ext' )->text()
496 $credits[
'other'] ??= [];
497 foreach ( $credits as $type => $extensions ) {
498 if ( !array_key_exists( $type, $extensionTypes ) ) {
499 $credits[
'other'] = array_merge( $credits[
'other'], $extensions );
504 foreach ( $extensionTypes as $type => $text ) {
506 if ( $type !==
'other' && $type !==
'skin' ) {
507 $out .= $this->getExtensionCategory( $type, $text, $credits[$type] ?? [] );
512 $out .= $this->getExtensionCategory(
'other', $extensionTypes[
'other'], $credits[
'other'] );
523 private function getSkinCredits( array $credits ) {
526 $out = Html::element(
528 [
'id' =>
'mw-version-skin' ],
529 $this->
msg(
'version-skins' )->text()
532 if ( !isset( $credits[
'skin'] ) || !$credits[
'skin'] ) {
533 $out .= Html::element(
536 $this->
msg(
'version-skins-no-skin' )->text()
541 $out .= $this->getExtensionCategory(
'skin',
null, $credits[
'skin'] );
553 $this->addTocSection(
id:
'mw-version-libraries', msg:
'version-libraries' );
555 $out = Html::element(
557 [
'id' =>
'mw-version-libraries' ],
558 $this->msg(
'version-libraries' )->text()
562 . $this->getExternalLibraries( $credits )
563 . $this->getClientSideLibraries();
574 MW_INSTALL_PATH .
'/vendor/composer/installed.json'
577 $extensionTypes = self::getExtensionTypes();
578 foreach ( $extensionTypes as $type => $message ) {
579 if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
582 foreach ( $credits[$type] as $extension ) {
583 if ( !isset( $extension[
'path'] ) ) {
586 $paths[] = dirname( $extension[
'path'] ) .
'/vendor/composer/installed.json';
592 foreach ( $paths as
$path ) {
593 if ( !file_exists(
$path ) ) {
599 $dependencies += $installed->getInstalledDependencies();
602 ksort( $dependencies );
603 return $dependencies;
613 $dependencies = self::parseComposerInstalled( $credits );
614 if ( $dependencies === [] ) {
618 $this->addTocSubSection(
id:
'mw-version-libraries-server', msg:
'version-libraries-server' );
620 $out = Html::element(
622 [
'id' =>
'mw-version-libraries-server' ],
623 $this->msg(
'version-libraries-server' )->text()
625 $out .= Html::openElement(
627 [
'class' =>
'wikitable plainlinks mw-installed-software',
'id' =>
'sv-libraries' ]
630 $out .= $this->getTableHeaderHtml( [
631 $this->msg(
'version-libraries-library' )->text(),
632 $this->msg(
'version-libraries-version' )->text(),
633 $this->msg(
'version-libraries-license' )->text(),
634 $this->msg(
'version-libraries-description' )->text(),
635 $this->msg(
'version-libraries-authors' )->text(),
638 foreach ( $dependencies as $name => $info ) {
639 if ( !is_array( $info ) || str_starts_with( $info[
'type'],
'mediawiki-' ) ) {
644 $authors = array_map(
static function ( $arr ) {
645 return new HtmlArmor( isset( $arr[
'homepage'] ) ?
646 Html::element(
'a', [
'href' => $arr[
'homepage'] ], $arr[
'name'] ) :
647 htmlspecialchars( $arr[
'name'] )
649 }, $info[
'authors'] );
650 $authors = $this->listAuthors( $authors,
false, MW_INSTALL_PATH .
"/vendor/$name" );
655 $out .= Html::openElement(
'tr', [
658 'id' => Sanitizer::escapeIdForAttribute(
659 "mw-version-library-$name"
664 $this->getLinkRenderer()->makeExternalLink(
665 "https://packagist.org/packages/$name",
667 $this->getFullTitle(),
669 [
'class' =>
'mw-version-library-name' ]
672 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
674 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'licenses'] ) )
675 . Html::element(
'td', [
'lang' =>
'en',
'dir' =>
'ltr' ], $info[
'description'] )
676 . Html::rawElement(
'td', [], $authors )
677 . Html::closeElement(
'tr' );
679 $out .= Html::closeElement(
'table' );
690 $registryDirs = [
'MediaWiki' => MW_INSTALL_PATH .
'/resources/lib' ]
691 + ExtensionRegistry::getInstance()->getAttribute(
'ForeignResourcesDir' );
694 foreach ( $registryDirs as
$source => $registryDir ) {
695 $foreignResources = Yaml::parseFile(
"$registryDir/foreign-resources.yaml" );
696 foreach ( $foreignResources as $name => $module ) {
697 $key = $name . $module[
'version'];
698 if ( isset( $modules[$key] ) ) {
699 $modules[$key][
'source'][] =
$source;
702 $modules[$key] = $module + [
'name' => $name,
'source' => [
$source ] ];
714 private function getClientSideLibraries() {
715 $this->addTocSubSection(
id:
'mw-version-libraries-client', msg:
'version-libraries-client' );
717 $out = Html::element(
719 [
'id' =>
'mw-version-libraries-client' ],
720 $this->msg(
'version-libraries-client' )->text()
722 $out .= Html::openElement(
724 [
'class' =>
'wikitable plainlinks mw-installed-software',
'id' =>
'sv-libraries-client' ]
727 $out .= $this->getTableHeaderHtml( [
728 $this->msg(
'version-libraries-library' )->text(),
729 $this->msg(
'version-libraries-version' )->text(),
730 $this->msg(
'version-libraries-license' )->text(),
731 $this->msg(
'version-libraries-authors' )->text(),
732 $this->msg(
'version-libraries-source' )->text()
735 foreach ( self::parseForeignResources() as $name => $info ) {
739 $out .= Html::openElement(
'tr', [
742 'id' => Sanitizer::escapeIdForAttribute(
743 "mw-version-library-$name"
748 $this->getLinkRenderer()->makeExternalLink(
751 $this->getFullTitle(),
753 [
'class' =>
'mw-version-library-name' ]
756 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
757 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'license'] )
758 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'authors'] ??
'—' )
760 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'source'] ) )
761 . Html::closeElement(
'tr' );
763 $out .= Html::closeElement(
'table' );
774 $tags = $this->parserFactory->getMainInstance()->getTags();
779 $this->addTocSection(
id:
'mw-version-parser-extensiontags', msg:
'version-parser-extensiontags' );
781 $out = Html::rawElement(
783 [
'id' =>
'mw-version-parser-extensiontags' ],
786 [
'class' =>
'plainlinks' ],
787 $this->getLinkRenderer()->makeExternalLink(
788 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
789 $this->msg(
'version-parser-extensiontags' ),
790 $this->getFullTitle()
795 array_walk( $tags,
static function ( &$value ) {
797 $value = Html::rawElement(
801 'style' =>
'white-space: nowrap;',
803 Html::element(
'code', [],
"<$value>" )
807 $out .= $this->listToText( $tags );
818 $funcHooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
823 $this->addTocSection(
id:
'mw-version-parser-function-hooks', msg:
'version-parser-function-hooks' );
825 $out = Html::rawElement(
827 [
'id' =>
'mw-version-parser-function-hooks' ],
830 [
'class' =>
'plainlinks' ],
831 $this->getLinkRenderer()->makeExternalLink(
832 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
833 $this->msg(
'version-parser-function-hooks' ),
834 $this->getFullTitle()
839 $funcSynonyms = $this->parserFactory->getMainInstance()->getFunctionSynonyms();
846 $preferredSynonyms = array_flip( array_reverse( $funcSynonyms[1] + $funcSynonyms[0] ) );
847 array_walk( $funcHooks,
static function ( &$value ) use ( $preferredSynonyms ) {
848 $value = $preferredSynonyms[$value];
850 $legacyHooks = array_flip( $funcHooks );
853 $cmpHooks =
static function ( $a, $b ) {
854 return strcasecmp( ltrim( $a,
'#' ), ltrim( $b,
'#' ) );
856 usort( $funcHooks, $cmpHooks );
858 $formatHooks =
static function ( &$value ) {
860 $value = Html::rawElement(
863 Html::element(
'code', [],
'{{' . $value .
'}}' )
866 array_walk( $funcHooks, $formatHooks );
868 $out .= $this->getLanguage()->listToText( $funcHooks );
870 # Get a list of parser functions from Parsoid as well.
872 $services = MediaWikiServices::getInstance();
873 $siteConfig = $services->getParsoidSiteConfig();
874 $magicWordFactory = $services->getMagicWordFactory();
875 foreach ( $siteConfig->getPFragmentHandlerKeys() as $key ) {
876 $config = $siteConfig->getPFragmentHandlerConfig( $key );
877 if ( !( $config[
'options'][
'parserFunction'] ??
false ) ) {
880 $mw = $magicWordFactory->get( $key );
881 foreach ( $mw->getSynonyms() as $local ) {
882 if ( !( $config[
'options'][
'nohash'] ??
false ) ) {
883 $local =
'#' . $local;
887 if ( isset( $legacyHooks[$local] ) ) {
890 $parsoidHooks[] = $local;
893 if ( $parsoidHooks ) {
894 $out .= Html::element(
896 [
'id' =>
'mw-version-parser-function-hooks-parsoid' ],
897 $this->msg(
'version-parser-function-hooks-parsoid' )->text()
899 usort( $parsoidHooks, $cmpHooks );
900 array_walk( $parsoidHooks, $formatHooks );
901 $out .= $this->getLanguage()->listToText( $parsoidHooks );
913 $siteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
914 $modules = $siteConfig->getExtensionModules();
920 $this->addTocSection(
id:
'mw-version-parsoid-modules', msg:
'version-parsoid-modules' );
922 $out = Html::rawElement(
924 [
'id' =>
'mw-version-parsoid-modules' ],
927 [
'class' =>
'plainlinks' ],
928 $this->getLinkRenderer()->makeExternalLink(
929 'https://www.mediawiki.org/wiki/Special:MyLanguage/Parsoid',
930 $this->msg(
'version-parsoid-modules' ),
931 $this->getFullTitle()
936 $moduleNames = array_map(
937 static fn ( $m )=>Html::element(
'code', [
938 'title' => $m->getConfig()[
'extension-name'] ??
null,
939 ], $m->getConfig()[
'name'] ),
943 $out .= $this->getLanguage()->listToText( $moduleNames );
960 if ( $creditsGroup ) {
961 $out .= $this->openExtType( $text,
'credits-' . $type );
963 usort( $creditsGroup, $this->compare( ... ) );
965 foreach ( $creditsGroup as $extension ) {
966 $out .= $this->getCreditsForExtension( $type, $extension );
969 $out .= Html::closeElement(
'table' );
981 private function compare( $a, $b ) {
982 return $this->getLanguage()->lc( $a[
'name'] ) <=> $this->getLanguage()->lc( $b[
'name'] );
1004 $out = $this->getOutput();
1008 if ( isset( $extension[
'namemsg'] ) ) {
1010 $extensionName = $this->msg( $extension[
'namemsg'] )->text();
1011 } elseif ( isset( $extension[
'name'] ) ) {
1013 $extensionName = $extension[
'name'];
1015 $extensionName = $this->msg(
'version-no-ext-name' )->text();
1018 if ( isset( $extension[
'url'] ) ) {
1019 $extensionNameLink = $this->getLinkRenderer()->makeExternalLink(
1022 $this->getFullTitle(),
1024 [
'class' =>
'mw-version-ext-name' ]
1027 $extensionNameLink = htmlspecialchars( $extensionName );
1033 $canonicalVersion =
'–';
1034 $extensionPath =
null;
1039 if ( isset( $extension[
'version'] ) ) {
1040 $canonicalVersion = $out->parseInlineAsInterface( $extension[
'version'] );
1043 if ( isset( $extension[
'path'] ) ) {
1044 $extensionPath = dirname( $extension[
'path'] );
1045 if ( $this->coreId ==
'' ) {
1046 wfDebug(
'Looking up core head id' );
1047 $coreHeadSHA1 = GitInfo::repo()->getHeadSHA1();
1048 if ( $coreHeadSHA1 ) {
1049 $this->coreId = $coreHeadSHA1;
1052 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance(
CACHE_ANYTHING );
1053 $memcKey = $cache->makeKey(
1054 'specialversion-ext-version-text', $extension[
'path'], $this->coreId
1056 $res = $cache->get( $memcKey );
1058 if ( $res ===
false ) {
1059 wfDebug(
"Getting VCS info for extension {$extension['name']}" );
1060 $gitInfo =
new GitInfo( $extensionPath );
1061 $vcsVersion = $gitInfo->getHeadSHA1();
1062 if ( $vcsVersion !==
false ) {
1063 $vcsVersion = substr( $vcsVersion, 0, 7 );
1064 $vcsLink = $gitInfo->getHeadViewUrl();
1065 $vcsDate = $gitInfo->getHeadCommitDate();
1067 $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
1069 wfDebug(
"Pulled VCS info for extension {$extension['name']} from cache" );
1070 [ $vcsVersion, $vcsLink, $vcsDate ] = $res;
1074 $versionString = Html::rawElement(
1076 [
'class' =>
'mw-version-ext-version' ],
1080 if ( $vcsVersion ) {
1082 $vcsVerString = $this->getLinkRenderer()->makeExternalLink(
1084 $this->msg(
'version-version', $vcsVersion ),
1085 $this->getFullTitle(),
1087 [
'class' =>
'mw-version-ext-vcs-version' ]
1090 $vcsVerString = Html::element(
'span',
1091 [
'class' =>
'mw-version-ext-vcs-version' ],
1095 $versionString .=
" {$vcsVerString}";
1098 $versionString .=
' ' . Html::element(
'span', [
1099 'class' =>
'mw-version-ext-vcs-timestamp',
1100 'dir' => $this->getLanguage()->getDir(),
1101 ], $this->getLanguage()->timeanddate( $vcsDate,
true ) );
1103 $versionString = Html::rawElement(
'span',
1104 [
'class' =>
'mw-version-ext-meta-version' ],
1112 if ( isset( $extension[
'name'] ) ) {
1113 $licenseName =
null;
1114 if ( isset( $extension[
'license-name'] ) ) {
1115 $licenseName =
new HtmlArmor( $out->parseInlineAsInterface( $extension[
'license-name'] ) );
1116 } elseif ( $extensionPath !==
null && ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
1117 $licenseName = $this->msg(
'version-ext-license' )->text();
1119 if ( $licenseName !==
null ) {
1120 $licenseLink = $this->getLinkRenderer()->makeLink(
1121 $this->getPageTitle(
'License/' . $extension[
'name'] ),
1124 'class' =>
'mw-version-ext-license',
1134 if ( isset( $extension[
'descriptionmsg'] ) ) {
1136 $descriptionMsg = $extension[
'descriptionmsg'];
1138 if ( is_array( $descriptionMsg ) ) {
1139 $descriptionMsgKey = array_shift( $descriptionMsg );
1140 $descriptionMsg = array_map(
'htmlspecialchars', $descriptionMsg );
1141 $description = $this->msg( $descriptionMsgKey, ...$descriptionMsg )->text();
1143 $description = $this->msg( $descriptionMsg )->text();
1145 } elseif ( isset( $extension[
'description'] ) ) {
1147 $description = $extension[
'description'];
1151 $description = $out->parseInlineAsInterface( $description );
1154 $authors = $extension[
'author'] ?? [];
1156 $authors = $this->listAuthors( $authors, $extension[
'name'], $extensionPath );
1159 $html = Html::openElement(
'tr', [
1160 'class' =>
'mw-version-ext',
1161 'id' => Sanitizer::escapeIdForAttribute(
'mw-version-ext-' . $type .
'-' . $extension[
'name'] )
1165 $html .= Html::rawElement(
'td', [], $extensionNameLink );
1166 $html .= Html::rawElement(
'td', [], $versionString );
1167 $html .= Html::rawElement(
'td', [], $licenseLink );
1168 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-description' ], $description );
1169 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-authors' ], $authors );
1171 $html .= Html::closeElement(
'tr' );
1181 private function getHooks() {
1182 if ( !$this->getConfig()->
get( MainConfigNames::SpecialVersionShowHooks ) ) {
1186 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1187 $hookNames = $hookContainer->getHookNames();
1189 if ( !$hookNames ) {
1196 $this->addTocSection(
id:
'mw-version-hooks', msg:
'version-hooks' );
1197 $ret[] = Html::element(
1199 [
'id' =>
'mw-version-hooks' ],
1200 $this->msg(
'version-hooks' )->text()
1202 $ret[] = Html::openElement(
'table', [
'class' =>
'wikitable',
'id' =>
'sv-hooks' ] );
1203 $ret[] = Html::openElement(
'tr' );
1204 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-name' )->text() );
1205 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-subscribedby' )->text() );
1206 $ret[] = Html::closeElement(
'tr' );
1208 foreach ( $hookNames as $name ) {
1209 $handlers = $hookContainer->getHandlerDescriptions( $name );
1211 $ret[] = Html::openElement(
'tr' );
1212 $ret[] = Html::element(
'td', [], $name );
1214 $ret[] = Html::element(
'td', [], $this->listToText( $handlers ) );
1215 $ret[] = Html::closeElement(
'tr' );
1218 $ret[] = Html::closeElement(
'table' );
1220 return implode(
"\n", $ret );
1223 private function openExtType( ?
string $text =
null, ?
string $name =
null ): string {
1226 $opt = [
'class' =>
'wikitable plainlinks mw-installed-software' ];
1229 $opt[
'id'] =
"sv-$name";
1232 $out .= Html::openElement(
'table', $opt );
1234 if ( $text !==
null ) {
1235 $out .= Html::element(
'caption', [], $text );
1238 if ( $name && $text !==
null ) {
1242 $firstHeadingMsg = ( $name ===
'credits-skin' )
1243 ?
'version-skin-colheader-name'
1244 :
'version-ext-colheader-name';
1246 $out .= $this->getTableHeaderHtml( [
1247 $this->
msg( $firstHeadingMsg )->text(),
1248 $this->
msg(
'version-ext-colheader-version' )->text(),
1249 $this->
msg(
'version-ext-colheader-license' )->text(),
1250 $this->
msg(
'version-ext-colheader-description' )->text(),
1251 $this->
msg(
'version-ext-colheader-credits' )->text()
1265 private function getTableHeaderHtml( $headers ): string {
1267 foreach ( $headers as $header ) {
1268 $out .= Html::element(
'th', [
'scope' =>
'col' ], $header );
1270 return Html::rawElement(
'thead', [],
1271 Html::rawElement(
'tr', [], $out )
1280 private function IPInfo() {
1281 $ip = str_replace(
'--',
' - ', htmlspecialchars( $this->
getRequest()->getIP() ) );
1283 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1308 $linkRenderer = $this->getLinkRenderer();
1311 $authors = (array)$authors;
1316 if ( count( $authors ) === 1 && $authors[0] ===
'...' ) {
1319 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1320 return $linkRenderer->makeLink(
1321 $this->getPageTitle(
"Credits/$extName" ),
1322 $this->msg(
'version-poweredby-various' )->text()
1325 return $this->msg(
'version-poweredby-various' )->escaped();
1331 foreach ( $authors as $item ) {
1332 if ( $item instanceof HtmlArmor ) {
1333 $list[] = HtmlArmor::getHtml( $item );
1334 } elseif ( $item ===
'...' ) {
1337 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1338 $text = $linkRenderer->makeLink(
1339 $this->getPageTitle(
"Credits/$extName" ),
1340 $this->
msg(
'version-poweredby-others' )->text()
1343 $text = $this->
msg(
'version-poweredby-others' )->escaped();
1346 } elseif ( str_ends_with( $item,
' ...]' ) ) {
1348 $list[] = $this->
getOutput()->parseInlineAsInterface(
1349 substr( $item, 0, -4 ) . $this->
msg(
'version-poweredby-others' )->text() .
"]"
1352 $list[] = $this->
getOutput()->parseInlineAsInterface( $item );
1356 if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1357 $list[] = $linkRenderer->makeLink(
1358 $this->getPageTitle(
"Credits/$extName" ),
1359 $this->
msg(
'version-poweredby-others' )->text()
1363 return $this->listToText( $list,
false );
1375 private function listToText( array $list,
bool $sort =
true ): string {
1383 return $this->getLanguage()
1384 ->listToText( array_map( self::arrayToString( ... ), $list ) );
1397 if ( is_array( $list ) && count( $list ) == 1 ) {
1400 if ( $list instanceof Closure ) {
1403 } elseif ( is_object( $list ) ) {
1404 return wfMessage(
'parentheses' )->params( get_class( $list ) )->escaped();
1405 } elseif ( !is_array( $list ) ) {
1408 if ( is_object( $list[0] ) ) {
1409 $class = get_class( $list[0] );
1414 return wfMessage(
'parentheses' )->params(
"$class, {$list[1]}" )->escaped();
1425 return (
new GitInfo( $dir ) )->getHeadSHA1();
1433 $config = $this->getConfig();
1434 $scriptPath = $config->get( MainConfigNames::ScriptPath ) ?:
'/';
1437 'version-entrypoints-articlepath' => $config->get( MainConfigNames::ArticlePath ),
1438 'version-entrypoints-scriptpath' => $scriptPath,
1439 'version-entrypoints-index-php' =>
wfScript(
'index' ),
1440 'version-entrypoints-api-php' =>
wfScript(
'api' ),
1441 'version-entrypoints-rest-php' =>
wfScript(
'rest' ),
1444 $language = $this->getLanguage();
1446 'dir' => $language->getDir(),
1447 'lang' => $language->getHtmlCode(),
1451 $this->addTocSection(
id:
'mw-version-entrypoints', msg:
'version-entrypoints' );
1453 $out = Html::element(
1455 [
'id' =>
'mw-version-entrypoints' ],
1456 $this->msg(
'version-entrypoints' )->text()
1458 Html::openElement(
'table',
1460 'class' =>
'wikitable plainlinks',
1461 'id' =>
'mw-version-entrypoints-table',
1466 Html::openElement(
'thead' ) .
1467 Html::openElement(
'tr' ) .
1471 $this->msg(
'version-entrypoints-header-entrypoint' )->text()
1476 $this->msg(
'version-entrypoints-header-url' )->text()
1478 Html::closeElement(
'tr' ) .
1479 Html::closeElement(
'thead' );
1481 foreach ( $entryPoints as $message => $value ) {
1483 $out .= Html::openElement(
'tr' ) .
1484 Html::rawElement(
'td', [], $this->msg( $message )->parse() ) .
1485 Html::rawElement(
'td', [],
1489 $this->msg(
new RawMessage(
"[$url $value]" ) )->parse()
1492 Html::closeElement(
'tr' );
1495 $out .= Html::closeElement(
'table' );
1510class_alias( SpecialVersion::class,
'SpecialVersion' );
const MW_VERSION
The running version of MediaWiki.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
if(!defined('MW_SETUP_CALLBACK'))
A class containing constants representing the names of configuration variables.
Parent class for all special pages.