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();
282 $versionLink = self::getVersionLinkedGit( $this->getLanguage() ) ?:
MW_VERSION;
284 '[https://www.mediawiki.org/ MediaWiki]' => $versionLink,
285 '[https://php.net/ PHP]' => PHP_VERSION .
" (" . PHP_SAPI .
")",
286 '[https://icu.unicode.org/ ICU]' => INTL_ICU_VERSION,
287 $dbr->getSoftwareLink() => $dbr->getServerInfo(),
291 if ( phpversion(
"wikidiff2" ) ) {
292 $software[
'[https://www.mediawiki.org/wiki/Wikidiff2 wikidiff2]' ] = phpversion(
"wikidiff2" );
296 $this->getHookRunner()->onSoftwareInfo( $software );
306 private function softwareInformation() {
307 $this->addTocSection(
id:
'mw-version-software', msg:
'version-software' );
311 [
'id' =>
'mw-version-software' ],
312 $this->msg(
'version-software' )->text()
315 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-software' ] );
317 $out .= $this->getTableHeaderHtml( [
318 $this->msg(
'version-software-product' )->text(),
319 $this->msg(
'version-software-version' )->text()
322 foreach ( $this->getSoftwareInformation() as $name => $version ) {
323 $out .= Html::rawElement(
326 Html::rawElement(
'td', [], $this->msg(
new RawMessage( $name ) )->parse() ) .
327 Html::rawElement(
'td', [
'dir' =>
'ltr' ], $this->msg(
new RawMessage( $version ) )->parse() )
331 $out .= Html::closeElement(
'table' );
345 public static function getVersion( $flags =
'', $lang =
null ) {
346 $gitInfo = GitInfo::repo()->getHeadSHA1();
349 } elseif ( $flags ===
'nodb' ) {
350 $shortSha1 = substr( $gitInfo, 0, 7 );
353 $shortSha1 = substr( $gitInfo, 0, 7 );
355 if ( $lang !==
null ) {
356 $msg->inLanguage( $lang );
358 $shortSha1 = $msg->params( $shortSha1 )->text();
368 private static function getMWVersionLinked() {
371 if ( $hookRunner->onSpecialVersionVersionUrl(
MW_VERSION, $versionUrl ) ) {
373 preg_match(
"/^(\d+\.\d+)/",
MW_VERSION, $versionParts );
374 $versionUrl =
"https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
377 return '[' . $versionUrl .
' ' .
MW_VERSION .
']';
385 private static function getVersionLinkedGit( Language $lang ) {
387 $gitInfo =
new GitInfo( MW_INSTALL_PATH );
388 $headSHA1 = $gitInfo->getHeadSHA1();
393 $shortSHA1 =
'(' . substr( $headSHA1, 0, 7 ) .
')';
395 $gitHeadUrl = $gitInfo->getHeadViewUrl();
396 if ( $gitHeadUrl !==
false ) {
397 $shortSHA1 =
"[$gitHeadUrl $shortSHA1]";
400 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
401 if ( $gitHeadCommitDate ) {
403 $shortSHA1 .= $lang->timeanddate( (
string)$gitHeadCommitDate,
true );
406 return self::getMWVersionLinked() .
" $shortSHA1";
419 if ( self::$extensionTypes === false ) {
420 self::$extensionTypes = [
421 'specialpage' =>
wfMessage(
'version-specialpages' )->text(),
422 'editor' =>
wfMessage(
'version-editors' )->text(),
423 'parserhook' =>
wfMessage(
'version-parserhooks' )->text(),
424 'variable' =>
wfMessage(
'version-variables' )->text(),
425 'media' =>
wfMessage(
'version-mediahandlers' )->text(),
426 'antispam' =>
wfMessage(
'version-antispam' )->text(),
427 'skin' =>
wfMessage(
'version-skins' )->text(),
428 'api' =>
wfMessage(
'version-api' )->text(),
429 'other' =>
wfMessage(
'version-other' )->text(),
433 ->onExtensionTypes( self::$extensionTypes );
436 return self::$extensionTypes;
449 $types = self::getExtensionTypes();
451 return $types[$type] ?? $types[
'other'];
460 private function getExtensionCredits( array $credits ) {
461 $extensionTypes = self::getExtensionTypes();
463 $this->addTocSection(
id:
'mw-version-ext', msg:
'version-extensions' );
465 $out = Html::element(
467 [
'id' =>
'mw-version-ext' ],
468 $this->msg(
'version-extensions' )->text()
474 ( count( $credits ) === 1 && isset( $credits[
'skin'] ) )
476 $out .= Html::element(
479 $this->msg(
'version-extensions-no-ext' )->text()
486 $credits[
'other'] ??= [];
487 foreach ( $credits as $type => $extensions ) {
488 if ( !array_key_exists( $type, $extensionTypes ) ) {
489 $credits[
'other'] = array_merge( $credits[
'other'], $extensions );
494 foreach ( $extensionTypes as $type => $text ) {
496 if ( $type !==
'other' && $type !==
'skin' ) {
497 $out .= $this->getExtensionCategory( $type, $text, $credits[$type] ?? [] );
502 $out .= $this->getExtensionCategory(
'other', $extensionTypes[
'other'], $credits[
'other'] );
513 private function getSkinCredits( array $credits ) {
516 $out = Html::element(
518 [
'id' =>
'mw-version-skin' ],
519 $this->
msg(
'version-skins' )->text()
522 if ( !isset( $credits[
'skin'] ) || !$credits[
'skin'] ) {
523 $out .= Html::element(
526 $this->
msg(
'version-skins-no-skin' )->text()
531 $out .= $this->getExtensionCategory(
'skin',
null, $credits[
'skin'] );
543 $this->addTocSection(
id:
'mw-version-libraries', msg:
'version-libraries' );
545 $out = Html::element(
547 [
'id' =>
'mw-version-libraries' ],
548 $this->msg(
'version-libraries' )->text()
552 . $this->getExternalLibraries( $credits )
553 . $this->getClientSideLibraries();
564 MW_INSTALL_PATH .
'/vendor/composer/installed.json'
567 $extensionTypes = self::getExtensionTypes();
568 foreach ( $extensionTypes as $type => $message ) {
569 if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
572 foreach ( $credits[$type] as $extension ) {
573 if ( !isset( $extension[
'path'] ) ) {
576 $paths[] = dirname( $extension[
'path'] ) .
'/vendor/composer/installed.json';
582 foreach ( $paths as
$path ) {
583 if ( !file_exists(
$path ) ) {
589 $dependencies += $installed->getInstalledDependencies();
592 ksort( $dependencies );
593 return $dependencies;
603 $dependencies = self::parseComposerInstalled( $credits );
604 if ( $dependencies === [] ) {
608 $this->addTocSubSection(
id:
'mw-version-libraries-server', msg:
'version-libraries-server' );
610 $out = Html::element(
612 [
'id' =>
'mw-version-libraries-server' ],
613 $this->msg(
'version-libraries-server' )->text()
615 $out .= Html::openElement(
617 [
'class' =>
'wikitable plainlinks mw-installed-software',
'id' =>
'sv-libraries' ]
620 $out .= $this->getTableHeaderHtml( [
621 $this->msg(
'version-libraries-library' )->text(),
622 $this->msg(
'version-libraries-version' )->text(),
623 $this->msg(
'version-libraries-license' )->text(),
624 $this->msg(
'version-libraries-description' )->text(),
625 $this->msg(
'version-libraries-authors' )->text(),
628 foreach ( $dependencies as $name => $info ) {
629 if ( !is_array( $info ) || str_starts_with( $info[
'type'],
'mediawiki-' ) ) {
634 $authors = array_map(
static function ( $arr ) {
635 return new HtmlArmor( isset( $arr[
'homepage'] ) ?
636 Html::element(
'a', [
'href' => $arr[
'homepage'] ], $arr[
'name'] ) :
637 htmlspecialchars( $arr[
'name'] )
639 }, $info[
'authors'] );
640 $authors = $this->listAuthors( $authors,
false, MW_INSTALL_PATH .
"/vendor/$name" );
645 $out .= Html::openElement(
'tr', [
648 'id' => Sanitizer::escapeIdForAttribute(
649 "mw-version-library-$name"
654 $this->getLinkRenderer()->makeExternalLink(
655 "https://packagist.org/packages/$name",
657 $this->getFullTitle(),
659 [
'class' =>
'mw-version-library-name' ]
662 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
664 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'licenses'] ) )
665 . Html::element(
'td', [
'lang' =>
'en',
'dir' =>
'ltr' ], $info[
'description'] )
666 . Html::rawElement(
'td', [], $authors )
667 . Html::closeElement(
'tr' );
669 $out .= Html::closeElement(
'table' );
680 $registryDirs = [
'MediaWiki' => MW_INSTALL_PATH .
'/resources/lib' ]
681 + ExtensionRegistry::getInstance()->getAttribute(
'ForeignResourcesDir' );
684 foreach ( $registryDirs as
$source => $registryDir ) {
685 $foreignResources = Yaml::parseFile(
"$registryDir/foreign-resources.yaml" );
686 foreach ( $foreignResources as $name => $module ) {
687 $key = $name . $module[
'version'];
688 if ( isset( $modules[$key] ) ) {
689 $modules[$key][
'source'][] =
$source;
692 $modules[$key] = $module + [
'name' => $name,
'source' => [
$source ] ];
704 private function getClientSideLibraries() {
705 $this->addTocSubSection(
id:
'mw-version-libraries-client', msg:
'version-libraries-client' );
707 $out = Html::element(
709 [
'id' =>
'mw-version-libraries-client' ],
710 $this->msg(
'version-libraries-client' )->text()
712 $out .= Html::openElement(
714 [
'class' =>
'wikitable plainlinks mw-installed-software',
'id' =>
'sv-libraries-client' ]
717 $out .= $this->getTableHeaderHtml( [
718 $this->msg(
'version-libraries-library' )->text(),
719 $this->msg(
'version-libraries-version' )->text(),
720 $this->msg(
'version-libraries-license' )->text(),
721 $this->msg(
'version-libraries-authors' )->text(),
722 $this->msg(
'version-libraries-source' )->text()
725 foreach ( self::parseForeignResources() as $name => $info ) {
729 $out .= Html::openElement(
'tr', [
732 'id' => Sanitizer::escapeIdForAttribute(
733 "mw-version-library-$name"
738 $this->getLinkRenderer()->makeExternalLink(
741 $this->getFullTitle(),
743 [
'class' =>
'mw-version-library-name' ]
746 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
747 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'license'] )
748 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'authors'] ??
'—' )
750 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'source'] ) )
751 . Html::closeElement(
'tr' );
753 $out .= Html::closeElement(
'table' );
764 $tags = $this->parserFactory->getMainInstance()->getTags();
769 $this->addTocSection(
id:
'mw-version-parser-extensiontags', msg:
'version-parser-extensiontags' );
771 $out = Html::rawElement(
773 [
'id' =>
'mw-version-parser-extensiontags' ],
776 [
'class' =>
'plainlinks' ],
777 $this->getLinkRenderer()->makeExternalLink(
778 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
779 $this->msg(
'version-parser-extensiontags' ),
780 $this->getFullTitle()
785 array_walk( $tags,
static function ( &$value ) {
787 $value = Html::rawElement(
791 'style' =>
'white-space: nowrap;',
793 Html::element(
'code', [],
"<$value>" )
797 $out .= $this->listToText( $tags );
808 $funcHooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
813 $this->addTocSection(
id:
'mw-version-parser-function-hooks', msg:
'version-parser-function-hooks' );
815 $out = Html::rawElement(
817 [
'id' =>
'mw-version-parser-function-hooks' ],
820 [
'class' =>
'plainlinks' ],
821 $this->getLinkRenderer()->makeExternalLink(
822 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
823 $this->msg(
'version-parser-function-hooks' ),
824 $this->getFullTitle()
829 $funcSynonyms = $this->parserFactory->getMainInstance()->getFunctionSynonyms();
836 $preferredSynonyms = array_flip( array_reverse( $funcSynonyms[1] + $funcSynonyms[0] ) );
837 array_walk( $funcHooks,
static function ( &$value ) use ( $preferredSynonyms ) {
838 $value = $preferredSynonyms[$value];
840 $legacyHooks = array_flip( $funcHooks );
843 $cmpHooks =
static function ( $a, $b ) {
844 return strcasecmp( ltrim( $a,
'#' ), ltrim( $b,
'#' ) );
846 usort( $funcHooks, $cmpHooks );
848 $formatHooks =
static function ( &$value ) {
850 $value = Html::rawElement(
853 Html::element(
'code', [],
'{{' . $value .
'}}' )
856 array_walk( $funcHooks, $formatHooks );
858 $out .= $this->getLanguage()->listToText( $funcHooks );
860 # Get a list of parser functions from Parsoid as well.
862 $services = MediaWikiServices::getInstance();
863 $siteConfig = $services->getParsoidSiteConfig();
864 $magicWordFactory = $services->getMagicWordFactory();
865 foreach ( $siteConfig->getPFragmentHandlerKeys() as $key ) {
866 $config = $siteConfig->getPFragmentHandlerConfig( $key );
867 if ( !( $config[
'options'][
'parserFunction'] ??
false ) ) {
870 $mw = $magicWordFactory->get( $key );
871 foreach ( $mw->getSynonyms() as $local ) {
872 if ( !( $config[
'options'][
'nohash'] ??
false ) ) {
873 $local =
'#' . $local;
877 if ( isset( $legacyHooks[$local] ) ) {
880 $parsoidHooks[] = $local;
883 if ( $parsoidHooks ) {
884 $out .= Html::element(
886 [
'id' =>
'mw-version-parser-function-hooks-parsoid' ],
887 $this->msg(
'version-parser-function-hooks-parsoid' )->text()
889 usort( $parsoidHooks, $cmpHooks );
890 array_walk( $parsoidHooks, $formatHooks );
891 $out .= $this->getLanguage()->listToText( $parsoidHooks );
903 $siteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
904 $modules = $siteConfig->getExtensionModules();
910 $this->addTocSection(
id:
'mw-version-parsoid-modules', msg:
'version-parsoid-modules' );
912 $out = Html::rawElement(
914 [
'id' =>
'mw-version-parsoid-modules' ],
917 [
'class' =>
'plainlinks' ],
918 $this->getLinkRenderer()->makeExternalLink(
919 'https://www.mediawiki.org/wiki/Special:MyLanguage/Parsoid',
920 $this->msg(
'version-parsoid-modules' ),
921 $this->getFullTitle()
926 $moduleNames = array_map(
927 static fn ( $m )=>Html::element(
'code', [
928 'title' => $m->getConfig()[
'extension-name'] ??
null,
929 ], $m->getConfig()[
'name'] ),
933 $out .= $this->getLanguage()->listToText( $moduleNames );
950 if ( $creditsGroup ) {
951 $out .= $this->openExtType( $text,
'credits-' . $type );
953 usort( $creditsGroup, $this->compare( ... ) );
955 foreach ( $creditsGroup as $extension ) {
956 $out .= $this->getCreditsForExtension( $type, $extension );
959 $out .= Html::closeElement(
'table' );
971 private function compare( $a, $b ) {
972 return $this->getLanguage()->lc( $a[
'name'] ) <=> $this->getLanguage()->lc( $b[
'name'] );
994 $out = $this->getOutput();
998 if ( isset( $extension[
'namemsg'] ) ) {
1000 $extensionName = $this->msg( $extension[
'namemsg'] )->text();
1001 } elseif ( isset( $extension[
'name'] ) ) {
1003 $extensionName = $extension[
'name'];
1005 $extensionName = $this->msg(
'version-no-ext-name' )->text();
1008 if ( isset( $extension[
'url'] ) ) {
1009 $extensionNameLink = $this->getLinkRenderer()->makeExternalLink(
1012 $this->getFullTitle(),
1014 [
'class' =>
'mw-version-ext-name' ]
1017 $extensionNameLink = htmlspecialchars( $extensionName );
1023 $canonicalVersion =
'–';
1024 $extensionPath =
null;
1029 if ( isset( $extension[
'version'] ) ) {
1030 $canonicalVersion = $out->parseInlineAsInterface( $extension[
'version'] );
1033 if ( isset( $extension[
'path'] ) ) {
1034 $extensionPath = dirname( $extension[
'path'] );
1035 if ( $this->coreId ==
'' ) {
1036 wfDebug(
'Looking up core head id' );
1037 $coreHeadSHA1 = GitInfo::repo()->getHeadSHA1();
1038 if ( $coreHeadSHA1 ) {
1039 $this->coreId = $coreHeadSHA1;
1042 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance(
CACHE_ANYTHING );
1043 $memcKey = $cache->makeKey(
1044 'specialversion-ext-version-text', $extension[
'path'], $this->coreId
1046 $res = $cache->get( $memcKey );
1048 if ( $res ===
false ) {
1049 wfDebug(
"Getting VCS info for extension {$extension['name']}" );
1050 $gitInfo =
new GitInfo( $extensionPath );
1051 $vcsVersion = $gitInfo->getHeadSHA1();
1052 if ( $vcsVersion !==
false ) {
1053 $vcsVersion = substr( $vcsVersion, 0, 7 );
1054 $vcsLink = $gitInfo->getHeadViewUrl();
1055 $vcsDate = $gitInfo->getHeadCommitDate();
1057 $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
1059 wfDebug(
"Pulled VCS info for extension {$extension['name']} from cache" );
1060 [ $vcsVersion, $vcsLink, $vcsDate ] = $res;
1064 $versionString = Html::rawElement(
1066 [
'class' =>
'mw-version-ext-version' ],
1070 if ( $vcsVersion ) {
1072 $vcsVerString = $this->getLinkRenderer()->makeExternalLink(
1074 $this->msg(
'version-version', $vcsVersion ),
1075 $this->getFullTitle(),
1077 [
'class' =>
'mw-version-ext-vcs-version' ]
1080 $vcsVerString = Html::element(
'span',
1081 [
'class' =>
'mw-version-ext-vcs-version' ],
1085 $versionString .=
" {$vcsVerString}";
1088 $versionString .=
' ' . Html::element(
'span', [
1089 'class' =>
'mw-version-ext-vcs-timestamp',
1090 'dir' => $this->getLanguage()->getDir(),
1091 ], $this->getLanguage()->timeanddate( $vcsDate,
true ) );
1093 $versionString = Html::rawElement(
'span',
1094 [
'class' =>
'mw-version-ext-meta-version' ],
1102 if ( isset( $extension[
'name'] ) ) {
1103 $licenseName =
null;
1104 if ( isset( $extension[
'license-name'] ) ) {
1105 $licenseName =
new HtmlArmor( $out->parseInlineAsInterface( $extension[
'license-name'] ) );
1106 } elseif ( $extensionPath !==
null && ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
1107 $licenseName = $this->msg(
'version-ext-license' )->text();
1109 if ( $licenseName !==
null ) {
1110 $licenseLink = $this->getLinkRenderer()->makeLink(
1111 $this->getPageTitle(
'License/' . $extension[
'name'] ),
1114 'class' =>
'mw-version-ext-license',
1124 if ( isset( $extension[
'descriptionmsg'] ) ) {
1126 $descriptionMsg = $extension[
'descriptionmsg'];
1128 if ( is_array( $descriptionMsg ) ) {
1129 $descriptionMsgKey = array_shift( $descriptionMsg );
1130 $descriptionMsg = array_map(
'htmlspecialchars', $descriptionMsg );
1131 $description = $this->msg( $descriptionMsgKey, ...$descriptionMsg )->text();
1133 $description = $this->msg( $descriptionMsg )->text();
1135 } elseif ( isset( $extension[
'description'] ) ) {
1137 $description = $extension[
'description'];
1141 $description = $out->parseInlineAsInterface( $description );
1144 $authors = $extension[
'author'] ?? [];
1146 $authors = $this->listAuthors( $authors, $extension[
'name'], $extensionPath );
1149 $html = Html::openElement(
'tr', [
1150 'class' =>
'mw-version-ext',
1151 'id' => Sanitizer::escapeIdForAttribute(
'mw-version-ext-' . $type .
'-' . $extension[
'name'] )
1155 $html .= Html::rawElement(
'td', [], $extensionNameLink );
1156 $html .= Html::rawElement(
'td', [], $versionString );
1157 $html .= Html::rawElement(
'td', [], $licenseLink );
1158 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-description' ], $description );
1159 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-authors' ], $authors );
1161 $html .= Html::closeElement(
'tr' );
1171 private function getHooks() {
1172 if ( !$this->getConfig()->
get( MainConfigNames::SpecialVersionShowHooks ) ) {
1176 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1177 $hookNames = $hookContainer->getHookNames();
1179 if ( !$hookNames ) {
1186 $this->addTocSection(
id:
'mw-version-hooks', msg:
'version-hooks' );
1187 $ret[] = Html::element(
1189 [
'id' =>
'mw-version-hooks' ],
1190 $this->msg(
'version-hooks' )->text()
1192 $ret[] = Html::openElement(
'table', [
'class' =>
'wikitable',
'id' =>
'sv-hooks' ] );
1193 $ret[] = Html::openElement(
'tr' );
1194 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-name' )->text() );
1195 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-subscribedby' )->text() );
1196 $ret[] = Html::closeElement(
'tr' );
1198 foreach ( $hookNames as $name ) {
1199 $handlers = $hookContainer->getHandlerDescriptions( $name );
1201 $ret[] = Html::openElement(
'tr' );
1202 $ret[] = Html::element(
'td', [], $name );
1204 $ret[] = Html::element(
'td', [], $this->listToText( $handlers ) );
1205 $ret[] = Html::closeElement(
'tr' );
1208 $ret[] = Html::closeElement(
'table' );
1210 return implode(
"\n", $ret );
1213 private function openExtType( ?
string $text =
null, ?
string $name =
null ): string {
1216 $opt = [
'class' =>
'wikitable plainlinks mw-installed-software' ];
1219 $opt[
'id'] =
"sv-$name";
1222 $out .= Html::openElement(
'table', $opt );
1224 if ( $text !==
null ) {
1225 $out .= Html::element(
'caption', [], $text );
1228 if ( $name && $text !==
null ) {
1232 $firstHeadingMsg = ( $name ===
'credits-skin' )
1233 ?
'version-skin-colheader-name'
1234 :
'version-ext-colheader-name';
1236 $out .= $this->getTableHeaderHtml( [
1237 $this->
msg( $firstHeadingMsg )->text(),
1238 $this->
msg(
'version-ext-colheader-version' )->text(),
1239 $this->
msg(
'version-ext-colheader-license' )->text(),
1240 $this->
msg(
'version-ext-colheader-description' )->text(),
1241 $this->
msg(
'version-ext-colheader-credits' )->text()
1255 private function getTableHeaderHtml( $headers ): string {
1257 foreach ( $headers as $header ) {
1258 $out .= Html::element(
'th', [
'scope' =>
'col' ], $header );
1260 return Html::rawElement(
'thead', [],
1261 Html::rawElement(
'tr', [], $out )
1270 private function IPInfo() {
1271 $ip = str_replace(
'--',
' - ', htmlspecialchars( $this->
getRequest()->getIP() ) );
1273 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1298 $linkRenderer = $this->getLinkRenderer();
1301 $authors = (array)$authors;
1306 if ( count( $authors ) === 1 && $authors[0] ===
'...' ) {
1309 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1310 return $linkRenderer->makeLink(
1311 $this->getPageTitle(
"Credits/$extName" ),
1312 $this->msg(
'version-poweredby-various' )->text()
1315 return $this->msg(
'version-poweredby-various' )->escaped();
1321 foreach ( $authors as $item ) {
1322 if ( $item instanceof HtmlArmor ) {
1323 $list[] = HtmlArmor::getHtml( $item );
1324 } elseif ( $item ===
'...' ) {
1327 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1328 $text = $linkRenderer->makeLink(
1329 $this->getPageTitle(
"Credits/$extName" ),
1330 $this->
msg(
'version-poweredby-others' )->text()
1333 $text = $this->
msg(
'version-poweredby-others' )->escaped();
1336 } elseif ( str_ends_with( $item,
' ...]' ) ) {
1338 $list[] = $this->
getOutput()->parseInlineAsInterface(
1339 substr( $item, 0, -4 ) . $this->
msg(
'version-poweredby-others' )->text() .
"]"
1342 $list[] = $this->
getOutput()->parseInlineAsInterface( $item );
1346 if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1347 $list[] = $linkRenderer->makeLink(
1348 $this->getPageTitle(
"Credits/$extName" ),
1349 $this->
msg(
'version-poweredby-others' )->text()
1353 return $this->listToText( $list,
false );
1365 private function listToText( array $list,
bool $sort =
true ): string {
1374 ->listToText( array_map( self::arrayToString( ... ), $list ) );
1387 if ( is_array( $list ) && count( $list ) == 1 ) {
1390 if ( $list instanceof Closure ) {
1393 } elseif ( is_object( $list ) ) {
1394 return wfMessage(
'parentheses' )->params( get_class( $list ) )->escaped();
1395 } elseif ( !is_array( $list ) ) {
1398 if ( is_object( $list[0] ) ) {
1399 $class = get_class( $list[0] );
1404 return wfMessage(
'parentheses' )->params(
"$class, {$list[1]}" )->escaped();
1415 return (
new GitInfo( $dir ) )->getHeadSHA1();
1423 $config = $this->getConfig();
1424 $scriptPath = $config->get( MainConfigNames::ScriptPath ) ?:
'/';
1427 'version-entrypoints-articlepath' => $config->get( MainConfigNames::ArticlePath ),
1428 'version-entrypoints-scriptpath' => $scriptPath,
1429 'version-entrypoints-index-php' =>
wfScript(
'index' ),
1430 'version-entrypoints-api-php' =>
wfScript(
'api' ),
1431 'version-entrypoints-rest-php' =>
wfScript(
'rest' ),
1434 $language = $this->getLanguage();
1436 'dir' => $language->getDir(),
1437 'lang' => $language->getHtmlCode(),
1441 $this->addTocSection(
id:
'mw-version-entrypoints', msg:
'version-entrypoints' );
1443 $out = Html::element(
1445 [
'id' =>
'mw-version-entrypoints' ],
1446 $this->msg(
'version-entrypoints' )->text()
1448 Html::openElement(
'table',
1450 'class' =>
'wikitable plainlinks',
1451 'id' =>
'mw-version-entrypoints-table',
1456 Html::openElement(
'thead' ) .
1457 Html::openElement(
'tr' ) .
1461 $this->msg(
'version-entrypoints-header-entrypoint' )->text()
1466 $this->msg(
'version-entrypoints-header-url' )->text()
1468 Html::closeElement(
'tr' ) .
1469 Html::closeElement(
'thead' );
1471 foreach ( $entryPoints as $message => $value ) {
1473 $out .= Html::openElement(
'tr' ) .
1474 Html::rawElement(
'td', [], $this->msg( $message )->parse() ) .
1475 Html::rawElement(
'td', [],
1479 $this->msg(
new RawMessage(
"[$url $value]" ) )->parse()
1482 Html::closeElement(
'tr' );
1485 $out .= Html::closeElement(
'table' );
1500class_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(!defined('MW_SETUP_CALLBACK'))
A class containing constants representing the names of configuration variables.
Parent class for all special pages.