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>' ],
170 } elseif ( ( $extNode !==
null ) && isset( $extNode[
'path'] ) ) {
171 $file = ExtensionInfo::getAuthorsFileName( dirname( $extNode[
'path'] ) );
173 $wikiText = file_get_contents( $file );
174 if ( str_ends_with( $file,
'.txt' ) ) {
187 $out->setPageTitleMsg( $this->msg(
'version-credits-title' )->plaintextParams( $extName ) );
188 $out->addWikiTextAsInterface( $wikiText );
192 $out->setPageTitleMsg( $this->msg(
'version-license-title' )->plaintextParams( $extName ) );
194 $licenseFound =
false;
196 if ( $extName ===
'MediaWiki' ) {
197 $out->addWikiTextAsInterface(
198 file_get_contents( MW_INSTALL_PATH .
'/COPYING' )
200 $licenseFound =
true;
201 } elseif ( ( $extNode !==
null ) && isset( $extNode[
'path'] ) ) {
202 $files = ExtensionInfo::getLicenseFileNames( dirname( $extNode[
'path'] ) );
204 $licenseFound =
true;
205 foreach ( $files as $file ) {
206 $out->addWikiTextAsInterface(
213 file_get_contents( $file )
219 if ( !$licenseFound ) {
220 $out->addWikiTextAsInterface(
'{{int:version-license-not-found}}' );
225 $out->addModuleStyles(
'mediawiki.special' );
227 $out->addHTML( $this->getMediaWikiCredits() );
229 $this->tocData =
new TOCData();
231 $this->tocSection = 0;
232 $this->tocSubSection = 0;
236 $this->softwareInformation(),
237 $this->getEntryPointInfo(),
238 $this->getSkinCredits( $credits ),
239 $this->getExtensionCredits( $credits ),
240 $this->getExternalLibraries( $credits ),
241 $this->getClientSideLibraries(),
242 $this->getParserTags(),
243 $this->getParserFunctionHooks(),
251 $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
252 $pout->setRawText( Parser::TOC_PLACEHOLDER );
253 $out->addParserOutput( $pout );
256 foreach ( $sections as $content ) {
257 $out->addHTML( $content );
271 private function addTocSection( $labelMsg, $id ) {
274 $this->tocSubSection = 0;
275 $this->tocData->addSection(
new SectionMetadata(
278 $this->msg( $labelMsg )->escaped(),
279 $this->getLanguage()->formatNum( $this->tocSection ),
280 (
string)$this->tocIndex,
295 private function addTocSubSection( $label, $id ) {
297 $this->tocSubSection++;
298 $this->tocData->addSection(
new SectionMetadata(
301 htmlspecialchars( $label ),
303 $this->getLanguage()->formatNum( $this->tocSection ) .
'.' .
304 $this->getLanguage()->formatNum( $this->tocSubSection ),
305 (
string)$this->tocIndex,
318 private function getMediaWikiCredits() {
323 [
'id' =>
'mw-version-license' ],
324 $this->msg(
'version-license' )->text()
327 $ret .= Html::rawElement(
'div', [
'class' =>
'plainlinks' ],
328 $this->msg(
new RawMessage( self::getCopyrightAndAuthorList() ) )->parseAsBlock() .
329 Html::rawElement(
'div', [
'class' =>
'mw-version-license-info' ],
330 $this->msg(
'version-license-info' )->parseAsBlock()
344 if ( defined(
'MEDIAWIKI_INSTALL' ) ) {
345 $othersLink =
'[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
346 wfMessage(
'version-poweredby-others' )->plain() .
']';
348 $othersLink =
'[[Special:Version/Credits|' .
349 wfMessage(
'version-poweredby-others' )->plain() .
']]';
352 $translatorsLink =
'[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
353 wfMessage(
'version-poweredby-translators' )->plain() .
']';
356 'Magnus Manske',
'Brooke Vibber',
'Lee Daniel Crocker',
357 'Tim Starling',
'Erik Möller',
'Gabriel Wicke',
'Ævar Arnfjörð Bjarmason',
358 'Niklas Laxström',
'Domas Mituzas',
'Rob Church',
'Yuri Astrakhan',
359 'Aryeh Gregor',
'Aaron Schulz',
'Andrew Garrett',
'Raimond Spekking',
360 'Alexandre Emsenhuber',
'Siebrand Mazeland',
'Chad Horohoe',
361 'Roan Kattouw',
'Trevor Parscal',
'Bryan Tong Minh',
'Sam Reed',
362 'Victor Vasiliev',
'Rotem Liss',
'Platonides',
'Antoine Musso',
363 'Timo Tijhof',
'Daniel Kinzler',
'Jeroen De Dauw',
'Brad Jorsch',
364 'Bartosz Dziewoński',
'Ed Sanders',
'Moriel Schottlender',
365 'Kunal Mehta',
'James D. Forrester',
'Brian Wolff',
'Adam Shorland',
366 'DannyS712',
'Ori Livneh',
'Max Semenik',
'Amir Sarabadani',
367 'Derk-Jan Hartman',
'Petr Pchelko',
368 $othersLink, $translatorsLink
371 return wfMessage(
'version-poweredby-credits', MWTimestamp::getLocalInstance()->format(
'Y' ),
380 private function getSoftwareInformation() {
381 $dbr = $this->dbProvider->getReplicaDatabase();
387 '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked(),
388 '[https://php.net/ PHP]' => PHP_VERSION .
" (" . PHP_SAPI .
")",
389 '[https://icu.unicode.org/ ICU]' => INTL_ICU_VERSION,
390 $dbr->getSoftwareLink() => $dbr->getServerInfo(),
394 if ( phpversion(
"wikidiff2" ) ) {
395 $software[
'[https://www.mediawiki.org/wiki/Wikidiff2 wikidiff2]' ] = phpversion(
"wikidiff2" );
399 $this->getHookRunner()->onSoftwareInfo( $software );
409 private function softwareInformation() {
410 $this->addTocSection(
'version-software',
'mw-version-software' );
414 [
'id' =>
'mw-version-software' ],
415 $this->msg(
'version-software' )->text()
418 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-software' ] );
420 $out .= Html::rawElement(
'tr', [],
421 Html::element(
'th', [], $this->msg(
'version-software-product' )->text() ) .
422 Html::element(
'th', [], $this->msg(
'version-software-version' )->text() )
425 foreach ( $this->getSoftwareInformation() as $name => $version ) {
426 $out .= Html::rawElement(
429 Html::rawElement(
'td', [], $this->msg(
new RawMessage( $name ) )->parse() ) .
430 Html::rawElement(
'td', [
'dir' =>
'ltr' ], $this->msg(
new RawMessage( $version ) )->parse() )
434 $out .= Html::closeElement(
'table' );
448 public static function getVersion( $flags =
'', $lang =
null ) {
449 $gitInfo = GitInfo::repo()->getHeadSHA1();
452 } elseif ( $flags ===
'nodb' ) {
453 $shortSha1 = substr( $gitInfo, 0, 7 );
456 $shortSha1 = substr( $gitInfo, 0, 7 );
458 if ( $lang !==
null ) {
459 $msg->inLanguage( $lang );
461 $shortSha1 = $msg->params( $shortSha1 )->text();
476 return self::getVersionLinkedGit() ?:
MW_VERSION;
482 private static function getMWVersionLinked() {
485 if ( $hookRunner->onSpecialVersionVersionUrl(
MW_VERSION, $versionUrl ) ) {
487 preg_match(
"/^(\d+\.\d+)/",
MW_VERSION, $versionParts );
488 $versionUrl =
"https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
491 return '[' . $versionUrl .
' ' .
MW_VERSION .
']';
499 private static function getVersionLinkedGit() {
502 $gitInfo =
new GitInfo( MW_INSTALL_PATH );
503 $headSHA1 = $gitInfo->getHeadSHA1();
508 $shortSHA1 =
'(' . substr( $headSHA1, 0, 7 ) .
')';
510 $gitHeadUrl = $gitInfo->getHeadViewUrl();
511 if ( $gitHeadUrl !==
false ) {
512 $shortSHA1 =
"[$gitHeadUrl $shortSHA1]";
515 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
516 if ( $gitHeadCommitDate ) {
520 return self::getMWVersionLinked() .
" $shortSHA1";
533 if ( self::$extensionTypes === false ) {
534 self::$extensionTypes = [
535 'specialpage' =>
wfMessage(
'version-specialpages' )->text(),
536 'editor' =>
wfMessage(
'version-editors' )->text(),
537 'parserhook' =>
wfMessage(
'version-parserhooks' )->text(),
538 'variable' =>
wfMessage(
'version-variables' )->text(),
539 'media' =>
wfMessage(
'version-mediahandlers' )->text(),
540 'antispam' =>
wfMessage(
'version-antispam' )->text(),
541 'skin' =>
wfMessage(
'version-skins' )->text(),
542 'api' =>
wfMessage(
'version-api' )->text(),
543 'other' =>
wfMessage(
'version-other' )->text(),
547 ->onExtensionTypes( self::$extensionTypes );
550 return self::$extensionTypes;
563 $types = self::getExtensionTypes();
565 return $types[$type] ?? $types[
'other'];
574 private function getExtensionCredits( array $credits ) {
575 $extensionTypes = self::getExtensionTypes();
577 $this->addTocSection(
'version-extensions',
'mw-version-ext' );
579 $out = Html::element(
581 [
'id' =>
'mw-version-ext' ],
582 $this->msg(
'version-extensions' )->text()
588 ( count( $credits ) === 1 && isset( $credits[
'skin'] ) )
590 $out .= Html::element(
593 $this->msg(
'version-extensions-no-ext' )->text()
599 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-ext' ] );
602 $credits[
'other'] ??= [];
603 foreach ( $credits as $type => $extensions ) {
604 if ( !array_key_exists( $type, $extensionTypes ) ) {
605 $credits[
'other'] = array_merge( $credits[
'other'], $extensions );
609 $this->firstExtOpened =
false;
611 foreach ( $extensionTypes as $type => $text ) {
613 if ( $type !==
'other' && $type !==
'skin' ) {
614 $out .= $this->getExtensionCategory( $type, $text, $credits[$type] ?? [] );
619 $out .= $this->getExtensionCategory(
'other', $extensionTypes[
'other'], $credits[
'other'] );
621 $out .= Html::closeElement(
'table' );
632 private function getSkinCredits( array $credits ) {
633 $this->addTocSection(
'version-skins',
'mw-version-skin' );
635 $out = Html::element(
637 [
'id' =>
'mw-version-skin' ],
638 $this->msg(
'version-skins' )->text()
641 if ( !isset( $credits[
'skin'] ) || !$credits[
'skin'] ) {
642 $out .= Html::element(
645 $this->msg(
'version-skins-no-skin' )->text()
650 $out .= Html::openElement(
'table', [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-skin' ] );
652 $this->firstExtOpened =
false;
653 $out .= $this->getExtensionCategory(
'skin',
null, $credits[
'skin'] );
655 $out .= Html::closeElement(
'table' );
668 MW_INSTALL_PATH .
'/vendor/composer/installed.json'
671 $extensionTypes = self::getExtensionTypes();
672 foreach ( $extensionTypes as $type => $message ) {
673 if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
676 foreach ( $credits[$type] as $extension ) {
677 if ( !isset( $extension[
'path'] ) ) {
680 $paths[] = dirname( $extension[
'path'] ) .
'/vendor/composer/installed.json';
686 foreach ( $paths as
$path ) {
687 if ( !file_exists(
$path ) ) {
693 $dependencies += $installed->getInstalledDependencies();
696 if ( $dependencies === [] ) {
700 ksort( $dependencies );
702 $this->addTocSection(
'version-libraries',
'mw-version-libraries' );
704 $out = Html::element(
706 [
'id' =>
'mw-version-libraries' ],
707 $this->msg(
'version-libraries' )->text()
709 $out .= Html::openElement(
711 [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-libraries' ]
713 $out .= Html::openElement(
'tr' )
714 . Html::element(
'th', [], $this->msg(
'version-libraries-library' )->text() )
715 . Html::element(
'th', [], $this->msg(
'version-libraries-version' )->text() )
716 . Html::element(
'th', [], $this->msg(
'version-libraries-license' )->text() )
717 . Html::element(
'th', [], $this->msg(
'version-libraries-description' )->text() )
718 . Html::element(
'th', [], $this->msg(
'version-libraries-authors' )->text() )
719 . Html::closeElement(
'tr' );
721 foreach ( $dependencies as $name => $info ) {
722 if ( !is_array( $info ) || str_starts_with( $info[
'type'],
'mediawiki-' ) ) {
727 $authors = array_map(
static function ( $arr ) {
728 return new HtmlArmor( isset( $arr[
'homepage'] ) ?
729 Html::element(
'a', [
'href' => $arr[
'homepage'] ], $arr[
'name'] ) :
730 htmlspecialchars( $arr[
'name'] )
732 }, $info[
'authors'] );
733 $authors = $this->listAuthors( $authors,
false, MW_INSTALL_PATH .
"/vendor/$name" );
738 $out .= Html::openElement(
'tr', [
741 'id' => Sanitizer::escapeIdForAttribute(
742 "mw-version-library-$name"
747 Linker::makeExternalLink(
748 "https://packagist.org/packages/$name", $name,
750 [
'class' =>
'mw-version-library-name' ]
753 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
755 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'licenses'] ) )
756 . Html::element(
'td', [
'lang' =>
'en',
'dir' =>
'ltr' ], $info[
'description'] )
757 . Html::rawElement(
'td', [], $authors )
758 . Html::closeElement(
'tr' );
760 $out .= Html::closeElement(
'table' );
771 $registryDirs = [
'MediaWiki' => MW_INSTALL_PATH .
'/resources/lib' ]
772 + ExtensionRegistry::getInstance()->getAttribute(
'ForeignResourcesDir' );
775 foreach ( $registryDirs as
$source => $registryDir ) {
776 $foreignResources = Yaml::parseFile(
"$registryDir/foreign-resources.yaml" );
777 foreach ( $foreignResources as $name => $module ) {
778 $key = $name . $module[
'version'];
779 if ( isset( $modules[$key] ) ) {
780 $modules[$key][
'source'][] =
$source;
783 $modules[$key] = $module + [
'name' => $name,
'source' => [
$source ] ];
795 private function getClientSideLibraries() {
796 $this->addTocSection(
'version-libraries-client',
'mw-version-libraries-client' );
798 $out = Html::element(
800 [
'id' =>
'mw-version-libraries-client' ],
801 $this->msg(
'version-libraries-client' )->text()
803 $out .= Html::openElement(
805 [
'class' =>
'wikitable plainlinks',
'id' =>
'sv-libraries-client' ]
807 $out .= Html::openElement(
'tr' )
808 . Html::element(
'th', [], $this->msg(
'version-libraries-library' )->text() )
809 . Html::element(
'th', [], $this->msg(
'version-libraries-version' )->text() )
810 . Html::element(
'th', [], $this->msg(
'version-libraries-license' )->text() )
811 . Html::element(
'th', [], $this->msg(
'version-libraries-authors' )->text() )
812 . Html::element(
'th', [], $this->msg(
'version-libraries-source' )->text() )
813 . Html::closeElement(
'tr' );
815 foreach ( self::parseForeignResources() as $name => $info ) {
819 $out .= Html::openElement(
'tr', [
822 'id' => Sanitizer::escapeIdForAttribute(
823 "mw-version-library-$name"
828 Linker::makeExternalLink(
829 $info[
'homepage'], $info[
'name'],
831 [
'class' =>
'mw-version-library-name' ]
834 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'version'] )
835 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'license'] )
836 . Html::element(
'td', [
'dir' =>
'auto' ], $info[
'authors'] ??
'—' )
838 . Html::element(
'td', [
'dir' =>
'auto' ], $this->listToText( $info[
'source'] ) )
839 . Html::closeElement(
'tr' );
841 $out .= Html::closeElement(
'table' );
852 $tags = $this->parserFactory->getMainInstance()->getTags();
857 $this->addTocSection(
'version-parser-extensiontags',
'mw-version-parser-extensiontags' );
859 $out = Html::rawElement(
861 [
'id' =>
'mw-version-parser-extensiontags' ],
864 [
'class' =>
'plainlinks' ],
865 Linker::makeExternalLink(
866 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
867 $this->msg(
'version-parser-extensiontags' )->text()
872 array_walk( $tags,
static function ( &$value ) {
874 $value = Html::element(
878 'style' =>
'white-space: nowrap;',
884 $out .= $this->listToText( $tags );
895 $funcHooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
900 $this->addTocSection(
'version-parser-function-hooks',
'mw-version-parser-function-hooks' );
902 $out = Html::rawElement(
904 [
'id' =>
'mw-version-parser-function-hooks' ],
907 [
'class' =>
'plainlinks' ],
908 Linker::makeExternalLink(
909 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
910 $this->msg(
'version-parser-function-hooks' )->text()
915 $out .= $this->listToText( $funcHooks );
932 if ( $creditsGroup ) {
933 $out .= $this->openExtType( $text,
'credits-' . $type );
935 usort( $creditsGroup, [ $this,
'compare' ] );
937 foreach ( $creditsGroup as $extension ) {
938 $out .= $this->getCreditsForExtension( $type, $extension );
952 return $this->getLanguage()->lc( $a[
'name'] ) <=> $this->getLanguage()->lc( $b[
'name'] );
974 $out = $this->getOutput();
978 if ( isset( $extension[
'namemsg'] ) ) {
980 $extensionName = $this->msg( $extension[
'namemsg'] )->text();
981 } elseif ( isset( $extension[
'name'] ) ) {
983 $extensionName = $extension[
'name'];
985 $extensionName = $this->msg(
'version-no-ext-name' )->text();
988 if ( isset( $extension[
'url'] ) ) {
989 $extensionNameLink = Linker::makeExternalLink(
994 [
'class' =>
'mw-version-ext-name' ]
997 $extensionNameLink = htmlspecialchars( $extensionName );
1003 $canonicalVersion =
'–';
1004 $extensionPath =
null;
1009 if ( isset( $extension[
'version'] ) ) {
1010 $canonicalVersion = $out->parseInlineAsInterface( $extension[
'version'] );
1013 if ( isset( $extension[
'path'] ) ) {
1014 $extensionPath = dirname( $extension[
'path'] );
1015 if ( $this->coreId ==
'' ) {
1016 wfDebug(
'Looking up core head id' );
1017 $coreHeadSHA1 = GitInfo::repo()->getHeadSHA1();
1018 if ( $coreHeadSHA1 ) {
1019 $this->coreId = $coreHeadSHA1;
1023 $memcKey = $cache->makeKey(
1024 'specialversion-ext-version-text', $extension[
'path'], $this->coreId
1026 [ $vcsVersion, $vcsLink, $vcsDate ] = $cache->get( $memcKey );
1028 if ( !$vcsVersion ) {
1029 wfDebug(
"Getting VCS info for extension {$extension['name']}" );
1030 $gitInfo =
new GitInfo( $extensionPath );
1031 $vcsVersion = $gitInfo->getHeadSHA1();
1032 if ( $vcsVersion !==
false ) {
1033 $vcsVersion = substr( $vcsVersion, 0, 7 );
1034 $vcsLink = $gitInfo->getHeadViewUrl();
1035 $vcsDate = $gitInfo->getHeadCommitDate();
1037 $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
1039 wfDebug(
"Pulled VCS info for extension {$extension['name']} from cache" );
1043 $versionString = Html::rawElement(
1045 [
'class' =>
'mw-version-ext-version' ],
1049 if ( $vcsVersion ) {
1051 $vcsVerString = Linker::makeExternalLink(
1053 $this->msg(
'version-version', $vcsVersion )->text(),
1056 [
'class' =>
'mw-version-ext-vcs-version' ]
1059 $vcsVerString = Html::element(
'span',
1060 [
'class' =>
'mw-version-ext-vcs-version' ],
1064 $versionString .=
" {$vcsVerString}";
1067 $versionString .=
' ' . Html::element(
'span', [
1068 'class' =>
'mw-version-ext-vcs-timestamp',
1069 'dir' => $this->getLanguage()->getDir(),
1070 ], $this->getLanguage()->timeanddate( $vcsDate,
true ) );
1072 $versionString = Html::rawElement(
'span',
1073 [
'class' =>
'mw-version-ext-meta-version' ],
1081 if ( isset( $extension[
'name'] ) ) {
1082 $licenseName =
null;
1083 if ( isset( $extension[
'license-name'] ) ) {
1084 $licenseName =
new HtmlArmor( $out->parseInlineAsInterface( $extension[
'license-name'] ) );
1085 } elseif ( $extensionPath !==
null && ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
1086 $licenseName = $this->msg(
'version-ext-license' )->text();
1088 if ( $licenseName !==
null ) {
1089 $licenseLink = $this->getLinkRenderer()->makeLink(
1090 $this->getPageTitle(
'License/' . $extension[
'name'] ),
1093 'class' =>
'mw-version-ext-license',
1103 if ( isset( $extension[
'descriptionmsg'] ) ) {
1105 $descriptionMsg = $extension[
'descriptionmsg'];
1107 if ( is_array( $descriptionMsg ) ) {
1108 $descriptionMsgKey = array_shift( $descriptionMsg );
1109 $descriptionMsg = array_map(
'htmlspecialchars', $descriptionMsg );
1110 $description = $this->msg( $descriptionMsgKey, ...$descriptionMsg )->text();
1112 $description = $this->msg( $descriptionMsg )->text();
1114 } elseif ( isset( $extension[
'description'] ) ) {
1116 $description = $extension[
'description'];
1120 $description = $out->parseInlineAsInterface( $description );
1123 $authors = $extension[
'author'] ?? [];
1125 $authors = $this->listAuthors( $authors, $extension[
'name'], $extensionPath );
1128 $html = Html::openElement(
'tr', [
1129 'class' =>
'mw-version-ext',
1130 'id' => Sanitizer::escapeIdForAttribute(
'mw-version-ext-' . $type .
'-' . $extension[
'name'] )
1134 $html .= Html::rawElement(
'td', [], $extensionNameLink );
1135 $html .= Html::rawElement(
'td', [], $versionString );
1136 $html .= Html::rawElement(
'td', [], $licenseLink );
1137 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-description' ], $description );
1138 $html .= Html::rawElement(
'td', [
'class' =>
'mw-version-ext-authors' ], $authors );
1140 $html .= Html::closeElement(
'tr' );
1150 private function getHooks() {
1151 if ( !$this->getConfig()->
get( MainConfigNames::SpecialVersionShowHooks ) ) {
1155 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1156 $hookNames = $hookContainer->getHookNames();
1158 if ( !$hookNames ) {
1165 $this->addTocSection(
'version-hooks',
'mw-version-hooks' );
1166 $ret[] = Html::element(
1168 [
'id' =>
'mw-version-hooks' ],
1169 $this->msg(
'version-hooks' )->text()
1171 $ret[] = Html::openElement(
'table', [
'class' =>
'wikitable',
'id' =>
'sv-hooks' ] );
1172 $ret[] = Html::openElement(
'tr' );
1173 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-name' )->text() );
1174 $ret[] = Html::element(
'th', [], $this->msg(
'version-hook-subscribedby' )->text() );
1175 $ret[] = Html::closeElement(
'tr' );
1177 foreach ( $hookNames as $name ) {
1178 $handlers = $hookContainer->getHandlerDescriptions( $name );
1180 $ret[] = Html::openElement(
'tr' );
1181 $ret[] = Html::element(
'td', [], $name );
1183 $ret[] = Html::element(
'td', [], $this->listToText( $handlers ) );
1184 $ret[] = Html::closeElement(
'tr' );
1187 $ret[] = Html::closeElement(
'table' );
1189 return implode(
"\n", $ret );
1192 private function openExtType(
string $text =
null,
string $name =
null ) {
1195 $opt = [
'colspan' => 5 ];
1196 if ( $this->firstExtOpened ) {
1198 $out .= Html::rawElement(
'tr', [
'class' =>
'sv-space' ],
1199 Html::element(
'td', $opt )
1202 $this->firstExtOpened =
true;
1205 $opt[
'id'] =
"sv-$name";
1208 if ( $text !==
null ) {
1209 $out .= Html::rawElement(
'tr', [],
1210 Html::element(
'th', $opt, $text )
1214 if ( $name && $text !==
null ) {
1215 $this->addTocSubSection( $text,
"sv-$name" );
1218 $firstHeadingMsg = ( $name ===
'credits-skin' )
1219 ?
'version-skin-colheader-name'
1220 :
'version-ext-colheader-name';
1221 $out .= Html::openElement(
'tr' );
1222 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1223 $this->msg( $firstHeadingMsg )->text() );
1224 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1225 $this->msg(
'version-ext-colheader-version' )->text() );
1226 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1227 $this->msg(
'version-ext-colheader-license' )->text() );
1228 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1229 $this->msg(
'version-ext-colheader-description' )->text() );
1230 $out .= Html::element(
'th', [
'class' =>
'mw-version-ext-col-label' ],
1231 $this->msg(
'version-ext-colheader-credits' )->text() );
1232 $out .= Html::closeElement(
'tr' );
1242 private function IPInfo() {
1243 $ip = str_replace(
'--',
' - ', htmlspecialchars( $this->
getRequest()->getIP() ) );
1245 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1270 $linkRenderer = $this->getLinkRenderer();
1273 $authors = (array)$authors;
1278 if ( count( $authors ) === 1 && $authors[0] ===
'...' ) {
1281 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1282 return $linkRenderer->makeLink(
1283 $this->getPageTitle(
"Credits/$extName" ),
1284 $this->msg(
'version-poweredby-various' )->text()
1287 return $this->msg(
'version-poweredby-various' )->escaped();
1293 foreach ( $authors as $item ) {
1295 $list[] = HtmlArmor::getHtml( $item );
1296 } elseif ( $item ===
'...' ) {
1299 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1300 $text = $linkRenderer->makeLink(
1301 $this->getPageTitle(
"Credits/$extName" ),
1302 $this->msg(
'version-poweredby-others' )->text()
1305 $text = $this->msg(
'version-poweredby-others' )->escaped();
1308 } elseif ( str_ends_with( $item,
' ...]' ) ) {
1310 $list[] = $this->getOutput()->parseInlineAsInterface(
1311 substr( $item, 0, -4 ) . $this->msg(
'version-poweredby-others' )->text() .
"]"
1314 $list[] = $this->getOutput()->parseInlineAsInterface( $item );
1318 if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1319 $list[] = $linkRenderer->makeLink(
1320 $this->getPageTitle(
"Credits/$extName" ),
1321 $this->msg(
'version-poweredby-others' )->text()
1325 return $this->listToText( $list,
false );
1337 private function listToText( array $list,
bool $sort =
true ): string {
1345 return $this->getLanguage()
1346 ->listToText( array_map( [ __CLASS__,
'arrayToString' ], $list ) );
1359 if ( is_array( $list ) && count( $list ) == 1 ) {
1362 if ( $list instanceof Closure ) {
1365 } elseif ( is_object( $list ) ) {
1366 return wfMessage(
'parentheses' )->params( get_class( $list ) )->escaped();
1367 } elseif ( !is_array( $list ) ) {
1370 if ( is_object( $list[0] ) ) {
1371 $class = get_class( $list[0] );
1376 return wfMessage(
'parentheses' )->params(
"$class, {$list[1]}" )->escaped();
1387 return (
new GitInfo( $dir ) )->getHeadSHA1();
1395 $config = $this->getConfig();
1396 $scriptPath = $config->get( MainConfigNames::ScriptPath ) ?:
'/';
1399 'version-entrypoints-articlepath' => $config->get( MainConfigNames::ArticlePath ),
1400 'version-entrypoints-scriptpath' => $scriptPath,
1401 'version-entrypoints-index-php' =>
wfScript(
'index' ),
1402 'version-entrypoints-api-php' =>
wfScript(
'api' ),
1403 'version-entrypoints-rest-php' =>
wfScript(
'rest' ),
1406 $language = $this->getLanguage();
1408 'dir' => $language->getDir(),
1409 'lang' => $language->getHtmlCode()
1412 $this->addTocSection(
'version-entrypoints',
'mw-version-entrypoints' );
1414 $out = Html::element(
1416 [
'id' =>
'mw-version-entrypoints' ],
1417 $this->msg(
'version-entrypoints' )->text()
1419 Html::openElement(
'table',
1421 'class' =>
'wikitable plainlinks',
1422 'id' =>
'mw-version-entrypoints-table',
1427 Html::openElement(
'tr' ) .
1431 $this->msg(
'version-entrypoints-header-entrypoint' )->text()
1436 $this->msg(
'version-entrypoints-header-url' )->text()
1438 Html::closeElement(
'tr' );
1440 foreach ( $entryPoints as $message => $value ) {
1442 $out .= Html::openElement(
'tr' ) .
1443 Html::rawElement(
'td', [], $this->msg( $message )->parse() ) .
1444 Html::rawElement(
'td', [],
1448 $this->msg(
new RawMessage(
"[$url $value]" ) )->parse()
1451 Html::closeElement(
'tr' );
1454 $out .= Html::closeElement(
'table' );