MediaWiki REL1_35
SpecialVersion.php
Go to the documentation of this file.
1<?php
28
35
39 protected $firstExtOpened = false;
40
44 protected $coreId = '';
45
49 protected static $extensionTypes = false;
50
51 public function __construct() {
52 parent::__construct( 'Version' );
53 }
54
62 public static function getCredits( ExtensionRegistry $reg, Config $conf ) : array {
63 $credits = $conf->get( 'ExtensionCredits' );
64 foreach ( $reg->getAllThings() as $name => $credit ) {
65 $credits[$credit['type']][] = $credit;
66 }
67 return $credits;
68 }
69
74 public function execute( $par ) {
75 global $IP;
76 $config = $this->getConfig();
77 $credits = self::getCredits( ExtensionRegistry::getInstance(), $config );
78
79 $this->setHeaders();
80 $this->outputHeader();
81 $out = $this->getOutput();
82 $out->allowClickjacking();
83
84 // Explode the sub page information into useful bits
85 $parts = explode( '/', (string)$par );
86 $extNode = null;
87 if ( isset( $parts[1] ) ) {
88 $extName = str_replace( '_', ' ', $parts[1] );
89 // Find it!
90 foreach ( $credits as $group => $extensions ) {
91 foreach ( $extensions as $ext ) {
92 if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
93 $extNode = &$ext;
94 break 2;
95 }
96 }
97 }
98 if ( !$extNode ) {
99 $out->setStatusCode( 404 );
100 }
101 } else {
102 $extName = 'MediaWiki';
103 }
104
105 // Now figure out what to do
106 switch ( strtolower( $parts[0] ) ) {
107 case 'credits':
108 $out->addModuleStyles( 'mediawiki.special.version' );
109
110 $wikiText = '{{int:version-credits-not-found}}';
111 if ( $extName === 'MediaWiki' ) {
112 $wikiText = file_get_contents( $IP . '/CREDITS' );
113 // Put the contributor list into columns
114 $wikiText = str_replace(
115 [ '<!-- BEGIN CONTRIBUTOR LIST -->', '<!-- END CONTRIBUTOR LIST -->' ],
116 [ '<div class="mw-version-credits">', '</div>' ],
117 $wikiText );
118 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
119 $file = ExtensionInfo::getAuthorsFileName( dirname( $extNode['path'] ) );
120 if ( $file ) {
121 $wikiText = file_get_contents( $file );
122 if ( substr( $file, -4 ) === '.txt' ) {
123 $wikiText = Html::element(
124 'pre',
125 [
126 'lang' => 'en',
127 'dir' => 'ltr',
128 ],
129 $wikiText
130 );
131 }
132 }
133 }
134
135 $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
136 $out->addWikiTextAsInterface( $wikiText );
137 break;
138
139 case 'license':
140 $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
141
142 $licenseFound = false;
143
144 if ( $extName === 'MediaWiki' ) {
145 $out->addWikiTextAsInterface(
146 file_get_contents( $IP . '/COPYING' )
147 );
148 $licenseFound = true;
149 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
150 $files = ExtensionInfo::getLicenseFileNames( dirname( $extNode['path'] ) );
151
152 if ( count( $files ) ) {
153 $licenseFound = true;
154 foreach ( $files as $file ) {
155 $out->addWikiTextAsInterface(
156 Html::element(
157 'pre',
158 [
159 'lang' => 'en',
160 'dir' => 'ltr',
161 ],
162 file_get_contents( $file )
163 )
164 );
165 }
166 }
167 }
168 if ( !$licenseFound ) {
169 $out->addWikiTextAsInterface( '{{int:version-license-not-found}}' );
170 }
171 break;
172 default:
173 $out->addModuleStyles( 'mediawiki.special.version' );
174 $out->addWikiTextAsInterface(
175 self::getMediaWikiCredits() .
176 self::softwareInformation() .
177 $this->getEntryPointInfo()
178 );
179 $out->addHTML(
180 $this->getSkinCredits( $credits ) .
181 $this->getExtensionCredits( $credits ) .
182 $this->getExternalLibraries( $credits ) .
183 $this->getParserTags() .
184 $this->getParserFunctionHooks()
185 );
186 $out->addWikiTextAsInterface( $this->getWgHooks() );
187 $out->addHTML( $this->IPInfo() );
188
189 break;
190 }
191 }
192
198 private static function getMediaWikiCredits() {
199 $ret = Xml::element(
200 'h2',
201 [ 'id' => 'mw-version-license' ],
202 wfMessage( 'version-license' )->text()
203 );
204
205 // This text is always left-to-right.
206 $ret .= '<div class="plainlinks">';
207 $ret .= "__NOTOC__
208 " . self::getCopyrightAndAuthorList() . "\n
209 " . '<div class="mw-version-license-info">' .
210 wfMessage( 'version-license-info' )->text() .
211 '</div>';
212 $ret .= '</div>';
213
214 return str_replace( "\t\t", '', $ret ) . "\n";
215 }
216
222 public static function getCopyrightAndAuthorList() {
223 global $wgLang;
224
225 if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
226 $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
227 wfMessage( 'version-poweredby-others' )->text() . ']';
228 } else {
229 $othersLink = '[[Special:Version/Credits|' .
230 wfMessage( 'version-poweredby-others' )->text() . ']]';
231 }
232
233 $translatorsLink = '[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
234 wfMessage( 'version-poweredby-translators' )->text() . ']';
235
236 $authorList = [
237 'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
238 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
239 'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
240 'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
241 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
242 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
243 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
244 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
245 'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
246 'Kunal Mehta', 'James D. Forrester', 'Brian Wolff', 'Adam Shorland',
247 $othersLink, $translatorsLink
248 ];
249
250 return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
251 $wgLang->listToText( $authorList ) )->text();
252 }
253
259 public static function getSoftwareInformation() {
261
262 // Put the software in an array of form 'name' => 'version'. All messages should
263 // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
264 // wikimarkup can be used.
265 $software = [
266 '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked(),
267 '[https://php.net/ PHP]' => PHP_VERSION . " (" . PHP_SAPI . ")",
268 $dbr->getSoftwareLink() => $dbr->getServerInfo(),
269 ];
270
271 if ( defined( 'INTL_ICU_VERSION' ) ) {
272 $software['[http://site.icu-project.org/ ICU]'] = INTL_ICU_VERSION;
273 }
274
275 // Allow a hook to add/remove items.
276 Hooks::runner()->onSoftwareInfo( $software );
277
278 return $software;
279 }
280
286 public static function softwareInformation() {
287 $out = Xml::element(
288 'h2',
289 [ 'id' => 'mw-version-software' ],
290 wfMessage( 'version-software' )->text()
291 ) .
292 Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] ) .
293 "<tr>
294 <th>" . wfMessage( 'version-software-product' )->text() . "</th>
295 <th>" . wfMessage( 'version-software-version' )->text() . "</th>
296 </tr>\n";
297
298 foreach ( self::getSoftwareInformation() as $name => $version ) {
299 $out .= "<tr>
300 <td>" . $name . "</td>
301 <td dir=\"ltr\">" . $version . "</td>
302 </tr>\n";
303 }
304
305 return $out . Xml::closeElement( 'table' );
306 }
307
315 public static function getVersion( $flags = '', $lang = null ) {
316 global $IP;
317
318 $gitInfo = self::getGitHeadSha1( $IP );
319 if ( !$gitInfo ) {
320 $version = MW_VERSION;
321 } elseif ( $flags === 'nodb' ) {
322 $shortSha1 = substr( $gitInfo, 0, 7 );
323 $version = MW_VERSION . " ($shortSha1)";
324 } else {
325 $shortSha1 = substr( $gitInfo, 0, 7 );
326 $msg = wfMessage( 'parentheses' );
327 if ( $lang !== null ) {
328 $msg->inLanguage( $lang );
329 }
330 $shortSha1 = $msg->params( $shortSha1 )->escaped();
331 $version = MW_VERSION . ' ' . $shortSha1;
332 }
333
334 return $version;
335 }
336
344 public static function getVersionLinked() {
345 $gitVersion = self::getVersionLinkedGit();
346 if ( $gitVersion ) {
347 $v = $gitVersion;
348 } else {
349 $v = MW_VERSION; // fallback
350 }
351
352 return $v;
353 }
354
358 private static function getMWVersionLinked() {
359 $versionUrl = "";
360 if ( Hooks::runner()->onSpecialVersionVersionUrl( MW_VERSION, $versionUrl ) ) {
361 $versionParts = [];
362 preg_match( "/^(\d+\.\d+)/", MW_VERSION, $versionParts );
363 $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
364 }
365
366 return '[' . $versionUrl . ' ' . MW_VERSION . ']';
367 }
368
374 private static function getVersionLinkedGit() {
375 global $IP, $wgLang;
376
377 $gitInfo = new GitInfo( $IP );
378 $headSHA1 = $gitInfo->getHeadSHA1();
379 if ( !$headSHA1 ) {
380 return false;
381 }
382
383 $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
384
385 $gitHeadUrl = $gitInfo->getHeadViewUrl();
386 if ( $gitHeadUrl !== false ) {
387 $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
388 }
389
390 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
391 if ( $gitHeadCommitDate ) {
392 $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
393 }
394
395 return self::getMWVersionLinked() . " $shortSHA1";
396 }
397
408 public static function getExtensionTypes() {
409 if ( self::$extensionTypes === false ) {
410 self::$extensionTypes = [
411 'specialpage' => wfMessage( 'version-specialpages' )->text(),
412 'editor' => wfMessage( 'version-editors' )->text(),
413 'parserhook' => wfMessage( 'version-parserhooks' )->text(),
414 'variable' => wfMessage( 'version-variables' )->text(),
415 'media' => wfMessage( 'version-mediahandlers' )->text(),
416 'antispam' => wfMessage( 'version-antispam' )->text(),
417 'skin' => wfMessage( 'version-skins' )->text(),
418 'api' => wfMessage( 'version-api' )->text(),
419 'other' => wfMessage( 'version-other' )->text(),
420 ];
421
422 Hooks::runner()->onExtensionTypes( self::$extensionTypes );
423 }
424
425 return self::$extensionTypes;
426 }
427
437 public static function getExtensionTypeName( $type ) {
438 $types = self::getExtensionTypes();
439
440 return $types[$type] ?? $types['other'];
441 }
442
449 private function getExtensionCredits( array $credits ) {
450 if (
451 !$credits ||
452 // Skins are displayed separately, see getSkinCredits()
453 ( count( $credits ) === 1 && isset( $credits['skin'] ) )
454 ) {
455 return '';
456 }
457
458 $extensionTypes = self::getExtensionTypes();
459
460 $out = Xml::element(
461 'h2',
462 [ 'id' => 'mw-version-ext' ],
463 $this->msg( 'version-extensions' )->text()
464 ) .
465 Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ] );
466
467 // Make sure the 'other' type is set to an array.
468 if ( !array_key_exists( 'other', $credits ) ) {
469 $credits['other'] = [];
470 }
471
472 // Find all extensions that do not have a valid type and give them the type 'other'.
473 foreach ( $credits as $type => $extensions ) {
474 if ( !array_key_exists( $type, $extensionTypes ) ) {
475 $credits['other'] = array_merge( $credits['other'], $extensions );
476 }
477 }
478
479 $this->firstExtOpened = false;
480 // Loop through the extension categories to display their extensions in the list.
481 foreach ( $extensionTypes as $type => $message ) {
482 // Skins have a separate section
483 if ( $type !== 'other' && $type !== 'skin' ) {
484 $out .= $this->getExtensionCategory( $type, $message, $credits[$type] ?? [] );
485 }
486 }
487
488 // We want the 'other' type to be last in the list.
489 $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'], $credits['other'] );
490
491 $out .= Xml::closeElement( 'table' );
492
493 return $out;
494 }
495
502 private function getSkinCredits( array $credits ) {
503 if ( !isset( $credits['skin'] ) || count( $credits['skin'] ) === 0 ) {
504 return '';
505 }
506
507 $out = Xml::element(
508 'h2',
509 [ 'id' => 'mw-version-skin' ],
510 $this->msg( 'version-skins' )->text()
511 ) .
512 Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ] );
513
514 $this->firstExtOpened = false;
515 $out .= $this->getExtensionCategory( 'skin', null, $credits['skin'] );
516
517 $out .= Xml::closeElement( 'table' );
518
519 return $out;
520 }
521
528 protected function getExternalLibraries( array $credits ) {
529 global $IP;
530 $paths = [
531 "$IP/vendor/composer/installed.json"
532 ];
533
534 $extensionTypes = self::getExtensionTypes();
535 foreach ( $extensionTypes as $type => $message ) {
536 if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
537 continue;
538 }
539 foreach ( $credits[$type] as $extension ) {
540 if ( !isset( $extension['path'] ) ) {
541 continue;
542 }
543 $paths[] = dirname( $extension['path'] ) . '/vendor/composer/installed.json';
544 }
545 }
546
547 $dependencies = [];
548
549 foreach ( $paths as $path ) {
550 if ( !file_exists( $path ) ) {
551 continue;
552 }
553
554 $installed = new ComposerInstalled( $path );
555
556 $dependencies += $installed->getInstalledDependencies();
557 }
558
559 if ( $dependencies === [] ) {
560 return '';
561 }
562
563 ksort( $dependencies );
564
565 $out = Html::element(
566 'h2',
567 [ 'id' => 'mw-version-libraries' ],
568 $this->msg( 'version-libraries' )->text()
569 );
570 $out .= Html::openElement(
571 'table',
572 [ 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' ]
573 );
574 $out .= Html::openElement( 'tr' )
575 . Html::element( 'th', [], $this->msg( 'version-libraries-library' )->text() )
576 . Html::element( 'th', [], $this->msg( 'version-libraries-version' )->text() )
577 . Html::element( 'th', [], $this->msg( 'version-libraries-license' )->text() )
578 . Html::element( 'th', [], $this->msg( 'version-libraries-description' )->text() )
579 . Html::element( 'th', [], $this->msg( 'version-libraries-authors' )->text() )
580 . Html::closeElement( 'tr' );
581
582 foreach ( $dependencies as $name => $info ) {
583 if ( !is_array( $info ) || strpos( $info['type'], 'mediawiki-' ) === 0 ) {
584 // Skip any extensions or skins since they'll be listed
585 // in their proper section
586 continue;
587 }
588 $authors = array_map( function ( $arr ) {
589 // If a homepage is set, link to it
590 if ( isset( $arr['homepage'] ) ) {
591 return "[{$arr['homepage']} {$arr['name']}]";
592 }
593 return $arr['name'];
594 }, $info['authors'] );
595 $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
596
597 // We can safely assume that the libraries' names and descriptions
598 // are written in English and aren't going to be translated,
599 // so set appropriate lang and dir attributes
600 $out .= Html::openElement( 'tr', [
601 // Add an anchor so docs can link easily to the version of
602 // this specific library
603 'id' => Sanitizer::escapeIdForAttribute(
604 "mw-version-library-$name"
605 ) ] )
606 . Html::rawElement(
607 'td',
608 [],
610 "https://packagist.org/packages/$name", $name,
611 true, '',
612 [ 'class' => 'mw-version-library-name' ]
613 )
614 )
615 . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
616 . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['licenses'] ) )
617 . Html::element( 'td', [ 'lang' => 'en', 'dir' => 'ltr' ], $info['description'] )
618 . Html::rawElement( 'td', [], $authors )
619 . Html::closeElement( 'tr' );
620 }
621 $out .= Html::closeElement( 'table' );
622
623 return $out;
624 }
625
631 protected function getParserTags() {
632 $tags = MediaWikiServices::getInstance()->getParser()->getTags();
633
634 if ( count( $tags ) ) {
635 $out = Html::rawElement(
636 'h2',
637 [
638 'class' => 'mw-headline plainlinks',
639 'id' => 'mw-version-parser-extensiontags',
640 ],
642 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
643 $this->msg( 'version-parser-extensiontags' )->parse(),
644 false /* msg()->parse() already escapes */
645 )
646 );
647
648 array_walk( $tags, function ( &$value ) {
649 // Bidirectional isolation improves readability in RTL wikis
650 $value = Html::element(
651 'bdi',
652 // Prevent < and > from slipping to another line
653 [
654 'style' => 'white-space: nowrap;',
655 ],
656 "<$value>"
657 );
658 } );
659
660 $out .= $this->listToText( $tags );
661 } else {
662 $out = '';
663 }
664
665 return $out;
666 }
667
673 protected function getParserFunctionHooks() {
674 $fhooks = MediaWikiServices::getInstance()->getParser()->getFunctionHooks();
675 if ( count( $fhooks ) ) {
676 $out = Html::rawElement(
677 'h2',
678 [
679 'class' => 'mw-headline plainlinks',
680 'id' => 'mw-version-parser-function-hooks',
681 ],
683 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
684 $this->msg( 'version-parser-function-hooks' )->parse(),
685 false /* msg()->parse() already escapes */
686 )
687 );
688
689 $out .= $this->listToText( $fhooks );
690 } else {
691 $out = '';
692 }
693
694 return $out;
695 }
696
706 protected function getExtensionCategory( $type, $message, array $creditsGroup ) {
707 $config = $this->getConfig();
708 $credits = $config->get( 'ExtensionCredits' );
709
710 $out = '';
711
712 if ( $creditsGroup ) {
713 $out .= $this->openExtType( $message, 'credits-' . $type );
714
715 usort( $creditsGroup, [ $this, 'compare' ] );
716
717 foreach ( $creditsGroup as $extension ) {
718 $out .= $this->getCreditsForExtension( $type, $extension );
719 }
720 }
721
722 return $out;
723 }
724
731 public function compare( $a, $b ) {
732 return $this->getLanguage()->lc( $a['name'] ) <=> $this->getLanguage()->lc( $b['name'] );
733 }
734
753 public function getCreditsForExtension( $type, array $extension ) {
754 $out = $this->getOutput();
755
756 // We must obtain the information for all the bits and pieces!
757 // ... such as extension names and links
758 if ( isset( $extension['namemsg'] ) ) {
759 // Localized name of extension
760 $extensionName = $this->msg( $extension['namemsg'] )->text();
761 } elseif ( isset( $extension['name'] ) ) {
762 // Non localized version
763 $extensionName = $extension['name'];
764 } else {
765 $extensionName = $this->msg( 'version-no-ext-name' )->text();
766 }
767
768 if ( isset( $extension['url'] ) ) {
769 $extensionNameLink = Linker::makeExternalLink(
770 $extension['url'],
771 $extensionName,
772 true,
773 '',
774 [ 'class' => 'mw-version-ext-name' ]
775 );
776 } else {
777 $extensionNameLink = htmlspecialchars( $extensionName );
778 }
779
780 // ... and the version information
781 // If the extension path is set we will check that directory for GIT
782 // metadata in an attempt to extract date and vcs commit metadata.
783 $canonicalVersion = '&ndash;';
784 $extensionPath = null;
785 $vcsVersion = null;
786 $vcsLink = null;
787 $vcsDate = null;
788
789 if ( isset( $extension['version'] ) ) {
790 $canonicalVersion = $out->parseInlineAsInterface( $extension['version'] );
791 }
792
793 if ( isset( $extension['path'] ) ) {
794 global $IP;
795 $extensionPath = dirname( $extension['path'] );
796 if ( $this->coreId == '' ) {
797 wfDebug( 'Looking up core head id' );
798 $coreHeadSHA1 = self::getGitHeadSha1( $IP );
799 if ( $coreHeadSHA1 ) {
800 $this->coreId = $coreHeadSHA1;
801 }
802 }
804 $memcKey = $cache->makeKey(
805 'specialversion-ext-version-text', $extension['path'], $this->coreId
806 );
807 list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
808
809 if ( !$vcsVersion ) {
810 wfDebug( "Getting VCS info for extension {$extension['name']}" );
811 $gitInfo = new GitInfo( $extensionPath );
812 $vcsVersion = $gitInfo->getHeadSHA1();
813 if ( $vcsVersion !== false ) {
814 $vcsVersion = substr( $vcsVersion, 0, 7 );
815 $vcsLink = $gitInfo->getHeadViewUrl();
816 $vcsDate = $gitInfo->getHeadCommitDate();
817 }
818 $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
819 } else {
820 wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
821 }
822 }
823
824 $versionString = Html::rawElement(
825 'span',
826 [ 'class' => 'mw-version-ext-version' ],
827 $canonicalVersion
828 );
829
830 if ( $vcsVersion ) {
831 if ( $vcsLink ) {
832 $vcsVerString = Linker::makeExternalLink(
833 $vcsLink,
834 $this->msg( 'version-version', $vcsVersion ),
835 true,
836 '',
837 [ 'class' => 'mw-version-ext-vcs-version' ]
838 );
839 } else {
840 $vcsVerString = Html::element( 'span',
841 [ 'class' => 'mw-version-ext-vcs-version' ],
842 "({$vcsVersion})"
843 );
844 }
845 $versionString .= " {$vcsVerString}";
846
847 if ( $vcsDate ) {
848 $versionString .= ' ' . Html::element( 'span', [
849 'class' => 'mw-version-ext-vcs-timestamp',
850 'dir' => $this->getLanguage()->getDir(),
851 ], $this->getLanguage()->timeanddate( $vcsDate, true ) );
852 }
853 $versionString = Html::rawElement( 'span',
854 [ 'class' => 'mw-version-ext-meta-version' ],
855 $versionString
856 );
857 }
858
859 // ... and license information; if a license file exists we
860 // will link to it
861 $licenseLink = '';
862 if ( isset( $extension['name'] ) ) {
863 $licenseName = null;
864 if ( isset( $extension['license-name'] ) ) {
865 $licenseName = new HtmlArmor( $out->parseInlineAsInterface( $extension['license-name'] ) );
866 } elseif ( ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
867 $licenseName = $this->msg( 'version-ext-license' )->text();
868 }
869 if ( $licenseName !== null ) {
870 $licenseLink = $this->getLinkRenderer()->makeLink(
871 $this->getPageTitle( 'License/' . $extension['name'] ),
872 $licenseName,
873 [
874 'class' => 'mw-version-ext-license',
875 'dir' => 'auto',
876 ]
877 );
878 }
879 }
880
881 // ... and generate the description; which can be a parameterized l10n message
882 // in the form [ <msgname>, <parameter>, <parameter>... ] or just a straight
883 // up string
884 if ( isset( $extension['descriptionmsg'] ) ) {
885 // Localized description of extension
886 $descriptionMsg = $extension['descriptionmsg'];
887
888 if ( is_array( $descriptionMsg ) ) {
889 $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
890 array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
891 array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
892 $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
893 } else {
894 $description = $this->msg( $descriptionMsg )->text();
895 }
896 } elseif ( isset( $extension['description'] ) ) {
897 // Non localized version
898 $description = $extension['description'];
899 } else {
900 $description = '';
901 }
902 $description = $out->parseInlineAsInterface( $description );
903
904 // ... now get the authors for this extension
905 $authors = $extension['author'] ?? [];
906 $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
907
908 // Finally! Create the table
909 $html = Html::openElement( 'tr', [
910 'class' => 'mw-version-ext',
911 'id' => Sanitizer::escapeIdForAttribute( 'mw-version-ext-' . $type . '-' . $extension['name'] )
912 ]
913 );
914
915 $html .= Html::rawElement( 'td', [], $extensionNameLink );
916 $html .= Html::rawElement( 'td', [], $versionString );
917 $html .= Html::rawElement( 'td', [], $licenseLink );
918 $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-description' ], $description );
919 $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-authors' ], $authors );
920
921 $html .= Html::closeElement( 'tr' );
922
923 return $html;
924 }
925
931 private function getWgHooks() {
933
934 if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
935 $myWgHooks = $wgHooks;
936 ksort( $myWgHooks );
937
938 $ret = [];
939 $ret[] = '== {{int:version-hooks}} ==';
940 $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
941 $ret[] = Html::openElement( 'tr' );
942 $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
943 $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
944 $ret[] = Html::closeElement( 'tr' );
945
946 foreach ( $myWgHooks as $hook => $hooks ) {
947 $ret[] = Html::openElement( 'tr' );
948 $ret[] = Html::element( 'td', [], $hook );
949 $ret[] = Html::element( 'td', [], $this->listToText( $hooks ) );
950 $ret[] = Html::closeElement( 'tr' );
951 }
952
953 $ret[] = Html::closeElement( 'table' );
954
955 return implode( "\n", $ret );
956 }
957
958 return '';
959 }
960
961 private function openExtType( $text = null, $name = null ) {
962 $out = '';
963
964 $opt = [ 'colspan' => 5 ];
965 if ( $this->firstExtOpened ) {
966 // Insert a spacing line
967 $out .= Html::rawElement( 'tr', [ 'class' => 'sv-space' ],
968 Html::element( 'td', $opt )
969 );
970 }
971 $this->firstExtOpened = true;
972
973 if ( $name ) {
974 $opt['id'] = "sv-$name";
975 }
976
977 if ( $text !== null ) {
978 $out .= Html::rawElement( 'tr', [],
979 Html::element( 'th', $opt, $text )
980 );
981 }
982
983 $firstHeadingMsg = ( $name === 'credits-skin' )
984 ? 'version-skin-colheader-name'
985 : 'version-ext-colheader-name';
986 $out .= Html::openElement( 'tr' );
987 $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
988 $this->msg( $firstHeadingMsg )->text() );
989 $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
990 $this->msg( 'version-ext-colheader-version' )->text() );
991 $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
992 $this->msg( 'version-ext-colheader-license' )->text() );
993 $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
994 $this->msg( 'version-ext-colheader-description' )->text() );
995 $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
996 $this->msg( 'version-ext-colheader-credits' )->text() );
997 $out .= Html::closeElement( 'tr' );
998
999 return $out;
1000 }
1001
1007 private function IPInfo() {
1008 $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
1009
1010 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1011 }
1012
1034 public function listAuthors( $authors, $extName, $extDir ) {
1035 $hasOthers = false;
1036 $linkRenderer = $this->getLinkRenderer();
1037
1038 $list = [];
1039 $authors = (array)$authors;
1040
1041 // Special case: if the authors array has only one item and it is "...",
1042 // it should not be rendered as the "version-poweredby-others" i18n msg,
1043 // but rather as "version-poweredby-various" i18n msg instead.
1044 if ( count( $authors ) === 1 && $authors[0] === '...' ) {
1045 // Link to the extension's or skin's AUTHORS or CREDITS file, if there is
1046 // such a file; otherwise just return the i18n msg as-is
1047 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1048 return $linkRenderer->makeLink(
1049 $this->getPageTitle( "Credits/$extName" ),
1050 $this->msg( 'version-poweredby-various' )->text()
1051 );
1052 } else {
1053 return $this->msg( 'version-poweredby-various' )->escaped();
1054 }
1055 }
1056
1057 // Otherwise, if we have an actual array that has more than one item,
1058 // process each array item as usual
1059 foreach ( $authors as $item ) {
1060 if ( $item == '...' ) {
1061 $hasOthers = true;
1062
1063 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1064 $text = $linkRenderer->makeLink(
1065 $this->getPageTitle( "Credits/$extName" ),
1066 $this->msg( 'version-poweredby-others' )->text()
1067 );
1068 } else {
1069 $text = $this->msg( 'version-poweredby-others' )->escaped();
1070 }
1071 $list[] = $text;
1072 } elseif ( substr( $item, -5 ) == ' ...]' ) {
1073 $hasOthers = true;
1074 $list[] = $this->getOutput()->parseInlineAsInterface(
1075 substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
1076 );
1077 } else {
1078 $list[] = $this->getOutput()->parseInlineAsInterface( $item );
1079 }
1080 }
1081
1082 if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1083 $list[] = $text = $linkRenderer->makeLink(
1084 $this->getPageTitle( "Credits/$extName" ),
1085 $this->msg( 'version-poweredby-others' )->text()
1086 );
1087 }
1088
1089 return $this->listToText( $list, false );
1090 }
1091
1104 public static function getExtAuthorsFileName( $extDir ) {
1105 wfDeprecated( __METHOD__, '1.35' );
1106 return ExtensionInfo::getAuthorsFileName( $extDir );
1107 }
1108
1121 public static function getExtLicenseFileName( $extDir ) {
1122 wfDeprecated( __METHOD__, '1.35' );
1123 $licenses = ExtensionInfo::getLicenseFileNames( $extDir );
1124 if ( count( $licenses ) === 0 ) {
1125 return false;
1126 }
1127 return $licenses[0];
1128 }
1129
1138 public function listToText( $list, $sort = true ) {
1139 if ( !count( $list ) ) {
1140 return '';
1141 }
1142 if ( $sort ) {
1143 sort( $list );
1144 }
1145
1146 return $this->getLanguage()
1147 ->listToText( array_map( [ __CLASS__, 'arrayToString' ], $list ) );
1148 }
1149
1158 public static function arrayToString( $list ) {
1159 if ( is_array( $list ) && count( $list ) == 1 ) {
1160 $list = $list[0];
1161 }
1162 if ( $list instanceof Closure ) {
1163 // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
1164 return 'Closure';
1165 } elseif ( is_object( $list ) ) {
1166 $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
1167
1168 return $class;
1169 } elseif ( !is_array( $list ) ) {
1170 return $list;
1171 } else {
1172 if ( is_object( $list[0] ) ) {
1173 $class = get_class( $list[0] );
1174 } else {
1175 $class = $list[0];
1176 }
1177
1178 return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
1179 }
1180 }
1181
1186 public static function getGitHeadSha1( $dir ) {
1187 $repo = new GitInfo( $dir );
1188
1189 return $repo->getHeadSHA1();
1190 }
1191
1196 public static function getGitCurrentBranch( $dir ) {
1197 $repo = new GitInfo( $dir );
1198 return $repo->getCurrentBranch();
1199 }
1200
1205 public function getEntryPointInfo() {
1206 $config = $this->getConfig();
1207 $scriptPath = $config->get( 'ScriptPath' ) ?: '/';
1208
1209 $entryPoints = [
1210 'version-entrypoints-articlepath' => $config->get( 'ArticlePath' ),
1211 'version-entrypoints-scriptpath' => $scriptPath,
1212 'version-entrypoints-index-php' => wfScript( 'index' ),
1213 'version-entrypoints-api-php' => wfScript( 'api' ),
1214 'version-entrypoints-rest-php' => wfScript( 'rest' ),
1215 ];
1216
1217 $language = $this->getLanguage();
1218 $thAttribures = [
1219 'dir' => $language->getDir(),
1220 'lang' => $language->getHtmlCode()
1221 ];
1222 $out = Html::element(
1223 'h2',
1224 [ 'id' => 'mw-version-entrypoints' ],
1225 $this->msg( 'version-entrypoints' )->text()
1226 ) .
1227 Html::openElement( 'table',
1228 [
1229 'class' => 'wikitable plainlinks',
1230 'id' => 'mw-version-entrypoints-table',
1231 'dir' => 'ltr',
1232 'lang' => 'en'
1233 ]
1234 ) .
1235 Html::openElement( 'tr' ) .
1236 Html::element(
1237 'th',
1238 $thAttribures,
1239 $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1240 ) .
1241 Html::element(
1242 'th',
1243 $thAttribures,
1244 $this->msg( 'version-entrypoints-header-url' )->text()
1245 ) .
1246 Html::closeElement( 'tr' );
1247
1248 foreach ( $entryPoints as $message => $value ) {
1249 $url = wfExpandUrl( $value, PROTO_RELATIVE );
1250 $out .= Html::openElement( 'tr' ) .
1251 // ->plain() looks like it should be ->parse(), but this function
1252 // returns wikitext, not HTML, boo
1253 Html::rawElement( 'td', [], $this->msg( $message )->plain() ) .
1254 Html::rawElement( 'td', [], Html::rawElement( 'code', [], "[$url $value]" ) ) .
1255 Html::closeElement( 'tr' );
1256 }
1257
1258 $out .= Html::closeElement( 'table' );
1259
1260 return $out;
1261 }
1262
1263 protected function getGroupName() {
1264 return 'wiki';
1265 }
1266}
$wgHooks
Global list of hooks.
$wgSpecialVersionShowHooks
Show the contents of $wgHooks in Special:Version.
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:40
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfGetCache( $cacheType)
Get a specific cache object.
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
$wgLang
Definition Setup.php:781
$IP
Definition WebStart.php:49
Reads an installed.json file and provides accessors to get what is installed.
ExtensionRegistry class.
getAllThings()
Get credits information about all installed extensions and skins.
@newable
Definition GitInfo.php:34
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:856
MediaWikiServices is the service locator for the application scope of MediaWiki.
Parent class for all special pages.
Give information about the version of MediaWiki, PHP, the DB and extensions.
static getVersionLinked()
Return a wikitext-formatted string of the MediaWiki version with a link to the Git SHA1 of head if av...
static getExtensionTypeName( $type)
Returns the internationalized name for an extension type.
IPInfo()
Get information about client's IP address.
static getGitHeadSha1( $dir)
getParserFunctionHooks()
Obtains a list of installed parser function hooks and the associated H2 header.
execute( $par)
main()
getExternalLibraries(array $credits)
Generate an HTML table for external libraries that are installed.
listToText( $list, $sort=true)
Convert an array of items into a list for display.
getEntryPointInfo()
Get the list of entry points and their URLs.
getWgHooks()
Generate wikitext showing hooks in $wgHooks.
static getCredits(ExtensionRegistry $reg, Config $conf)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getExtensionCategory( $type, $message, array $creditsGroup)
Creates and returns the HTML for a single extension category.
getExtensionCredits(array $credits)
Generate wikitext showing the name, URL, author and description of each extension.
listAuthors( $authors, $extName, $extDir)
Return a formatted unsorted list of authors.
static getSoftwareInformation()
string $coreId
The current rev id/SHA hash of MediaWiki core.
static softwareInformation()
Returns HTML showing the third party software versions (apache, php, mysql).
static getVersionLinkedGit()
static getExtensionTypes()
Returns an array with the base extension types.
openExtType( $text=null, $name=null)
getCreditsForExtension( $type, array $extension)
Creates and formats a version line for a single extension.
static getExtLicenseFileName( $extDir)
Obtains the full path of an extensions COPYING or LICENSE file if one exists.
static getMediaWikiCredits()
Returns wiki text showing the license information.
getParserTags()
Obtains a list of installed parser tags and the associated H2 header.
static getExtAuthorsFileName( $extDir)
Obtains the full path of an extensions AUTHORS or CREDITS file if one exists.
compare( $a, $b)
Callback to sort extensions by type.
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
static getCopyrightAndAuthorList()
Get the "MediaWiki is copyright 2001-20xx by lots of cool folks" text.
static getGitCurrentBranch( $dir)
static string[] false $extensionTypes
Lazy initialized key/value with message content.
static getMWVersionLinked()
static arrayToString( $list)
Convert an array or object to a string for display.
getSkinCredits(array $credits)
Generate wikitext showing the name, URL, author and description of each skin.
const CACHE_ANYTHING
Definition Defines.php:91
const PROTO_RELATIVE
Definition Defines.php:211
Interface for configuration instances.
Definition Config.php:30
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!is_readable( $file)) $ext
Definition router.php:48
if(!isset( $args[0])) $lang