102 parent::__construct(
'Version' );
103 $this->parserFactory = $parserFactory;
104 $this->urlUtils = $urlUtils;
105 $this->dbProvider = $dbProvider;
118 $credits[$credit[
'type']][] = $credit;
127 $config = $this->getConfig();
128 $credits = self::getCredits( ExtensionRegistry::getInstance(), $config );
131 $this->outputHeader();
132 $out = $this->getOutput();
133 $out->setPreventClickjacking(
false );
136 $parts = explode(
'/', (
string)$par );
138 if ( isset( $parts[1] ) ) {
139 $extName = str_replace(
'_',
' ', $parts[1] );
141 foreach ( $credits as $extensions ) {
142 foreach ( $extensions as
$ext ) {
143 if ( isset(
$ext[
'name'] ) && (
$ext[
'name'] === $extName ) ) {
150 $out->setStatusCode( 404 );
153 $extName =
'MediaWiki';
157 switch ( strtolower( $parts[0] ) ) {
159 $out->addModuleStyles(
'mediawiki.special' );
161 $wikiText =
'{{int:version-credits-not-found}}';
162 if ( $extName ===
'MediaWiki' ) {
163 $wikiText = file_get_contents( MW_INSTALL_PATH .
'/CREDITS' );
165 $wikiText = str_replace(
166 [
'<!-- BEGIN CONTRIBUTOR LIST -->',
'<!-- END CONTRIBUTOR LIST -->' ],
167 [
'<div class="mw-version-credits">',
'</div>' ],
169 } elseif ( ( $extNode !==
null ) && isset( $extNode[
'path'] ) ) {
170 $file = ExtensionInfo::getAuthorsFileName( dirname( $extNode[
'path'] ) );
172 $wikiText = file_get_contents(
$file );
173 if ( str_ends_with(
$file,
'.txt' ) ) {
174 $wikiText = Html::element(
186 $out->setPageTitleMsg( $this->msg(
'version-credits-title' )->plaintextParams( $extName ) );
187 $out->addWikiTextAsInterface( $wikiText );
191 $out->setPageTitleMsg( $this->msg(
'version-license-title' )->plaintextParams( $extName ) );
193 $licenseFound =
false;
195 if ( $extName ===
'MediaWiki' ) {
196 $out->addWikiTextAsInterface(
197 file_get_contents( MW_INSTALL_PATH .
'/COPYING' )
199 $licenseFound =
true;
200 } elseif ( ( $extNode !==
null ) && isset( $extNode[
'path'] ) ) {
201 $files = ExtensionInfo::getLicenseFileNames( dirname( $extNode[
'path'] ) );
203 $licenseFound =
true;
204 foreach ( $files as
$file ) {
205 $out->addWikiTextAsInterface(
212 file_get_contents(
$file )
218 if ( !$licenseFound ) {
219 $out->addWikiTextAsInterface(
'{{int:version-license-not-found}}' );
224 $out->addModuleStyles(
'mediawiki.special' );
226 $out->addHTML( $this->getMediaWikiCredits() );
228 $this->tocData =
new TOCData();
230 $this->tocSection = 0;
231 $this->tocSubSection = 0;
235 $this->softwareInformation(),
236 $this->getEntryPointInfo(),
237 $this->getSkinCredits( $credits ),
238 $this->getExtensionCredits( $credits ),
239 $this->getExternalLibraries( $credits ),
240 $this->getClientSideLibraries(),
241 $this->getParserTags(),
242 $this->getParserFunctionHooks(),
250 $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
251 $pout->setText( Parser::TOC_PLACEHOLDER );
252 $out->addParserOutput( $pout );
270 private function addTocSection( $labelMsg, $id ) {
273 $this->tocSubSection = 0;
274 $this->tocData->addSection(
new SectionMetadata(
277 $this->msg( $labelMsg )->escaped(),
278 $this->getLanguage()->formatNum( $this->tocSection ),
279 (
string)$this->tocIndex,
294 private function addTocSubSection( $label, $id ) {
296 $this->tocSubSection++;
297 $this->tocData->addSection(
new SectionMetadata(
300 htmlspecialchars( $label ),
302 $this->getLanguage()->formatNum( $this->tocSection ) .
'.' .
303 $this->getLanguage()->formatNum( $this->tocSubSection ),
304 (
string)$this->tocIndex,
317 private function getMediaWikiCredits() {
320 $ret = Html::element(
322 [
'id' =>
'mw-version-license' ],
323 $this->msg(
'version-license' )->text()
326 $ret .= Html::rawElement(
'div', [
'class' =>
'plainlinks' ],
327 $this->msg(
new RawMessage( self::getCopyrightAndAuthorList() ) )->parseAsBlock() .
328 Html::rawElement(
'div', [
'class' =>
'mw-version-license-info' ],
329 $this->msg(
'version-license-info' )->parseAsBlock()
343 if ( defined(
'MEDIAWIKI_INSTALL' ) ) {
344 $othersLink =
'[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
345 wfMessage(
'version-poweredby-others' )->plain() .
']';
347 $othersLink =
'[[Special:Version/Credits|' .
348 wfMessage(
'version-poweredby-others' )->plain() .
']]';
351 $translatorsLink =
'[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
352 wfMessage(
'version-poweredby-translators' )->plain() .
']';
355 'Magnus Manske',
'Brion Vibber',
'Lee Daniel Crocker',
356 'Tim Starling',
'Erik Möller',
'Gabriel Wicke',
'Ævar Arnfjörð Bjarmason',
357 'Niklas Laxström',
'Domas Mituzas',
'Rob Church',
'Yuri Astrakhan',
358 'Aryeh Gregor',
'Aaron Schulz',
'Andrew Garrett',
'Raimond Spekking',
359 'Alexandre Emsenhuber',
'Siebrand Mazeland',
'Chad Horohoe',
360 'Roan Kattouw',
'Trevor Parscal',
'Bryan Tong Minh',
'Sam Reed',
361 'Victor Vasiliev',
'Rotem Liss',
'Platonides',
'Antoine Musso',
362 'Timo Tijhof',
'Daniel Kinzler',
'Jeroen De Dauw',
'Brad Jorsch',
363 'Bartosz Dziewoński',
'Ed Sanders',
'Moriel Schottlender',
364 'Kunal Mehta',
'James D. Forrester',
'Brian Wolff',
'Adam Shorland',
365 'DannyS712',
'Ori Livneh',
'Max Semenik',
'Amir Sarabadani',
366 'Derk-Jan Hartman',
'Petr Pchelko',
367 $othersLink, $translatorsLink
370 return wfMessage(
'version-poweredby-credits', MWTimestamp::getLocalInstance()->format(
'Y' ),
379 private function getSoftwareInformation() {
380 $dbr = $this->dbProvider->getReplicaDatabase();
386 '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked(),
387 '[https://php.net/ PHP]' => PHP_VERSION .
" (" . PHP_SAPI .
")",
388 '[https://icu.unicode.org/ ICU]' => INTL_ICU_VERSION,
389 $dbr->getSoftwareLink() => $dbr->getServerInfo(),
393 if ( phpversion(
"wikidiff2" ) ) {
394 $software[
'[https://www.mediawiki.org/wiki/Wikidiff2 wikidiff2]' ] = phpversion(
"wikidiff2" );
398 $this->getHookRunner()->onSoftwareInfo( $software );
408 private function softwareInformation() {
409 $this->addTocSection(
'version-software',
'mw-version-software' );
411 $out = Html::element(
413 [
'id' =>
'mw-version-software' ],
414 $this->msg(
'version-software' )->text()
417 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-software' ] );
419 $out .= Html::rawElement(
'tr', [],
420 Html::element(
'th', [], $this->msg(
'version-software-product' )->text() ) .
421 Html::element(
'th', [], $this->msg(
'version-software-version' )->text() )
424 foreach ( $this->getSoftwareInformation() as $name => $version ) {
425 $out .= Html::rawElement(
'tr', [],
426 Html::rawElement(
'td', [], $this->msg(
new RawMessage( $name ) )->parse() ) .
427 Html::rawElement(
'td', [
'dir' =>
'ltr' ], $this->msg(
new RawMessage( $version ) )->parse() )
431 $out .= Html::closeElement(
'table' );
444 public static function getVersion( $flags =
'', $lang =
null ) {
445 $gitInfo = GitInfo::repo()->getHeadSHA1();
448 } elseif ( $flags ===
'nodb' ) {
449 $shortSha1 = substr( $gitInfo, 0, 7 );
452 $shortSha1 = substr( $gitInfo, 0, 7 );
454 if ( $lang !==
null ) {
455 $msg->inLanguage( $lang );
457 $shortSha1 = $msg->params( $shortSha1 )->escaped();
472 $gitVersion = self::getVersionLinkedGit();
485 private static function getMWVersionLinked() {
488 if ( $hookRunner->onSpecialVersionVersionUrl(
MW_VERSION, $versionUrl ) ) {
490 preg_match(
"/^(\d+\.\d+)/",
MW_VERSION, $versionParts );
491 $versionUrl =
"https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
494 return '[' . $versionUrl .
' ' .
MW_VERSION .
']';
502 private static function getVersionLinkedGit() {
505 $gitInfo =
new GitInfo( MW_INSTALL_PATH );
506 $headSHA1 = $gitInfo->getHeadSHA1();
511 $shortSHA1 =
'(' . substr( $headSHA1, 0, 7 ) .
')';
513 $gitHeadUrl = $gitInfo->getHeadViewUrl();
514 if ( $gitHeadUrl !==
false ) {
515 $shortSHA1 =
"[$gitHeadUrl $shortSHA1]";
518 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
519 if ( $gitHeadCommitDate ) {
520 $shortSHA1 .= Html::element(
'br' ) .
$wgLang->timeanddate( (
string)$gitHeadCommitDate,
true );
523 return self::getMWVersionLinked() .
" $shortSHA1";
536 if ( self::$extensionTypes === false ) {
537 self::$extensionTypes = [
538 'specialpage' =>
wfMessage(
'version-specialpages' )->text(),
539 'editor' =>
wfMessage(
'version-editors' )->text(),
540 'parserhook' =>
wfMessage(
'version-parserhooks' )->text(),
541 'variable' =>
wfMessage(
'version-variables' )->text(),
542 'media' =>
wfMessage(
'version-mediahandlers' )->text(),
543 'antispam' =>
wfMessage(
'version-antispam' )->text(),
544 'skin' =>
wfMessage(
'version-skins' )->text(),
545 'api' =>
wfMessage(
'version-api' )->text(),
546 'other' =>
wfMessage(
'version-other' )->text(),
550 ->onExtensionTypes( self::$extensionTypes );
553 return self::$extensionTypes;
566 $types = self::getExtensionTypes();
568 return $types[$type] ?? $types[
'other'];
577 private function getExtensionCredits( array $credits ) {
578 $extensionTypes = self::getExtensionTypes();
580 $this->addTocSection(
'version-extensions',
'mw-version-ext' );
582 $out = Html::element(
584 [
'id' =>
'mw-version-ext' ],
585 $this->msg(
'version-extensions' )->text()
591 ( count( $credits ) === 1 && isset( $credits[
'skin'] ) )
593 $out .= Html::element(
596 $this->msg(
'version-extensions-no-ext' )->text()
603 if ( !array_key_exists(
'other', $credits ) ) {
604 $credits[
'other'] = [];
606 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-ext' ] );
609 foreach ( $credits as $type => $extensions ) {
610 if ( !array_key_exists( $type, $extensionTypes ) ) {
611 $credits[
'other'] = array_merge( $credits[
'other'], $extensions );
615 $this->firstExtOpened =
false;
617 foreach ( $extensionTypes as $type => $text ) {
619 if ( $type !==
'other' && $type !==
'skin' ) {
620 $out .= $this->getExtensionCategory( $type, $text, $credits[$type] ?? [] );
625 $out .= $this->getExtensionCategory(
'other', $extensionTypes[
'other'], $credits[
'other'] );
627 $out .= Html::closeElement(
'table' );
638 private function getSkinCredits( array $credits ) {
639 $this->addTocSection(
'version-skins',
'mw-version-skin' );
641 $out = Html::element(
643 [
'id' =>
'mw-version-skin' ],
644 $this->msg(
'version-skins' )->text()
647 if ( !isset( $credits[
'skin'] ) || !$credits[
'skin'] ) {
648 $out .= Html::element(
651 $this->msg(
'version-skins-no-skin' )->text()
656 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-skin' ] );
658 $this->firstExtOpened =
false;
659 $out .= $this->getExtensionCategory(
'skin',
null, $credits[
'skin'] );
661 $out .= Html::closeElement(
'table' );
674 MW_INSTALL_PATH .
'/vendor/composer/installed.json'
677 $extensionTypes = self::getExtensionTypes();
678 foreach ( $extensionTypes as $type => $message ) {
679 if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
682 foreach ( $credits[$type] as $extension ) {
683 if ( !isset( $extension[
'path'] ) ) {
686 $paths[] = dirname( $extension[
'path'] ) .
'/vendor/composer/installed.json';
692 foreach ( $paths as
$path ) {
693 if ( !file_exists(
$path ) ) {
699 $dependencies += $installed->getInstalledDependencies();
702 if ( $dependencies === [] ) {
706 ksort( $dependencies );
708 $this->addTocSection(
'version-libraries',
'mw-version-libraries' );
710 $out = Html::element(
712 [
'id' =>
'mw-version-libraries' ],
713 $this->msg(
'version-libraries' )->text()
715 $out .= Html::openElement(
717 [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-libraries' ]
719 $out .= Html::openElement(
'tr' )
720 . Html::element(
'th', [], $this->msg(
'version-libraries-library' )->text() )
721 . Html::element(
'th', [], $this->msg(
'version-libraries-version' )->text() )
722 . Html::element(
'th', [], $this->msg(
'version-libraries-license' )->text() )
723 . Html::element(
'th', [], $this->msg(
'version-libraries-description' )->text() )
724 . Html::element(
'th', [], $this->msg(
'version-libraries-authors' )->text() )
725 . Html::closeElement(
'tr' );
727 foreach ( $dependencies as $name => $info ) {
728 if ( !is_array( $info ) || str_starts_with( $info[
'type'],
'mediawiki-' ) ) {
733 $authors = array_map(
static function ( $arr ) {
734 return new HtmlArmor( isset( $arr[
'homepage'] ) ?
735 Html::element(
'a', [
'href' => $arr[
'homepage'] ], $arr[
'name'] ) :
736 htmlspecialchars( $arr[
'name'] )
738 }, $info[
'authors'] );
739 $authors = $this->listAuthors( $authors,
false, MW_INSTALL_PATH .
"/vendor/$name" );
744 $out .= Html::openElement(
'tr', [
747 'id' => Sanitizer::escapeIdForAttribute(
748 "mw-version-library-$name"
753 Linker::makeExternalLink(
754 "https://packagist.org/packages/$name", $name,
756 [
'class' =>
'mw-version-library-name' ]
759 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
761 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'licenses'] ) )
762 . Html::element(
'td', [
'lang' =>
'en',
'dir' =>
'ltr' ], $info[
'description'] )
763 . Html::rawElement(
'td', [], $authors )
764 . Html::closeElement(
'tr' );
766 $out .= Html::closeElement(
'table' );
776 private function getClientSideLibraries() {
777 $registryDirs = [
'MediaWiki' => MW_INSTALL_PATH .
'/resources/lib' ]
778 + ExtensionRegistry::getInstance()->getAttribute(
'ForeignResourcesDir' );
781 foreach ( $registryDirs as
$source => $registryDir ) {
782 $foreignResources = Yaml::parseFile(
"$registryDir/foreign-resources.yaml" );
783 foreach ( $foreignResources as $name => $module ) {
784 $key = $name . $module[
'version'];
789 $modules[$key] = $module + [
'name' => $name,
'source' => [
$source ] ];
794 $this->addTocSection(
'version-libraries-client',
'mw-version-libraries-client' );
796 $out = Html::element(
798 [
'id' =>
'mw-version-libraries-client' ],
799 $this->msg(
'version-libraries-client' )->text()
801 $out .= Html::openElement(
803 [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-libraries-client' ]
805 $out .= Html::openElement(
'tr' )
806 . Html::element(
'th', [], $this->msg(
'version-libraries-library' )->text() )
807 . Html::element(
'th', [], $this->msg(
'version-libraries-version' )->text() )
808 . Html::element(
'th', [], $this->msg(
'version-libraries-license' )->text() )
809 . Html::element(
'th', [], $this->msg(
'version-libraries-authors' )->text() )
810 . Html::element(
'th', [], $this->msg(
'version-libraries-source' )->text() )
811 . Html::closeElement(
'tr' );
813 foreach (
$modules as $name => $info ) {
817 $out .= Html::openElement(
'tr', [
820 'id' => Sanitizer::escapeIdForAttribute(
821 "mw-version-library-$name"
826 Linker::makeExternalLink(
827 $info[
'homepage'], $info[
'name'],
829 [
'class' =>
'mw-version-library-name' ]
832 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
833 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'license'] )
834 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'authors'] ??
'—' )
836 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'source'] ) )
837 . Html::closeElement(
'tr' );
839 $out .= Html::closeElement(
'table' );
850 $tags = $this->parserFactory->getMainInstance()->getTags();
855 $this->addTocSection(
'version-parser-extensiontags',
'mw-version-parser-extensiontags' );
857 $out = Html::rawElement(
859 [
'id' =>
'mw-version-parser-extensiontags' ],
862 [
'class' =>
'plainlinks' ],
863 Linker::makeExternalLink(
864 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
865 $this->msg(
'version-parser-extensiontags' )->text()
870 array_walk( $tags,
static function ( &$value ) {
872 $value = Html::element(
876 'style' =>
'white-space: nowrap;',
882 $out .= $this->listToText( $tags );
893 $funcHooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
898 $this->addTocSection(
'version-parser-function-hooks',
'mw-version-parser-function-hooks' );
900 $out = Html::rawElement(
902 [
'id' =>
'mw-version-parser-function-hooks' ],
905 [
'class' =>
'plainlinks' ],
906 Linker::makeExternalLink(
907 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
908 $this->msg(
'version-parser-function-hooks' )->text()
913 $out .= $this->listToText( $funcHooks );
930 if ( $creditsGroup ) {
931 $out .= $this->openExtType( $text,
'credits-' . $type );
933 usort( $creditsGroup, [ $this,
'compare' ] );
935 foreach ( $creditsGroup as $extension ) {
936 $out .= $this->getCreditsForExtension( $type, $extension );
950 return $this->getLanguage()->lc( $a[
'name'] ) <=> $this->getLanguage()->lc( $b[
'name'] );
972 $out = $this->getOutput();
976 if ( isset( $extension[
'namemsg'] ) ) {
978 $extensionName = $this->msg( $extension[
'namemsg'] )->text();
979 } elseif ( isset( $extension[
'name'] ) ) {
981 $extensionName = $extension[
'name'];
983 $extensionName = $this->msg(
'version-no-ext-name' )->text();
986 if ( isset( $extension[
'url'] ) ) {
987 $extensionNameLink = Linker::makeExternalLink(
992 [
'class' =>
'mw-version-ext-name' ]
995 $extensionNameLink = htmlspecialchars( $extensionName );
1001 $canonicalVersion =
'–';
1002 $extensionPath =
null;
1007 if ( isset( $extension[
'version'] ) ) {
1008 $canonicalVersion = $out->parseInlineAsInterface( $extension[
'version'] );
1011 if ( isset( $extension[
'path'] ) ) {
1012 $extensionPath = dirname( $extension[
'path'] );
1013 if ( $this->coreId ==
'' ) {
1014 wfDebug(
'Looking up core head id' );
1015 $coreHeadSHA1 = GitInfo::repo()->getHeadSHA1();
1016 if ( $coreHeadSHA1 ) {
1017 $this->coreId = $coreHeadSHA1;
1021 $memcKey = $cache->makeKey(
1022 'specialversion-ext-version-text', $extension[
'path'], $this->coreId
1024 [ $vcsVersion, $vcsLink, $vcsDate ] = $cache->get( $memcKey );
1026 if ( !$vcsVersion ) {
1027 wfDebug(
"Getting VCS info for extension {$extension['name']}" );
1028 $gitInfo =
new GitInfo( $extensionPath );
1029 $vcsVersion = $gitInfo->getHeadSHA1();
1030 if ( $vcsVersion !==
false ) {
1031 $vcsVersion = substr( $vcsVersion, 0, 7 );
1032 $vcsLink = $gitInfo->getHeadViewUrl();
1033 $vcsDate = $gitInfo->getHeadCommitDate();
1035 $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
1037 wfDebug(
"Pulled VCS info for extension {$extension['name']} from cache" );
1041 $versionString = Html::rawElement(
1043 [
'class' =>
'mw-version-ext-version' ],
1047 if ( $vcsVersion ) {
1049 $vcsVerString = Linker::makeExternalLink(
1051 $this->msg(
'version-version', $vcsVersion )->text(),
1054 [
'class' =>
'mw-version-ext-vcs-version' ]
1057 $vcsVerString = Html::element(
'span',
1058 [
'class' =>
'mw-version-ext-vcs-version' ],
1062 $versionString .=
" {$vcsVerString}";
1065 $versionString .=
' ' . Html::element(
'span', [
1066 'class' =>
'mw-version-ext-vcs-timestamp',
1067 'dir' => $this->getLanguage()->getDir(),
1068 ], $this->getLanguage()->timeanddate( $vcsDate,
true ) );
1070 $versionString = Html::rawElement(
'span',
1071 [
'class' =>
'mw-version-ext-meta-version' ],
1079 if ( isset( $extension[
'name'] ) ) {
1080 $licenseName =
null;
1081 if ( isset( $extension[
'license-name'] ) ) {
1082 $licenseName =
new HtmlArmor( $out->parseInlineAsInterface( $extension[
'license-name'] ) );
1083 } elseif ( $extensionPath !==
null && ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
1084 $licenseName = $this->msg(
'version-ext-license' )->text();
1086 if ( $licenseName !==
null ) {
1087 $licenseLink = $this->getLinkRenderer()->makeLink(
1088 $this->getPageTitle(
'License/' . $extension[
'name'] ),
1091 'class' =>
'mw-version-ext-license',
1101 if ( isset( $extension[
'descriptionmsg'] ) ) {
1103 $descriptionMsg = $extension[
'descriptionmsg'];
1105 if ( is_array( $descriptionMsg ) ) {
1106 $descriptionMsgKey = array_shift( $descriptionMsg );
1107 $descriptionMsg = array_map(
'htmlspecialchars', $descriptionMsg );
1108 $description = $this->msg( $descriptionMsgKey, ...$descriptionMsg )->text();
1110 $description = $this->msg( $descriptionMsg )->text();
1112 } elseif ( isset( $extension[
'description'] ) ) {
1114 $description = $extension[
'description'];
1118 $description = $out->parseInlineAsInterface( $description );
1121 $authors = $extension[
'author'] ?? [];
1123 $authors = $this->listAuthors( $authors, $extension[
'name'], $extensionPath );
1126 $html = Html::openElement(
'tr', [
1127 'class' =>
'mw-version-ext',
1128 'id' => Sanitizer::escapeIdForAttribute(
'mw-version-ext-' . $type .
'-' . $extension[
'name'] )
1132 $html .= Html::rawElement(
'td', [], $extensionNameLink );
1133 $html .= Html::rawElement(
'td', [], $versionString );
1134 $html .= Html::rawElement(
'td', [], $licenseLink );
1135 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-description' ], $description );
1136 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-authors' ], $authors );
1138 $html .= Html::closeElement(
'tr' );
1148 private function getHooks() {
1149 if ( !$this->getConfig()->
get( MainConfigNames::SpecialVersionShowHooks ) ) {
1153 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1154 $hookNames = $hookContainer->getHookNames();
1156 if ( !$hookNames ) {
1163 $this->addTocSection(
'version-hooks',
'mw-version-hooks' );
1164 $ret[] = Html::element(
1166 [
'id' =>
'mw-version-hooks' ],
1167 $this->msg(
'version-hooks' )->text()
1169 $ret[] = Html::openElement(
'table', [
'class' =>
'wikitable',
'id' =>
'sv-hooks' ] );
1170 $ret[] = Html::openElement(
'tr' );
1171 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-name' )->text() );
1172 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-subscribedby' )->text() );
1173 $ret[] = Html::closeElement(
'tr' );
1175 foreach ( $hookNames as $name ) {
1176 $handlers = $hookContainer->getHandlerDescriptions( $name );
1178 $ret[] = Html::openElement(
'tr' );
1179 $ret[] = Html::element(
'td', [], $name );
1181 $ret[] = Html::element(
'td', [], $this->listToText( $handlers ) );
1182 $ret[] = Html::closeElement(
'tr' );
1185 $ret[] = Html::closeElement(
'table' );
1187 return implode(
"\n", $ret );
1190 private function openExtType(
string $text =
null,
string $name =
null ) {
1193 $opt = [
'colspan' => 5 ];
1194 if ( $this->firstExtOpened ) {
1196 $out .= Html::rawElement(
'tr', [
'class' =>
'sv-space' ],
1197 Html::element(
'td', $opt )
1200 $this->firstExtOpened =
true;
1203 $opt[
'id'] =
"sv-$name";
1206 if ( $text !==
null ) {
1207 $out .= Html::rawElement(
'tr', [],
1208 Html::element(
'th', $opt, $text )
1212 if ( $name && $text !==
null ) {
1213 $this->addTocSubSection( $text,
"sv-$name" );
1216 $firstHeadingMsg = ( $name ===
'credits-skin' )
1217 ?
'version-skin-colheader-name'
1218 :
'version-ext-colheader-name';
1219 $out .= Html::openElement(
'tr' );
1220 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1221 $this->msg( $firstHeadingMsg )->text() );
1222 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1223 $this->msg(
'version-ext-colheader-version' )->text() );
1224 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1225 $this->msg(
'version-ext-colheader-license' )->text() );
1226 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1227 $this->msg(
'version-ext-colheader-description' )->text() );
1228 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1229 $this->msg(
'version-ext-colheader-credits' )->text() );
1230 $out .= Html::closeElement(
'tr' );
1240 private function IPInfo() {
1241 $ip = str_replace(
'--',
' - ', htmlspecialchars( $this->
getRequest()->getIP() ) );
1243 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1268 $linkRenderer = $this->getLinkRenderer();
1271 $authors = (array)$authors;
1276 if ( count( $authors ) === 1 && $authors[0] ===
'...' ) {
1279 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1280 return $linkRenderer->makeLink(
1281 $this->getPageTitle(
"Credits/$extName" ),
1282 $this->msg(
'version-poweredby-various' )->text()
1285 return $this->msg(
'version-poweredby-various' )->escaped();
1291 foreach ( $authors as $item ) {
1293 $list[] = HtmlArmor::getHtml( $item );
1294 } elseif ( $item ===
'...' ) {
1297 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1298 $text = $linkRenderer->makeLink(
1299 $this->getPageTitle(
"Credits/$extName" ),
1300 $this->msg(
'version-poweredby-others' )->text()
1303 $text = $this->msg(
'version-poweredby-others' )->escaped();
1306 } elseif ( str_ends_with( $item,
' ...]' ) ) {
1308 $list[] = $this->getOutput()->parseInlineAsInterface(
1309 substr( $item, 0, -4 ) . $this->msg(
'version-poweredby-others' )->text() .
"]"
1312 $list[] = $this->getOutput()->parseInlineAsInterface( $item );
1316 if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1317 $list[] = $linkRenderer->makeLink(
1318 $this->getPageTitle(
"Credits/$extName" ),
1319 $this->msg(
'version-poweredby-others' )->text()
1323 return $this->listToText( $list,
false );
1335 private function listToText( array $list,
bool $sort =
true ): string {
1343 return $this->getLanguage()
1344 ->listToText( array_map( [ __CLASS__,
'arrayToString' ], $list ) );
1357 if ( is_array( $list ) && count( $list ) == 1 ) {
1360 if ( $list instanceof Closure ) {
1363 } elseif ( is_object( $list ) ) {
1364 return wfMessage(
'parentheses' )->params( get_class( $list ) )->escaped();
1365 } elseif ( !is_array( $list ) ) {
1368 if ( is_object( $list[0] ) ) {
1369 $class = get_class( $list[0] );
1374 return wfMessage(
'parentheses' )->params(
"$class, {$list[1]}" )->escaped();
1385 return (
new GitInfo( $dir ) )->getHeadSHA1();
1393 $config = $this->getConfig();
1394 $scriptPath = $config->get( MainConfigNames::ScriptPath ) ?:
'/';
1397 'version-entrypoints-articlepath' => $config->get( MainConfigNames::ArticlePath ),
1398 'version-entrypoints-scriptpath' => $scriptPath,
1399 'version-entrypoints-index-php' =>
wfScript(
'index' ),
1400 'version-entrypoints-api-php' =>
wfScript(
'api' ),
1401 'version-entrypoints-rest-php' =>
wfScript(
'rest' ),
1404 $language = $this->getLanguage();
1406 'dir' => $language->getDir(),
1407 'lang' => $language->getHtmlCode()
1410 $this->addTocSection(
'version-entrypoints',
'mw-version-entrypoints' );
1412 $out = Html::element(
1414 [
'id' =>
'mw-version-entrypoints' ],
1415 $this->msg(
'version-entrypoints' )->text()
1417 Html::openElement(
'table',
1419 'class' =>
'wikitable plainlinks',
1420 'id' =>
'mw-version-entrypoints-table',
1425 Html::openElement(
'tr' ) .
1429 $this->msg(
'version-entrypoints-header-entrypoint' )->text()
1434 $this->msg(
'version-entrypoints-header-url' )->text()
1436 Html::closeElement(
'tr' );
1438 foreach ( $entryPoints as $message => $value ) {
1440 $out .= Html::openElement(
'tr' ) .
1441 Html::rawElement(
'td', [], $this->msg( $message )->parse() ) .
1442 Html::rawElement(
'td', [], Html::rawElement(
'code', [],
1443 $this->msg(
new RawMessage(
"[$url $value]" ) )->parse() ) ) .
1444 Html::closeElement(
'tr' );
1447 $out .= Html::closeElement(
'table' );