47use Symfony\Component\Yaml\Yaml;
49use Wikimedia\Parsoid\Core\SectionMetadata;
50use Wikimedia\Parsoid\Core\TOCData;
96 parent::__construct(
'Version' );
97 $this->parserFactory = $parserFactory;
98 $this->urlUtils = $urlUtils;
99 $this->dbProvider = $dbProvider;
112 $credits[$credit[
'type']][] = $credit;
121 $config = $this->getConfig();
122 $credits = self::getCredits( ExtensionRegistry::getInstance(), $config );
125 $this->outputHeader();
126 $out = $this->getOutput();
127 $out->setPreventClickjacking(
false );
130 $parts = explode(
'/', (
string)$par );
132 if ( isset( $parts[1] ) ) {
133 $extName = str_replace(
'_',
' ', $parts[1] );
135 foreach ( $credits as $extensions ) {
136 foreach ( $extensions as $ext ) {
137 if ( isset( $ext[
'name'] ) && ( $ext[
'name'] === $extName ) ) {
144 $out->setStatusCode( 404 );
147 $extName =
'MediaWiki';
151 switch ( strtolower( $parts[0] ) ) {
153 $out->addModuleStyles(
'mediawiki.special' );
155 $wikiText =
'{{int:version-credits-not-found}}';
156 if ( $extName ===
'MediaWiki' ) {
157 $wikiText = file_get_contents( MW_INSTALL_PATH .
'/CREDITS' );
159 $wikiText = str_replace(
160 [
'<!-- BEGIN CONTRIBUTOR LIST -->',
'<!-- END CONTRIBUTOR LIST -->' ],
161 [
'<div class="mw-version-credits">',
'</div>' ],
164 } elseif ( ( $extNode !==
null ) && isset( $extNode[
'path'] ) ) {
165 $file = ExtensionInfo::getAuthorsFileName( dirname( $extNode[
'path'] ) );
167 $wikiText = file_get_contents( $file );
168 if ( str_ends_with( $file,
'.txt' ) ) {
181 $out->setPageTitleMsg( $this->msg(
'version-credits-title' )->plaintextParams( $extName ) );
182 $out->addWikiTextAsInterface( $wikiText );
186 $out->setPageTitleMsg( $this->msg(
'version-license-title' )->plaintextParams( $extName ) );
188 $licenseFound =
false;
190 if ( $extName ===
'MediaWiki' ) {
191 $out->addWikiTextAsInterface(
192 file_get_contents( MW_INSTALL_PATH .
'/COPYING' )
194 $licenseFound =
true;
195 } elseif ( ( $extNode !==
null ) && isset( $extNode[
'path'] ) ) {
196 $files = ExtensionInfo::getLicenseFileNames( dirname( $extNode[
'path'] ) );
198 $licenseFound =
true;
199 foreach ( $files as $file ) {
200 $out->addWikiTextAsInterface(
207 file_get_contents( $file )
213 if ( !$licenseFound ) {
214 $out->addWikiTextAsInterface(
'{{int:version-license-not-found}}' );
219 $out->addModuleStyles(
'mediawiki.special' );
221 $out->addHTML( $this->getMediaWikiCredits() );
223 $this->tocData =
new TOCData();
225 $this->tocSection = 0;
226 $this->tocSubSection = 0;
230 $this->softwareInformation(),
231 $this->getEntryPointInfo(),
232 $this->getSkinCredits( $credits ),
233 $this->getExtensionCredits( $credits ),
234 $this->getLibraries( $credits ),
235 $this->getParserTags(),
236 $this->getParserFunctionHooks(),
244 $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
245 $pout->setRawText( Parser::TOC_PLACEHOLDER );
246 $out->addParserOutput( $pout );
249 foreach ( $sections as $content ) {
250 $out->addHTML( $content );
264 private function addTocSection( $labelMsg, $id ) {
267 $this->tocSubSection = 0;
268 $this->tocData->addSection(
new SectionMetadata(
271 $this->msg( $labelMsg )->escaped(),
272 $this->getLanguage()->formatNum( $this->tocSection ),
273 (
string)$this->tocIndex,
288 private function addTocSubSection( $label, $id ) {
290 $this->tocSubSection++;
291 $this->tocData->addSection(
new SectionMetadata(
294 htmlspecialchars( $label ),
296 $this->getLanguage()->formatNum( $this->tocSection ) .
'.' .
297 $this->getLanguage()->formatNum( $this->tocSubSection ),
298 (
string)$this->tocIndex,
311 private function getMediaWikiCredits() {
316 [
'id' =>
'mw-version-license' ],
317 $this->msg(
'version-license' )->text()
320 $ret .= Html::rawElement(
'div', [
'class' =>
'plainlinks' ],
321 $this->msg(
new RawMessage( self::getCopyrightAndAuthorList() ) )->parseAsBlock() .
322 Html::rawElement(
'div', [
'class' =>
'mw-version-license-info' ],
323 $this->msg(
'version-license-info' )->parseAsBlock()
337 if ( defined(
'MEDIAWIKI_INSTALL' ) ) {
338 $othersLink =
'[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
339 wfMessage(
'version-poweredby-others' )->plain() .
']';
341 $othersLink =
'[[Special:Version/Credits|' .
342 wfMessage(
'version-poweredby-others' )->plain() .
']]';
345 $translatorsLink =
'[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
346 wfMessage(
'version-poweredby-translators' )->plain() .
']';
349 'Magnus Manske',
'Brooke Vibber',
'Lee Daniel Crocker',
350 'Tim Starling',
'Erik Möller',
'Gabriel Wicke',
'Ævar Arnfjörð Bjarmason',
351 'Niklas Laxström',
'Domas Mituzas',
'Rob Church',
'Yuri Astrakhan',
352 'Aryeh Gregor',
'Aaron Schulz',
'Andrew Garrett',
'Raimond Spekking',
353 'Alexandre Emsenhuber',
'Siebrand Mazeland',
'Chad Horohoe',
354 'Roan Kattouw',
'Trevor Parscal',
'Bryan Tong Minh',
'Sam Reed',
355 'Victor Vasiliev',
'Rotem Liss',
'Platonides',
'Antoine Musso',
356 'Timo Tijhof',
'Daniel Kinzler',
'Jeroen De Dauw',
'Brad Jorsch',
357 'Bartosz Dziewoński',
'Ed Sanders',
'Moriel Schottlender',
358 'Kunal Mehta',
'James D. Forrester',
'Brian Wolff',
'Adam Shorland',
359 'DannyS712',
'Ori Livneh',
'Max Semenik',
'Amir Sarabadani',
360 'Derk-Jan Hartman',
'Petr Pchelko',
361 $othersLink, $translatorsLink
364 return wfMessage(
'version-poweredby-credits', MWTimestamp::getLocalInstance()->format(
'Y' ),
373 private function getSoftwareInformation() {
374 $dbr = $this->dbProvider->getReplicaDatabase();
380 '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked(),
381 '[https://php.net/ PHP]' => PHP_VERSION .
" (" . PHP_SAPI .
")",
382 '[https://icu.unicode.org/ ICU]' => INTL_ICU_VERSION,
383 $dbr->getSoftwareLink() => $dbr->getServerInfo(),
387 if ( phpversion(
"wikidiff2" ) ) {
388 $software[
'[https://www.mediawiki.org/wiki/Wikidiff2 wikidiff2]' ] = phpversion(
"wikidiff2" );
392 $this->getHookRunner()->onSoftwareInfo( $software );
402 private function softwareInformation() {
403 $this->addTocSection(
'version-software',
'mw-version-software' );
407 [
'id' =>
'mw-version-software' ],
408 $this->msg(
'version-software' )->text()
411 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-software' ] );
413 $out .= $this->getTableHeaderHtml( [
414 $this->msg(
'version-software-product' )->text(),
415 $this->msg(
'version-software-version' )->text()
418 foreach ( $this->getSoftwareInformation() as $name => $version ) {
419 $out .= Html::rawElement(
422 Html::rawElement(
'td', [], $this->msg(
new RawMessage( $name ) )->parse() ) .
423 Html::rawElement(
'td', [
'dir' =>
'ltr' ], $this->msg(
new RawMessage( $version ) )->parse() )
427 $out .= Html::closeElement(
'table' );
441 public static function getVersion( $flags =
'', $lang =
null ) {
442 $gitInfo = GitInfo::repo()->getHeadSHA1();
445 } elseif ( $flags ===
'nodb' ) {
446 $shortSha1 = substr( $gitInfo, 0, 7 );
449 $shortSha1 = substr( $gitInfo, 0, 7 );
451 if ( $lang !==
null ) {
452 $msg->inLanguage( $lang );
454 $shortSha1 = $msg->params( $shortSha1 )->text();
469 return self::getVersionLinkedGit() ?:
MW_VERSION;
475 private static function getMWVersionLinked() {
478 if ( $hookRunner->onSpecialVersionVersionUrl(
MW_VERSION, $versionUrl ) ) {
480 preg_match(
"/^(\d+\.\d+)/",
MW_VERSION, $versionParts );
481 $versionUrl =
"https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
484 return '[' . $versionUrl .
' ' .
MW_VERSION .
']';
492 private static function getVersionLinkedGit() {
495 $gitInfo =
new GitInfo( MW_INSTALL_PATH );
496 $headSHA1 = $gitInfo->getHeadSHA1();
501 $shortSHA1 =
'(' . substr( $headSHA1, 0, 7 ) .
')';
503 $gitHeadUrl = $gitInfo->getHeadViewUrl();
504 if ( $gitHeadUrl !==
false ) {
505 $shortSHA1 =
"[$gitHeadUrl $shortSHA1]";
508 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
509 if ( $gitHeadCommitDate ) {
513 return self::getMWVersionLinked() .
" $shortSHA1";
526 if ( self::$extensionTypes === false ) {
527 self::$extensionTypes = [
528 'specialpage' =>
wfMessage(
'version-specialpages' )->text(),
529 'editor' =>
wfMessage(
'version-editors' )->text(),
530 'parserhook' =>
wfMessage(
'version-parserhooks' )->text(),
531 'variable' =>
wfMessage(
'version-variables' )->text(),
532 'media' =>
wfMessage(
'version-mediahandlers' )->text(),
533 'antispam' =>
wfMessage(
'version-antispam' )->text(),
534 'skin' =>
wfMessage(
'version-skins' )->text(),
535 'api' =>
wfMessage(
'version-api' )->text(),
536 'other' =>
wfMessage(
'version-other' )->text(),
540 ->onExtensionTypes( self::$extensionTypes );
543 return self::$extensionTypes;
556 $types = self::getExtensionTypes();
558 return $types[$type] ?? $types[
'other'];
567 private function getExtensionCredits( array $credits ) {
568 $extensionTypes = self::getExtensionTypes();
570 $this->addTocSection(
'version-extensions',
'mw-version-ext' );
572 $out = Html::element(
574 [
'id' =>
'mw-version-ext' ],
575 $this->msg(
'version-extensions' )->text()
581 ( count( $credits ) === 1 && isset( $credits[
'skin'] ) )
583 $out .= Html::element(
586 $this->msg(
'version-extensions-no-ext' )->text()
593 $credits[
'other'] ??= [];
594 foreach ( $credits as $type => $extensions ) {
595 if ( !array_key_exists( $type, $extensionTypes ) ) {
596 $credits[
'other'] = array_merge( $credits[
'other'], $extensions );
601 foreach ( $extensionTypes as $type => $text ) {
603 if ( $type !==
'other' && $type !==
'skin' ) {
604 $out .= $this->getExtensionCategory( $type, $text, $credits[$type] ?? [] );
609 $out .= $this->getExtensionCategory(
'other', $extensionTypes[
'other'], $credits[
'other'] );
620 private function getSkinCredits( array $credits ) {
621 $this->addTocSection(
'version-skins',
'mw-version-skin' );
623 $out = Html::element(
625 [
'id' =>
'mw-version-skin' ],
626 $this->msg(
'version-skins' )->text()
629 if ( !isset( $credits[
'skin'] ) || !$credits[
'skin'] ) {
630 $out .= Html::element(
633 $this->msg(
'version-skins-no-skin' )->text()
638 $out .= $this->getExtensionCategory(
'skin',
null, $credits[
'skin'] );
650 $this->addTocSection(
'version-libraries',
'mw-version-libraries' );
652 $out = Html::element(
654 [
'id' =>
'mw-version-libraries' ],
655 $this->msg(
'version-libraries' )->text()
659 . $this->getExternalLibraries( $credits )
660 . $this->getClientSideLibraries();
671 MW_INSTALL_PATH .
'/vendor/composer/installed.json'
674 $extensionTypes = self::getExtensionTypes();
675 foreach ( $extensionTypes as $type => $message ) {
676 if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
679 foreach ( $credits[$type] as $extension ) {
680 if ( !isset( $extension[
'path'] ) ) {
683 $paths[] = dirname( $extension[
'path'] ) .
'/vendor/composer/installed.json';
689 foreach ( $paths as
$path ) {
690 if ( !file_exists(
$path ) ) {
696 $dependencies += $installed->getInstalledDependencies();
699 if ( $dependencies === [] ) {
703 ksort( $dependencies );
705 $this->addTocSubSection( $this->msg(
'version-libraries-server' )->text(),
'mw-version-libraries-server' );
707 $out = Html::element(
709 [
'id' =>
'mw-version-libraries-server' ],
710 $this->msg(
'version-libraries-server' )->text()
712 $out .= Html::openElement(
714 [
'class' =>
'wikitable plainlinks mw-installed-software',
'id' =>
'sv-libraries' ]
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-description' )->text(),
722 $this->msg(
'version-libraries-authors' )->text(),
725 foreach ( $dependencies as $name => $info ) {
726 if ( !is_array( $info ) || str_starts_with( $info[
'type'],
'mediawiki-' ) ) {
731 $authors = array_map(
static function ( $arr ) {
732 return new HtmlArmor( isset( $arr[
'homepage'] ) ?
733 Html::element(
'a', [
'href' => $arr[
'homepage'] ], $arr[
'name'] ) :
734 htmlspecialchars( $arr[
'name'] )
736 }, $info[
'authors'] );
737 $authors = $this->listAuthors( $authors,
false, MW_INSTALL_PATH .
"/vendor/$name" );
742 $out .= Html::openElement(
'tr', [
745 'id' => Sanitizer::escapeIdForAttribute(
746 "mw-version-library-$name"
751 Linker::makeExternalLink(
752 "https://packagist.org/packages/$name", $name,
754 [
'class' =>
'mw-version-library-name' ]
757 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
759 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'licenses'] ) )
760 . Html::element(
'td', [
'lang' =>
'en',
'dir' =>
'ltr' ], $info[
'description'] )
761 . Html::rawElement(
'td', [], $authors )
762 . Html::closeElement(
'tr' );
764 $out .= Html::closeElement(
'table' );
775 $registryDirs = [
'MediaWiki' => MW_INSTALL_PATH .
'/resources/lib' ]
776 + ExtensionRegistry::getInstance()->getAttribute(
'ForeignResourcesDir' );
779 foreach ( $registryDirs as
$source => $registryDir ) {
780 $foreignResources = Yaml::parseFile(
"$registryDir/foreign-resources.yaml" );
781 foreach ( $foreignResources as $name => $module ) {
782 $key = $name . $module[
'version'];
783 if ( isset( $modules[$key] ) ) {
784 $modules[$key][
'source'][] =
$source;
787 $modules[$key] = $module + [
'name' => $name,
'source' => [
$source ] ];
799 private function getClientSideLibraries() {
800 $this->addTocSubSection( $this->msg(
'version-libraries-client' )->text(),
'mw-version-libraries-client' );
802 $out = Html::element(
804 [
'id' =>
'mw-version-libraries-client' ],
805 $this->msg(
'version-libraries-client' )->text()
807 $out .= Html::openElement(
809 [
'class' =>
'wikitable plainlinks mw-installed-software',
'id' =>
'sv-libraries-client' ]
812 $out .= $this->getTableHeaderHtml( [
813 $this->msg(
'version-libraries-library' )->text(),
814 $this->msg(
'version-libraries-version' )->text(),
815 $this->msg(
'version-libraries-license' )->text(),
816 $this->msg(
'version-libraries-authors' )->text(),
817 $this->msg(
'version-libraries-source' )->text()
820 foreach ( self::parseForeignResources() as $name => $info ) {
824 $out .= Html::openElement(
'tr', [
827 'id' => Sanitizer::escapeIdForAttribute(
828 "mw-version-library-$name"
833 Linker::makeExternalLink(
834 $info[
'homepage'], $info[
'name'],
836 [
'class' =>
'mw-version-library-name' ]
839 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
840 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'license'] )
841 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'authors'] ??
'—' )
843 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'source'] ) )
844 . Html::closeElement(
'tr' );
846 $out .= Html::closeElement(
'table' );
857 $tags = $this->parserFactory->getMainInstance()->getTags();
862 $this->addTocSection(
'version-parser-extensiontags',
'mw-version-parser-extensiontags' );
864 $out = Html::rawElement(
866 [
'id' =>
'mw-version-parser-extensiontags' ],
869 [
'class' =>
'plainlinks' ],
870 Linker::makeExternalLink(
871 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
872 $this->msg(
'version-parser-extensiontags' )->text()
877 array_walk( $tags,
static function ( &$value ) {
879 $value = Html::rawElement(
883 'style' =>
'white-space: nowrap;',
885 Html::element(
'code', [],
"<$value>" )
889 $out .= $this->listToText( $tags );
900 $funcHooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
905 $this->addTocSection(
'version-parser-function-hooks',
'mw-version-parser-function-hooks' );
907 $out = Html::rawElement(
909 [
'id' =>
'mw-version-parser-function-hooks' ],
912 [
'class' =>
'plainlinks' ],
913 Linker::makeExternalLink(
914 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
915 $this->msg(
'version-parser-function-hooks' )->text()
920 $funcSynonyms = $this->parserFactory->getMainInstance()->getFunctionSynonyms();
927 $preferredSynonyms = array_flip( array_reverse( $funcSynonyms[1] + $funcSynonyms[0] ) );
928 array_walk( $funcHooks,
static function ( &$value ) use ( $preferredSynonyms ) {
929 $value = $preferredSynonyms[$value];
933 usort( $funcHooks,
static function ( $a, $b ) {
934 return strcasecmp( ltrim( $a,
'#' ), ltrim( $b,
'#' ) );
937 array_walk( $funcHooks,
static function ( &$value ) {
939 $value = Html::rawElement(
942 Html::element(
'code', [],
'{{' . $value .
'}}' )
946 $out .= $this->getLanguage()->listToText( $funcHooks );
963 if ( $creditsGroup ) {
964 $out .= $this->openExtType( $text,
'credits-' . $type );
966 usort( $creditsGroup, [ $this,
'compare' ] );
968 foreach ( $creditsGroup as $extension ) {
969 $out .= $this->getCreditsForExtension( $type, $extension );
972 $out .= Html::closeElement(
'table' );
985 return $this->getLanguage()->lc( $a[
'name'] ) <=> $this->getLanguage()->lc( $b[
'name'] );
1007 $out = $this->getOutput();
1011 if ( isset( $extension[
'namemsg'] ) ) {
1013 $extensionName = $this->msg( $extension[
'namemsg'] )->text();
1014 } elseif ( isset( $extension[
'name'] ) ) {
1016 $extensionName = $extension[
'name'];
1018 $extensionName = $this->msg(
'version-no-ext-name' )->text();
1021 if ( isset( $extension[
'url'] ) ) {
1022 $extensionNameLink = Linker::makeExternalLink(
1027 [
'class' =>
'mw-version-ext-name' ]
1030 $extensionNameLink = htmlspecialchars( $extensionName );
1036 $canonicalVersion =
'–';
1037 $extensionPath =
null;
1042 if ( isset( $extension[
'version'] ) ) {
1043 $canonicalVersion = $out->parseInlineAsInterface( $extension[
'version'] );
1046 if ( isset( $extension[
'path'] ) ) {
1047 $extensionPath = dirname( $extension[
'path'] );
1048 if ( $this->coreId ==
'' ) {
1049 wfDebug(
'Looking up core head id' );
1050 $coreHeadSHA1 = GitInfo::repo()->getHeadSHA1();
1051 if ( $coreHeadSHA1 ) {
1052 $this->coreId = $coreHeadSHA1;
1055 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance(
CACHE_ANYTHING );
1056 $memcKey = $cache->makeKey(
1057 'specialversion-ext-version-text', $extension[
'path'], $this->coreId
1059 [ $vcsVersion, $vcsLink, $vcsDate ] = $cache->get( $memcKey );
1061 if ( !$vcsVersion ) {
1062 wfDebug(
"Getting VCS info for extension {$extension['name']}" );
1063 $gitInfo =
new GitInfo( $extensionPath );
1064 $vcsVersion = $gitInfo->getHeadSHA1();
1065 if ( $vcsVersion !==
false ) {
1066 $vcsVersion = substr( $vcsVersion, 0, 7 );
1067 $vcsLink = $gitInfo->getHeadViewUrl();
1068 $vcsDate = $gitInfo->getHeadCommitDate();
1070 $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
1072 wfDebug(
"Pulled VCS info for extension {$extension['name']} from cache" );
1076 $versionString = Html::rawElement(
1078 [
'class' =>
'mw-version-ext-version' ],
1082 if ( $vcsVersion ) {
1084 $vcsVerString = Linker::makeExternalLink(
1086 $this->msg(
'version-version', $vcsVersion )->text(),
1089 [
'class' =>
'mw-version-ext-vcs-version' ]
1092 $vcsVerString = Html::element(
'span',
1093 [
'class' =>
'mw-version-ext-vcs-version' ],
1097 $versionString .=
" {$vcsVerString}";
1100 $versionString .=
' ' . Html::element(
'span', [
1101 'class' =>
'mw-version-ext-vcs-timestamp',
1102 'dir' => $this->getLanguage()->getDir(),
1103 ], $this->getLanguage()->timeanddate( $vcsDate,
true ) );
1105 $versionString = Html::rawElement(
'span',
1106 [
'class' =>
'mw-version-ext-meta-version' ],
1114 if ( isset( $extension[
'name'] ) ) {
1115 $licenseName =
null;
1116 if ( isset( $extension[
'license-name'] ) ) {
1117 $licenseName =
new HtmlArmor( $out->parseInlineAsInterface( $extension[
'license-name'] ) );
1118 } elseif ( $extensionPath !==
null && ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
1119 $licenseName = $this->msg(
'version-ext-license' )->text();
1121 if ( $licenseName !==
null ) {
1122 $licenseLink = $this->getLinkRenderer()->makeLink(
1123 $this->getPageTitle(
'License/' . $extension[
'name'] ),
1126 'class' =>
'mw-version-ext-license',
1136 if ( isset( $extension[
'descriptionmsg'] ) ) {
1138 $descriptionMsg = $extension[
'descriptionmsg'];
1140 if ( is_array( $descriptionMsg ) ) {
1141 $descriptionMsgKey = array_shift( $descriptionMsg );
1142 $descriptionMsg = array_map(
'htmlspecialchars', $descriptionMsg );
1143 $description = $this->msg( $descriptionMsgKey, ...$descriptionMsg )->text();
1145 $description = $this->msg( $descriptionMsg )->text();
1147 } elseif ( isset( $extension[
'description'] ) ) {
1149 $description = $extension[
'description'];
1153 $description = $out->parseInlineAsInterface( $description );
1156 $authors = $extension[
'author'] ?? [];
1158 $authors = $this->listAuthors( $authors, $extension[
'name'], $extensionPath );
1161 $html = Html::openElement(
'tr', [
1162 'class' =>
'mw-version-ext',
1163 'id' => Sanitizer::escapeIdForAttribute(
'mw-version-ext-' . $type .
'-' . $extension[
'name'] )
1167 $html .= Html::rawElement(
'td', [], $extensionNameLink );
1168 $html .= Html::rawElement(
'td', [], $versionString );
1169 $html .= Html::rawElement(
'td', [], $licenseLink );
1170 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-description' ], $description );
1171 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-authors' ], $authors );
1173 $html .= Html::closeElement(
'tr' );
1183 private function getHooks() {
1184 if ( !$this->getConfig()->
get( MainConfigNames::SpecialVersionShowHooks ) ) {
1188 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1189 $hookNames = $hookContainer->getHookNames();
1191 if ( !$hookNames ) {
1198 $this->addTocSection(
'version-hooks',
'mw-version-hooks' );
1199 $ret[] = Html::element(
1201 [
'id' =>
'mw-version-hooks' ],
1202 $this->msg(
'version-hooks' )->text()
1204 $ret[] = Html::openElement(
'table', [
'class' =>
'wikitable',
'id' =>
'sv-hooks' ] );
1205 $ret[] = Html::openElement(
'tr' );
1206 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-name' )->text() );
1207 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-subscribedby' )->text() );
1208 $ret[] = Html::closeElement(
'tr' );
1210 foreach ( $hookNames as $name ) {
1211 $handlers = $hookContainer->getHandlerDescriptions( $name );
1213 $ret[] = Html::openElement(
'tr' );
1214 $ret[] = Html::element(
'td', [], $name );
1216 $ret[] = Html::element(
'td', [], $this->listToText( $handlers ) );
1217 $ret[] = Html::closeElement(
'tr' );
1220 $ret[] = Html::closeElement(
'table' );
1222 return implode(
"\n", $ret );
1225 private function openExtType(
string $text =
null,
string $name =
null ) {
1228 $opt = [
'class' =>
'wikitable plainlinks mw-installed-software' ];
1231 $opt[
'id'] =
"sv-$name";
1234 $out .= Html::openElement(
'table', $opt );
1236 if ( $text !==
null ) {
1237 $out .= Html::element(
'caption', [], $text );
1240 if ( $name && $text !==
null ) {
1241 $this->addTocSubSection( $text,
"sv-$name" );
1244 $firstHeadingMsg = ( $name ===
'credits-skin' )
1245 ?
'version-skin-colheader-name'
1246 :
'version-ext-colheader-name';
1248 $out .= $this->getTableHeaderHtml( [
1249 $this->msg( $firstHeadingMsg )->text(),
1250 $this->msg(
'version-ext-colheader-version' )->text(),
1251 $this->msg(
'version-ext-colheader-license' )->text(),
1252 $this->msg(
'version-ext-colheader-description' )->text(),
1253 $this->msg(
'version-ext-colheader-credits' )->text()
1267 private function getTableHeaderHtml( $headers ): string {
1269 $out .= Html::openElement(
'thead' );
1270 $out .= Html::openElement(
'tr' );
1271 foreach ( $headers as
$header ) {
1272 $out .= Html::element(
'th', [
'scope' =>
'col' ],
$header );
1274 $out .= Html::closeElement(
'tr' );
1275 $out .= Html::closeElement(
'thead' );
1284 private function IPInfo() {
1285 $ip = str_replace(
'--',
' - ', htmlspecialchars( $this->
getRequest()->getIP() ) );
1287 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1312 $linkRenderer = $this->getLinkRenderer();
1315 $authors = (array)$authors;
1320 if ( count( $authors ) === 1 && $authors[0] ===
'...' ) {
1323 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1324 return $linkRenderer->makeLink(
1325 $this->getPageTitle(
"Credits/$extName" ),
1326 $this->msg(
'version-poweredby-various' )->text()
1329 return $this->msg(
'version-poweredby-various' )->escaped();
1335 foreach ( $authors as $item ) {
1337 $list[] = HtmlArmor::getHtml( $item );
1338 } elseif ( $item ===
'...' ) {
1341 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1342 $text = $linkRenderer->makeLink(
1343 $this->getPageTitle(
"Credits/$extName" ),
1344 $this->msg(
'version-poweredby-others' )->text()
1347 $text = $this->msg(
'version-poweredby-others' )->escaped();
1350 } elseif ( str_ends_with( $item,
' ...]' ) ) {
1352 $list[] = $this->getOutput()->parseInlineAsInterface(
1353 substr( $item, 0, -4 ) . $this->msg(
'version-poweredby-others' )->text() .
"]"
1356 $list[] = $this->getOutput()->parseInlineAsInterface( $item );
1360 if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1361 $list[] = $linkRenderer->makeLink(
1362 $this->getPageTitle(
"Credits/$extName" ),
1363 $this->msg(
'version-poweredby-others' )->text()
1367 return $this->listToText( $list,
false );
1379 private function listToText( array $list,
bool $sort =
true ): string {
1387 return $this->getLanguage()
1388 ->listToText( array_map( [ __CLASS__,
'arrayToString' ], $list ) );
1401 if ( is_array( $list ) && count( $list ) == 1 ) {
1404 if ( $list instanceof Closure ) {
1407 } elseif ( is_object( $list ) ) {
1408 return wfMessage(
'parentheses' )->params( get_class( $list ) )->escaped();
1409 } elseif ( !is_array( $list ) ) {
1412 if ( is_object( $list[0] ) ) {
1413 $class = get_class( $list[0] );
1418 return wfMessage(
'parentheses' )->params(
"$class, {$list[1]}" )->escaped();
1429 return (
new GitInfo( $dir ) )->getHeadSHA1();
1437 $config = $this->getConfig();
1438 $scriptPath = $config->get( MainConfigNames::ScriptPath ) ?:
'/';
1441 'version-entrypoints-articlepath' => $config->get( MainConfigNames::ArticlePath ),
1442 'version-entrypoints-scriptpath' => $scriptPath,
1443 'version-entrypoints-index-php' =>
wfScript(
'index' ),
1444 'version-entrypoints-api-php' =>
wfScript(
'api' ),
1445 'version-entrypoints-rest-php' =>
wfScript(
'rest' ),
1448 $language = $this->getLanguage();
1450 'dir' => $language->getDir(),
1451 'lang' => $language->getHtmlCode(),
1455 $this->addTocSection(
'version-entrypoints',
'mw-version-entrypoints' );
1457 $out = Html::element(
1459 [
'id' =>
'mw-version-entrypoints' ],
1460 $this->msg(
'version-entrypoints' )->text()
1462 Html::openElement(
'table',
1464 'class' =>
'wikitable plainlinks',
1465 'id' =>
'mw-version-entrypoints-table',
1470 Html::openElement(
'thead' ) .
1471 Html::openElement(
'tr' ) .
1475 $this->msg(
'version-entrypoints-header-entrypoint' )->text()
1480 $this->msg(
'version-entrypoints-header-url' )->text()
1482 Html::closeElement(
'tr' ) .
1483 Html::closeElement(
'thead' );
1485 foreach ( $entryPoints as $message => $value ) {
1487 $out .= Html::openElement(
'tr' ) .
1488 Html::rawElement(
'td', [], $this->msg( $message )->parse() ) .
1489 Html::rawElement(
'td', [],
1493 $this->msg(
new RawMessage(
"[$url $value]" ) )->parse()
1496 Html::closeElement(
'tr' );
1499 $out .= Html::closeElement(
'table' );
1513class_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_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
if(!defined('MW_SETUP_CALLBACK'))
Load JSON files, and uses a Processor to extract information.
getAllThings()
Get credits information about all installed extensions and skins.
Marks HTML that shouldn't be escaped.
Base class for language-specific code.
A class containing constants representing the names of configuration variables.
Parent class for all special pages.