MediaWiki 1.44.0
SpecialVersion.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Specials;
24
25use Closure;
42use Symfony\Component\Yaml\Yaml;
45use Wikimedia\Parsoid\Core\SectionMetadata;
46use Wikimedia\Parsoid\Core\TOCData;
48
55
59 protected $coreId = '';
60
64 protected static $extensionTypes = false;
65
67 protected $tocData;
68
70 protected $tocIndex;
71
73 protected $tocSection;
74
76 protected $tocSubSection;
77
78 private ParserFactory $parserFactory;
79 private UrlUtils $urlUtils;
80 private IConnectionProvider $dbProvider;
81
82 public function __construct(
83 ParserFactory $parserFactory,
84 UrlUtils $urlUtils,
85 IConnectionProvider $dbProvider
86 ) {
87 parent::__construct( 'Version' );
88 $this->parserFactory = $parserFactory;
89 $this->urlUtils = $urlUtils;
90 $this->dbProvider = $dbProvider;
91 }
92
100 public static function getCredits( ExtensionRegistry $reg, Config $conf ): array {
101 $credits = $conf->get( MainConfigNames::ExtensionCredits );
102 foreach ( $reg->getAllThings() as $credit ) {
103 $credits[$credit['type']][] = $credit;
104 }
105 return $credits;
106 }
107
111 public function execute( $par ) {
112 $config = $this->getConfig();
113 $credits = self::getCredits( ExtensionRegistry::getInstance(), $config );
114
115 $this->setHeaders();
116 $this->outputHeader();
117 $out = $this->getOutput();
118 $out->getMetadata()->setPreventClickjacking( false );
119
120 // Explode the subpage information into useful bits
121 $parts = explode( '/', (string)$par );
122 $extNode = null;
123 if ( isset( $parts[1] ) ) {
124 $extName = str_replace( '_', ' ', $parts[1] );
125 // Find it!
126 foreach ( $credits as $extensions ) {
127 foreach ( $extensions as $ext ) {
128 if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
129 $extNode = &$ext;
130 break 2;
131 }
132 }
133 }
134 if ( !$extNode ) {
135 $out->setStatusCode( 404 );
136 }
137 } else {
138 $extName = 'MediaWiki';
139 }
140
141 // Now figure out what to do
142 switch ( strtolower( $parts[0] ) ) {
143 case 'credits':
144 $out->addModuleStyles( 'mediawiki.special' );
145
146 $wikiText = '{{int:version-credits-not-found}}';
147 if ( $extName === 'MediaWiki' ) {
148 $wikiText = file_get_contents( MW_INSTALL_PATH . '/CREDITS' );
149 // Put the contributor list into columns
150 $wikiText = str_replace(
151 [ '<!-- BEGIN CONTRIBUTOR LIST -->', '<!-- END CONTRIBUTOR LIST -->' ],
152 [ '<div class="mw-version-credits">', '</div>' ],
153 $wikiText
154 );
155 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
156 $file = ExtensionInfo::getAuthorsFileName( dirname( $extNode['path'] ) );
157 if ( $file ) {
158 $wikiText = file_get_contents( $file );
159 if ( str_ends_with( $file, '.txt' ) ) {
160 $wikiText = Html::element(
161 'pre',
162 [
163 'lang' => 'en',
164 'dir' => 'ltr',
165 ],
166 $wikiText
167 );
168 }
169 }
170 }
171
172 $out->setPageTitleMsg( $this->msg( 'version-credits-title' )->plaintextParams( $extName ) );
173 $out->addWikiTextAsInterface( $wikiText );
174 break;
175
176 case 'license':
177 $out->setPageTitleMsg( $this->msg( 'version-license-title' )->plaintextParams( $extName ) );
178
179 $licenseFound = false;
180
181 if ( $extName === 'MediaWiki' ) {
182 $out->addWikiTextAsInterface(
183 file_get_contents( MW_INSTALL_PATH . '/COPYING' )
184 );
185 $licenseFound = true;
186 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
187 $files = ExtensionInfo::getLicenseFileNames( dirname( $extNode['path'] ) );
188 if ( $files ) {
189 $licenseFound = true;
190 foreach ( $files as $file ) {
191 $out->addWikiTextAsInterface(
193 'pre',
194 [
195 'lang' => 'en',
196 'dir' => 'ltr',
197 ],
198 file_get_contents( $file )
199 )
200 );
201 }
202 }
203 }
204 if ( !$licenseFound ) {
205 $out->addWikiTextAsInterface( '{{int:version-license-not-found}}' );
206 }
207 break;
208
209 default:
210 $out->addModuleStyles( 'mediawiki.special' );
211
212 $out->addHTML( $this->getMediaWikiCredits() );
213
214 $this->tocData = new TOCData();
215 $this->tocIndex = 0;
216 $this->tocSection = 0;
217 $this->tocSubSection = 0;
218
219 // Build the page contents (this also fills in TOCData)
220 $sections = [
221 $this->softwareInformation(),
222 $this->getEntryPointInfo(),
223 $this->getSkinCredits( $credits ),
224 $this->getExtensionCredits( $credits ),
225 $this->getLibraries( $credits ),
226 $this->getParserTags(),
227 $this->getParserFunctionHooks(),
228 $this->getParsoidModules(),
229 $this->getHooks(),
230 $this->IPInfo(),
231 ];
232
233 // Insert TOC first
234 $out->addTOCPlaceholder( $this->tocData );
235
236 // Insert contents
237 foreach ( $sections as $content ) {
238 $out->addHTML( $content );
239 }
240
241 break;
242 }
243 }
244
252 private function addTocSection( $labelMsg, $id ) {
253 $this->tocIndex++;
254 $this->tocSection++;
255 $this->tocSubSection = 0;
256 $this->tocData->addSection( new SectionMetadata(
257 1,
258 2,
259 $this->msg( $labelMsg )->escaped(),
260 $this->getLanguage()->formatNum( $this->tocSection ),
261 (string)$this->tocIndex,
262 null,
263 null,
264 $id,
265 $id
266 ) );
267 }
268
276 private function addTocSubSection( $label, $id ) {
277 $this->tocIndex++;
278 $this->tocSubSection++;
279 $this->tocData->addSection( new SectionMetadata(
280 2,
281 3,
282 htmlspecialchars( $label ),
283 // See Parser::localizeTOC
284 $this->getLanguage()->formatNum( $this->tocSection ) . '.' .
285 $this->getLanguage()->formatNum( $this->tocSubSection ),
286 (string)$this->tocIndex,
287 null,
288 null,
289 $id,
290 $id
291 ) );
292 }
293
299 private function getMediaWikiCredits() {
300 // No TOC entry for this heading, we treat it like the lede section
301
302 $ret = Html::element(
303 'h2',
304 [ 'id' => 'mw-version-license' ],
305 $this->msg( 'version-license' )->text()
306 );
307
308 $ret .= Html::rawElement( 'div', [ 'class' => 'plainlinks' ],
309 $this->msg( new RawMessage( self::getCopyrightAndAuthorList() ) )->parseAsBlock() .
310 Html::rawElement( 'div', [ 'class' => 'mw-version-license-info' ],
311 $this->msg( 'version-license-info' )->parseAsBlock()
312 )
313 );
314
315 return $ret;
316 }
317
324 public static function getCopyrightAndAuthorList() {
325 if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
326 $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
327 wfMessage( 'version-poweredby-others' )->plain() . ']';
328 } else {
329 $othersLink = '[[Special:Version/Credits|' .
330 wfMessage( 'version-poweredby-others' )->plain() . ']]';
331 }
332
333 $translatorsLink = '[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
334 wfMessage( 'version-poweredby-translators' )->plain() . ']';
335
336 $authorList = [
337 'Magnus Manske', 'Brooke Vibber', 'Lee Daniel Crocker',
338 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
339 'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
340 'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
341 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
342 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
343 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
344 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
345 'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
346 'Kunal Mehta', 'James D. Forrester', 'Brian Wolff', 'Adam Shorland',
347 'DannyS712', 'Ori Livneh', 'Max Semenik', 'Amir Sarabadani',
348 'Derk-Jan Hartman', 'Petr Pchelko', 'Umherirrender', 'C. Scott Ananian',
349 'fomafix', 'Thiemo Kreuz', 'Gergő Tisza', 'Volker E.',
350 'Jack Phoenix', 'Isarra Yos',
351 $othersLink, $translatorsLink
352 ];
353
354 return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
355 Message::listParam( $authorList ) )->plain();
356 }
357
363 private function getSoftwareInformation() {
364 $dbr = $this->dbProvider->getReplicaDatabase();
365
366 // Put the software in an array of form 'name' => 'version'. All messages should
367 // be loaded here, so feel free to use wfMessage in the 'name'. Wikitext
368 // can be used both in the name and value.
369 $software = [
370 '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked(),
371 '[https://php.net/ PHP]' => PHP_VERSION . " (" . PHP_SAPI . ")",
372 '[https://icu.unicode.org/ ICU]' => INTL_ICU_VERSION,
373 $dbr->getSoftwareLink() => $dbr->getServerInfo(),
374 ];
375
376 // T339915: If wikidiff2 is installed, show version
377 if ( phpversion( "wikidiff2" ) ) {
378 $software[ '[https://www.mediawiki.org/wiki/Wikidiff2 wikidiff2]' ] = phpversion( "wikidiff2" );
379 }
380
381 // Allow a hook to add/remove items.
382 $this->getHookRunner()->onSoftwareInfo( $software );
383
384 return $software;
385 }
386
392 private function softwareInformation() {
393 $this->addTocSection( 'version-software', 'mw-version-software' );
394
395 $out = Html::element(
396 'h2',
397 [ 'id' => 'mw-version-software' ],
398 $this->msg( 'version-software' )->text()
399 );
400
401 $out .= Html::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] );
402
403 $out .= $this->getTableHeaderHtml( [
404 $this->msg( 'version-software-product' )->text(),
405 $this->msg( 'version-software-version' )->text()
406 ] );
407
408 foreach ( $this->getSoftwareInformation() as $name => $version ) {
409 $out .= Html::rawElement(
410 'tr',
411 [],
412 Html::rawElement( 'td', [], $this->msg( new RawMessage( $name ) )->parse() ) .
413 Html::rawElement( 'td', [ 'dir' => 'ltr' ], $this->msg( new RawMessage( $version ) )->parse() )
414 );
415 }
416
417 $out .= Html::closeElement( 'table' );
418
419 return $out;
420 }
421
431 public static function getVersion( $flags = '', $lang = null ) {
432 $gitInfo = GitInfo::repo()->getHeadSHA1();
433 if ( !$gitInfo ) {
434 $version = MW_VERSION;
435 } elseif ( $flags === 'nodb' ) {
436 $shortSha1 = substr( $gitInfo, 0, 7 );
437 $version = MW_VERSION . " ($shortSha1)";
438 } else {
439 $shortSha1 = substr( $gitInfo, 0, 7 );
440 $msg = wfMessage( 'parentheses' );
441 if ( $lang !== null ) {
442 $msg->inLanguage( $lang );
443 }
444 $shortSha1 = $msg->params( $shortSha1 )->text();
445 $version = MW_VERSION . ' ' . $shortSha1;
446 }
447
448 return $version;
449 }
450
458 public static function getVersionLinked() {
459 return self::getVersionLinkedGit() ?: MW_VERSION;
460 }
461
465 private static function getMWVersionLinked() {
466 $versionUrl = "";
467 $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
468 if ( $hookRunner->onSpecialVersionVersionUrl( MW_VERSION, $versionUrl ) ) {
469 $versionParts = [];
470 preg_match( "/^(\d+\.\d+)/", MW_VERSION, $versionParts );
471 $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
472 }
473
474 return '[' . $versionUrl . ' ' . MW_VERSION . ']';
475 }
476
482 private static function getVersionLinkedGit() {
483 global $wgLang;
484
485 $gitInfo = new GitInfo( MW_INSTALL_PATH );
486 $headSHA1 = $gitInfo->getHeadSHA1();
487 if ( !$headSHA1 ) {
488 return false;
489 }
490
491 $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
492
493 $gitHeadUrl = $gitInfo->getHeadViewUrl();
494 if ( $gitHeadUrl !== false ) {
495 $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
496 }
497
498 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
499 if ( $gitHeadCommitDate ) {
500 $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( (string)$gitHeadCommitDate, true );
501 }
502
503 return self::getMWVersionLinked() . " $shortSHA1";
504 }
505
515 public static function getExtensionTypes(): array {
516 if ( self::$extensionTypes === false ) {
517 self::$extensionTypes = [
518 'specialpage' => wfMessage( 'version-specialpages' )->text(),
519 'editor' => wfMessage( 'version-editors' )->text(),
520 'parserhook' => wfMessage( 'version-parserhooks' )->text(),
521 'variable' => wfMessage( 'version-variables' )->text(),
522 'media' => wfMessage( 'version-mediahandlers' )->text(),
523 'antispam' => wfMessage( 'version-antispam' )->text(),
524 'skin' => wfMessage( 'version-skins' )->text(),
525 'api' => wfMessage( 'version-api' )->text(),
526 'other' => wfMessage( 'version-other' )->text(),
527 ];
528
529 ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
530 ->onExtensionTypes( self::$extensionTypes );
531 }
532
533 return self::$extensionTypes;
534 }
535
545 public static function getExtensionTypeName( $type ) {
546 $types = self::getExtensionTypes();
547
548 return $types[$type] ?? $types['other'];
549 }
550
557 private function getExtensionCredits( array $credits ) {
558 $extensionTypes = self::getExtensionTypes();
559
560 $this->addTocSection( 'version-extensions', 'mw-version-ext' );
561
562 $out = Html::element(
563 'h2',
564 [ 'id' => 'mw-version-ext' ],
565 $this->msg( 'version-extensions' )->text()
566 );
567
568 if (
569 !$credits ||
570 // Skins are displayed separately, see getSkinCredits()
571 ( count( $credits ) === 1 && isset( $credits['skin'] ) )
572 ) {
573 $out .= Html::element(
574 'p',
575 [],
576 $this->msg( 'version-extensions-no-ext' )->text()
577 );
578
579 return $out;
580 }
581
582 // Find all extensions that do not have a valid type and give them the type 'other'.
583 $credits['other'] ??= [];
584 foreach ( $credits as $type => $extensions ) {
585 if ( !array_key_exists( $type, $extensionTypes ) ) {
586 $credits['other'] = array_merge( $credits['other'], $extensions );
587 }
588 }
589
590 // Loop through the extension categories to display their extensions in the list.
591 foreach ( $extensionTypes as $type => $text ) {
592 // Skins have a separate section
593 if ( $type !== 'other' && $type !== 'skin' ) {
594 $out .= $this->getExtensionCategory( $type, $text, $credits[$type] ?? [] );
595 }
596 }
597
598 // We want the 'other' type to be last in the list.
599 $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'], $credits['other'] );
600
601 return $out;
602 }
603
610 private function getSkinCredits( array $credits ) {
611 $this->addTocSection( 'version-skins', 'mw-version-skin' );
612
613 $out = Html::element(
614 'h2',
615 [ 'id' => 'mw-version-skin' ],
616 $this->msg( 'version-skins' )->text()
617 );
618
619 if ( !isset( $credits['skin'] ) || !$credits['skin'] ) {
620 $out .= Html::element(
621 'p',
622 [],
623 $this->msg( 'version-skins-no-skin' )->text()
624 );
625
626 return $out;
627 }
628 $out .= $this->getExtensionCategory( 'skin', null, $credits['skin'] );
629
630 return $out;
631 }
632
639 protected function getLibraries( array $credits ) {
640 $this->addTocSection( 'version-libraries', 'mw-version-libraries' );
641
642 $out = Html::element(
643 'h2',
644 [ 'id' => 'mw-version-libraries' ],
645 $this->msg( 'version-libraries' )->text()
646 );
647
648 return $out
649 . $this->getExternalLibraries( $credits )
650 . $this->getClientSideLibraries();
651 }
652
659 public static function parseComposerInstalled( array $credits ) {
660 $paths = [
661 MW_INSTALL_PATH . '/vendor/composer/installed.json'
662 ];
663
664 $extensionTypes = self::getExtensionTypes();
665 foreach ( $extensionTypes as $type => $message ) {
666 if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
667 continue;
668 }
669 foreach ( $credits[$type] as $extension ) {
670 if ( !isset( $extension['path'] ) ) {
671 continue;
672 }
673 $paths[] = dirname( $extension['path'] ) . '/vendor/composer/installed.json';
674 }
675 }
676
677 $dependencies = [];
678
679 foreach ( $paths as $path ) {
680 if ( !file_exists( $path ) ) {
681 continue;
682 }
683
684 $installed = new ComposerInstalled( $path );
685
686 $dependencies += $installed->getInstalledDependencies();
687 }
688
689 ksort( $dependencies );
690 return $dependencies;
691 }
692
699 protected function getExternalLibraries( array $credits ) {
700 $dependencies = self::parseComposerInstalled( $credits );
701 if ( $dependencies === [] ) {
702 return '';
703 }
704
705 $this->addTocSubSection( $this->msg( 'version-libraries-server' )->text(), 'mw-version-libraries-server' );
706
707 $out = Html::element(
708 'h3',
709 [ 'id' => 'mw-version-libraries-server' ],
710 $this->msg( 'version-libraries-server' )->text()
711 );
712 $out .= Html::openElement(
713 'table',
714 [ 'class' => 'wikitable plainlinks mw-installed-software', 'id' => 'sv-libraries' ]
715 );
716
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(),
723 ] );
724
725 foreach ( $dependencies as $name => $info ) {
726 if ( !is_array( $info ) || str_starts_with( $info['type'], 'mediawiki-' ) ) {
727 // Skip any extensions or skins since they'll be listed
728 // in their proper section
729 continue;
730 }
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'] )
735 );
736 }, $info['authors'] );
737 $authors = $this->listAuthors( $authors, false, MW_INSTALL_PATH . "/vendor/$name" );
738
739 // We can safely assume that the libraries' names and descriptions
740 // are written in English and aren't going to be translated,
741 // so set appropriate lang and dir attributes
742 $out .= Html::openElement( 'tr', [
743 // Add an anchor so docs can link easily to the version of
744 // this specific library
745 'id' => Sanitizer::escapeIdForAttribute(
746 "mw-version-library-$name"
747 ) ] )
748 . Html::rawElement(
749 'td',
750 [],
751 $this->getLinkRenderer()->makeExternalLink(
752 "https://packagist.org/packages/$name",
753 $name,
754 $this->getFullTitle(),
755 '',
756 [ 'class' => 'mw-version-library-name' ]
757 )
758 )
759 . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
760 // @phan-suppress-next-line SecurityCheck-DoubleEscaped See FIXME in listToText
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' );
765 }
766 $out .= Html::closeElement( 'table' );
767
768 return $out;
769 }
770
776 public static function parseForeignResources() {
777 $registryDirs = [ 'MediaWiki' => MW_INSTALL_PATH . '/resources/lib' ]
778 + ExtensionRegistry::getInstance()->getAttribute( 'ForeignResourcesDir' );
779
780 $modules = [];
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'];
785 if ( isset( $modules[$key] ) ) {
786 $modules[$key]['source'][] = $source;
787 continue;
788 }
789 $modules[$key] = $module + [ 'name' => $name, 'source' => [ $source ] ];
790 }
791 }
792 ksort( $modules );
793 return $modules;
794 }
795
801 private function getClientSideLibraries() {
802 $this->addTocSubSection( $this->msg( 'version-libraries-client' )->text(), 'mw-version-libraries-client' );
803
804 $out = Html::element(
805 'h3',
806 [ 'id' => 'mw-version-libraries-client' ],
807 $this->msg( 'version-libraries-client' )->text()
808 );
809 $out .= Html::openElement(
810 'table',
811 [ 'class' => 'wikitable plainlinks mw-installed-software', 'id' => 'sv-libraries-client' ]
812 );
813
814 $out .= $this->getTableHeaderHtml( [
815 $this->msg( 'version-libraries-library' )->text(),
816 $this->msg( 'version-libraries-version' )->text(),
817 $this->msg( 'version-libraries-license' )->text(),
818 $this->msg( 'version-libraries-authors' )->text(),
819 $this->msg( 'version-libraries-source' )->text()
820 ] );
821
822 foreach ( self::parseForeignResources() as $name => $info ) {
823 // We can safely assume that the libraries' names and descriptions
824 // are written in English and aren't going to be translated,
825 // so set appropriate lang and dir attributes
826 $out .= Html::openElement( 'tr', [
827 // Add an anchor so docs can link easily to the version of
828 // this specific library
829 'id' => Sanitizer::escapeIdForAttribute(
830 "mw-version-library-$name"
831 ) ] )
832 . Html::rawElement(
833 'td',
834 [],
835 $this->getLinkRenderer()->makeExternalLink(
836 $info['homepage'],
837 $info['name'],
838 $this->getFullTitle(),
839 '',
840 [ 'class' => 'mw-version-library-name' ]
841 )
842 )
843 . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
844 . Html::element( 'td', [ 'dir' => 'auto' ], $info['license'] )
845 . Html::element( 'td', [ 'dir' => 'auto' ], $info['authors'] ?? '—' )
846 // @phan-suppress-next-line SecurityCheck-DoubleEscaped See FIXME in listToText
847 . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['source'] ) )
848 . Html::closeElement( 'tr' );
849 }
850 $out .= Html::closeElement( 'table' );
851
852 return $out;
853 }
854
860 protected function getParserTags() {
861 $tags = $this->parserFactory->getMainInstance()->getTags();
862 if ( !$tags ) {
863 return '';
864 }
865
866 $this->addTocSection( 'version-parser-extensiontags', 'mw-version-parser-extensiontags' );
867
868 $out = Html::rawElement(
869 'h2',
870 [ 'id' => 'mw-version-parser-extensiontags' ],
871 Html::rawElement(
872 'span',
873 [ 'class' => 'plainlinks' ],
874 $this->getLinkRenderer()->makeExternalLink(
875 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
876 $this->msg( 'version-parser-extensiontags' ),
877 $this->getFullTitle()
878 )
879 )
880 );
881
882 array_walk( $tags, static function ( &$value ) {
883 // Bidirectional isolation improves readability in RTL wikis
884 $value = Html::rawElement(
885 'bdi',
886 // Prevent < and > from slipping to another line
887 [
888 'style' => 'white-space: nowrap;',
889 ],
890 Html::element( 'code', [], "<$value>" )
891 );
892 } );
893
894 $out .= $this->listToText( $tags );
895
896 return $out;
897 }
898
904 protected function getParserFunctionHooks() {
905 $funcHooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
906 if ( !$funcHooks ) {
907 return '';
908 }
909
910 $this->addTocSection( 'version-parser-function-hooks', 'mw-version-parser-function-hooks' );
911
912 $out = Html::rawElement(
913 'h2',
914 [ 'id' => 'mw-version-parser-function-hooks' ],
915 Html::rawElement(
916 'span',
917 [ 'class' => 'plainlinks' ],
918 $this->getLinkRenderer()->makeExternalLink(
919 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
920 $this->msg( 'version-parser-function-hooks' ),
921 $this->getFullTitle()
922 )
923 )
924 );
925
926 $funcSynonyms = $this->parserFactory->getMainInstance()->getFunctionSynonyms();
927 // This will give us the preferred synonyms in the content language, as if
928 // we used MagicWord::getSynonym( 0 ), because they appear first in the arrays.
929 // We can't use MagicWord directly, because only Parser knows whether a function
930 // uses the leading "#" or not. Case-sensitive functions ("1") win over
931 // case-insensitive ones ("0"), like in Parser::callParserFunction().
932 // There should probably be a better API for this.
933 $preferredSynonyms = array_flip( array_reverse( $funcSynonyms[1] + $funcSynonyms[0] ) );
934 array_walk( $funcHooks, static function ( &$value ) use ( $preferredSynonyms ) {
935 $value = $preferredSynonyms[$value];
936 } );
937 $legacyHooks = array_flip( $funcHooks );
938
939 // Sort case-insensitively, ignoring the leading '#' if present
940 $cmpHooks = static function ( $a, $b ) {
941 return strcasecmp( ltrim( $a, '#' ), ltrim( $b, '#' ) );
942 };
943 usort( $funcHooks, $cmpHooks );
944
945 $formatHooks = static function ( &$value ) {
946 // Bidirectional isolation ensures it displays as {{#ns}} and not {{ns#}} in RTL wikis
947 $value = Html::rawElement(
948 'bdi',
949 [],
950 Html::element( 'code', [], '{{' . $value . '}}' )
951 );
952 };
953 array_walk( $funcHooks, $formatHooks );
954
955 $out .= $this->getLanguage()->listToText( $funcHooks );
956
957 # Get a list of parser functions from Parsoid as well.
958 $parsoidHooks = [];
959 $services = MediaWikiServices::getInstance();
960 $siteConfig = $services->getParsoidSiteConfig();
961 $magicWordFactory = $services->getMagicWordFactory();
962 foreach ( $siteConfig->getPFragmentHandlerKeys() as $key ) {
963 $config = $siteConfig->getPFragmentHandlerConfig( $key );
964 if ( !( $config['options']['parserFunction'] ?? false ) ) {
965 continue;
966 }
967 $mw = $magicWordFactory->get( $key );
968 foreach ( $mw->getSynonyms() as $local ) {
969 if ( !( $config['options']['nohash'] ?? false ) ) {
970 $local = '#' . $local;
971 }
972 // Skip hooks already present in legacy hooks (they will
973 // also work in parsoid)
974 if ( isset( $legacyHooks[$local] ) ) {
975 continue;
976 }
977 $parsoidHooks[] = $local;
978 }
979 }
980 if ( $parsoidHooks ) {
981 $out .= Html::element(
982 'h3',
983 [ 'id' => 'mw-version-parser-function-hooks-parsoid' ],
984 $this->msg( 'version-parser-function-hooks-parsoid' )->text()
985 );
986 usort( $parsoidHooks, $cmpHooks );
987 array_walk( $parsoidHooks, $formatHooks );
988 $out .= $this->getLanguage()->listToText( $parsoidHooks );
989 }
990
991 return $out;
992 }
993
999 protected function getParsoidModules() {
1000 $siteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
1001 $modules = $siteConfig->getExtensionModules();
1002
1003 if ( !$modules ) {
1004 return '';
1005 }
1006
1007 $this->addTocSection( 'version-parsoid-modules', 'mw-version-parsoid-modules' );
1008
1009 $out = Html::rawElement(
1010 'h2',
1011 [ 'id' => 'mw-version-parsoid-modules' ],
1012 Html::rawElement(
1013 'span',
1014 [ 'class' => 'plainlinks' ],
1015 $this->getLinkRenderer()->makeExternalLink(
1016 'https://www.mediawiki.org/wiki/Special:MyLanguage/Parsoid',
1017 $this->msg( 'version-parsoid-modules' ),
1018 $this->getFullTitle()
1019 )
1020 )
1021 );
1022
1023 $moduleNames = array_map(
1024 static fn ( $m )=>Html::element( 'code', [], $m->getConfig()['name'] ),
1025 $modules
1026 );
1027
1028 $out .= $this->getLanguage()->listToText( $moduleNames );
1029
1030 return $out;
1031 }
1032
1042 protected function getExtensionCategory( $type, ?string $text, array $creditsGroup ) {
1043 $out = '';
1044
1045 if ( $creditsGroup ) {
1046 $out .= $this->openExtType( $text, 'credits-' . $type );
1047
1048 usort( $creditsGroup, [ $this, 'compare' ] );
1049
1050 foreach ( $creditsGroup as $extension ) {
1051 $out .= $this->getCreditsForExtension( $type, $extension );
1052 }
1053
1054 $out .= Html::closeElement( 'table' );
1055 }
1056
1057 return $out;
1058 }
1059
1066 public function compare( $a, $b ) {
1067 return $this->getLanguage()->lc( $a['name'] ) <=> $this->getLanguage()->lc( $b['name'] );
1068 }
1069
1088 public function getCreditsForExtension( $type, array $extension ) {
1089 $out = $this->getOutput();
1090
1091 // We must obtain the information for all the bits and pieces!
1092 // ... such as extension names and links
1093 if ( isset( $extension['namemsg'] ) ) {
1094 // Localized name of extension
1095 $extensionName = $this->msg( $extension['namemsg'] );
1096 } elseif ( isset( $extension['name'] ) ) {
1097 // Non localized version
1098 $extensionName = $extension['name'];
1099 } else {
1100 $extensionName = $this->msg( 'version-no-ext-name' );
1101 }
1102
1103 if ( isset( $extension['url'] ) ) {
1104 $extensionNameLink = $this->getLinkRenderer()->makeExternalLink(
1105 $extension['url'],
1106 $extensionName,
1107 $this->getFullTitle(),
1108 '',
1109 [ 'class' => 'mw-version-ext-name' ]
1110 );
1111 } else {
1112 $extensionNameLink = htmlspecialchars( $extensionName );
1113 }
1114
1115 // ... and the version information
1116 // If the extension path is set we will check that directory for GIT
1117 // metadata in an attempt to extract date and vcs commit metadata.
1118 $canonicalVersion = '&ndash;';
1119 $extensionPath = null;
1120 $vcsVersion = null;
1121 $vcsLink = null;
1122 $vcsDate = null;
1123
1124 if ( isset( $extension['version'] ) ) {
1125 $canonicalVersion = $out->parseInlineAsInterface( $extension['version'] );
1126 }
1127
1128 if ( isset( $extension['path'] ) ) {
1129 $extensionPath = dirname( $extension['path'] );
1130 if ( $this->coreId == '' ) {
1131 wfDebug( 'Looking up core head id' );
1132 $coreHeadSHA1 = GitInfo::repo()->getHeadSHA1();
1133 if ( $coreHeadSHA1 ) {
1134 $this->coreId = $coreHeadSHA1;
1135 }
1136 }
1137 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance( CACHE_ANYTHING );
1138 $memcKey = $cache->makeKey(
1139 'specialversion-ext-version-text', $extension['path'], $this->coreId
1140 );
1141 [ $vcsVersion, $vcsLink, $vcsDate ] = $cache->get( $memcKey );
1142
1143 if ( !$vcsVersion ) {
1144 wfDebug( "Getting VCS info for extension {$extension['name']}" );
1145 $gitInfo = new GitInfo( $extensionPath );
1146 $vcsVersion = $gitInfo->getHeadSHA1();
1147 if ( $vcsVersion !== false ) {
1148 $vcsVersion = substr( $vcsVersion, 0, 7 );
1149 $vcsLink = $gitInfo->getHeadViewUrl();
1150 $vcsDate = $gitInfo->getHeadCommitDate();
1151 }
1152 $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
1153 } else {
1154 wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
1155 }
1156 }
1157
1158 $versionString = Html::rawElement(
1159 'span',
1160 [ 'class' => 'mw-version-ext-version' ],
1161 $canonicalVersion
1162 );
1163
1164 if ( $vcsVersion ) {
1165 if ( $vcsLink ) {
1166 $vcsVerString = $this->getLinkRenderer()->makeExternalLink(
1167 $vcsLink,
1168 $this->msg( 'version-version', $vcsVersion ),
1169 $this->getFullTitle(),
1170 '',
1171 [ 'class' => 'mw-version-ext-vcs-version' ]
1172 );
1173 } else {
1174 $vcsVerString = Html::element( 'span',
1175 [ 'class' => 'mw-version-ext-vcs-version' ],
1176 "({$vcsVersion})"
1177 );
1178 }
1179 $versionString .= " {$vcsVerString}";
1180
1181 if ( $vcsDate ) {
1182 $versionString .= ' ' . Html::element( 'span', [
1183 'class' => 'mw-version-ext-vcs-timestamp',
1184 'dir' => $this->getLanguage()->getDir(),
1185 ], $this->getLanguage()->timeanddate( $vcsDate, true ) );
1186 }
1187 $versionString = Html::rawElement( 'span',
1188 [ 'class' => 'mw-version-ext-meta-version' ],
1189 $versionString
1190 );
1191 }
1192
1193 // ... and license information; if a license file exists we
1194 // will link to it
1195 $licenseLink = '';
1196 if ( isset( $extension['name'] ) ) {
1197 $licenseName = null;
1198 if ( isset( $extension['license-name'] ) ) {
1199 $licenseName = new HtmlArmor( $out->parseInlineAsInterface( $extension['license-name'] ) );
1200 } elseif ( $extensionPath !== null && ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
1201 $licenseName = $this->msg( 'version-ext-license' )->text();
1202 }
1203 if ( $licenseName !== null ) {
1204 $licenseLink = $this->getLinkRenderer()->makeLink(
1205 $this->getPageTitle( 'License/' . $extension['name'] ),
1206 $licenseName,
1207 [
1208 'class' => 'mw-version-ext-license',
1209 'dir' => 'auto',
1210 ]
1211 );
1212 }
1213 }
1214
1215 // ... and generate the description; which can be a parameterized l10n message
1216 // in the form [ <msgname>, <parameter>, <parameter>... ] or just a straight
1217 // up string
1218 if ( isset( $extension['descriptionmsg'] ) ) {
1219 // Localized description of extension
1220 $descriptionMsg = $extension['descriptionmsg'];
1221
1222 if ( is_array( $descriptionMsg ) ) {
1223 $descriptionMsgKey = array_shift( $descriptionMsg );
1224 $descriptionMsg = array_map( 'htmlspecialchars', $descriptionMsg );
1225 $description = $this->msg( $descriptionMsgKey, ...$descriptionMsg )->text();
1226 } else {
1227 $description = $this->msg( $descriptionMsg )->text();
1228 }
1229 } elseif ( isset( $extension['description'] ) ) {
1230 // Non localized version
1231 $description = $extension['description'];
1232 } else {
1233 $description = '';
1234 }
1235 $description = $out->parseInlineAsInterface( $description );
1236
1237 // ... now get the authors for this extension
1238 $authors = $extension['author'] ?? [];
1239 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable path is set when there is a name
1240 $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
1241
1242 // Finally! Create the table
1243 $html = Html::openElement( 'tr', [
1244 'class' => 'mw-version-ext',
1245 'id' => Sanitizer::escapeIdForAttribute( 'mw-version-ext-' . $type . '-' . $extension['name'] )
1246 ]
1247 );
1248
1249 $html .= Html::rawElement( 'td', [], $extensionNameLink );
1250 $html .= Html::rawElement( 'td', [], $versionString );
1251 $html .= Html::rawElement( 'td', [], $licenseLink );
1252 $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-description' ], $description );
1253 $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-authors' ], $authors );
1254
1255 $html .= Html::closeElement( 'tr' );
1256
1257 return $html;
1258 }
1259
1265 private function getHooks() {
1266 if ( !$this->getConfig()->get( MainConfigNames::SpecialVersionShowHooks ) ) {
1267 return '';
1268 }
1269
1270 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1271 $hookNames = $hookContainer->getHookNames();
1272
1273 if ( !$hookNames ) {
1274 return '';
1275 }
1276
1277 sort( $hookNames );
1278
1279 $ret = [];
1280 $this->addTocSection( 'version-hooks', 'mw-version-hooks' );
1281 $ret[] = Html::element(
1282 'h2',
1283 [ 'id' => 'mw-version-hooks' ],
1284 $this->msg( 'version-hooks' )->text()
1285 );
1286 $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
1287 $ret[] = Html::openElement( 'tr' );
1288 $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
1289 $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
1290 $ret[] = Html::closeElement( 'tr' );
1291
1292 foreach ( $hookNames as $name ) {
1293 $handlers = $hookContainer->getHandlerDescriptions( $name );
1294
1295 $ret[] = Html::openElement( 'tr' );
1296 $ret[] = Html::element( 'td', [], $name );
1297 // @phan-suppress-next-line SecurityCheck-DoubleEscaped See FIXME in listToText
1298 $ret[] = Html::element( 'td', [], $this->listToText( $handlers ) );
1299 $ret[] = Html::closeElement( 'tr' );
1300 }
1301
1302 $ret[] = Html::closeElement( 'table' );
1303
1304 return implode( "\n", $ret );
1305 }
1306
1307 private function openExtType( ?string $text = null, ?string $name = null ): string {
1308 $out = '';
1309
1310 $opt = [ 'class' => 'wikitable plainlinks mw-installed-software' ];
1311
1312 if ( $name ) {
1313 $opt['id'] = "sv-$name";
1314 }
1315
1316 $out .= Html::openElement( 'table', $opt );
1317
1318 if ( $text !== null ) {
1319 $out .= Html::element( 'caption', [], $text );
1320 }
1321
1322 if ( $name && $text !== null ) {
1323 $this->addTocSubSection( $text, "sv-$name" );
1324 }
1325
1326 $firstHeadingMsg = ( $name === 'credits-skin' )
1327 ? 'version-skin-colheader-name'
1328 : 'version-ext-colheader-name';
1329
1330 $out .= $this->getTableHeaderHtml( [
1331 $this->msg( $firstHeadingMsg )->text(),
1332 $this->msg( 'version-ext-colheader-version' )->text(),
1333 $this->msg( 'version-ext-colheader-license' )->text(),
1334 $this->msg( 'version-ext-colheader-description' )->text(),
1335 $this->msg( 'version-ext-colheader-credits' )->text()
1336 ] );
1337
1338 return $out;
1339 }
1340
1349 private function getTableHeaderHtml( $headers ): string {
1350 $out = '';
1351 $out .= Html::openElement( 'thead' );
1352 $out .= Html::openElement( 'tr' );
1353 foreach ( $headers as $header ) {
1354 $out .= Html::element( 'th', [ 'scope' => 'col' ], $header );
1355 }
1356 $out .= Html::closeElement( 'tr' );
1357 $out .= Html::closeElement( 'thead' );
1358 return $out;
1359 }
1360
1366 private function IPInfo() {
1367 $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
1368
1369 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1370 }
1371
1392 public function listAuthors( $authors, $extName, $extDir ): string {
1393 $hasOthers = false;
1394 $linkRenderer = $this->getLinkRenderer();
1395
1396 $list = [];
1397 $authors = (array)$authors;
1398
1399 // Special case: if the authors array has only one item and it is "...",
1400 // it should not be rendered as the "version-poweredby-others" i18n msg,
1401 // but rather as "version-poweredby-various" i18n msg instead.
1402 if ( count( $authors ) === 1 && $authors[0] === '...' ) {
1403 // Link to the extension's or skin's AUTHORS or CREDITS file, if there is
1404 // such a file; otherwise just return the i18n msg as-is
1405 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1406 return $linkRenderer->makeLink(
1407 $this->getPageTitle( "Credits/$extName" ),
1408 $this->msg( 'version-poweredby-various' )->text()
1409 );
1410 } else {
1411 return $this->msg( 'version-poweredby-various' )->escaped();
1412 }
1413 }
1414
1415 // Otherwise, if we have an actual array that has more than one item,
1416 // process each array item as usual
1417 foreach ( $authors as $item ) {
1418 if ( $item instanceof HtmlArmor ) {
1419 $list[] = HtmlArmor::getHtml( $item );
1420 } elseif ( $item === '...' ) {
1421 $hasOthers = true;
1422
1423 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1424 $text = $linkRenderer->makeLink(
1425 $this->getPageTitle( "Credits/$extName" ),
1426 $this->msg( 'version-poweredby-others' )->text()
1427 );
1428 } else {
1429 $text = $this->msg( 'version-poweredby-others' )->escaped();
1430 }
1431 $list[] = $text;
1432 } elseif ( str_ends_with( $item, ' ...]' ) ) {
1433 $hasOthers = true;
1434 $list[] = $this->getOutput()->parseInlineAsInterface(
1435 substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
1436 );
1437 } else {
1438 $list[] = $this->getOutput()->parseInlineAsInterface( $item );
1439 }
1440 }
1441
1442 if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1443 $list[] = $linkRenderer->makeLink(
1444 $this->getPageTitle( "Credits/$extName" ),
1445 $this->msg( 'version-poweredby-others' )->text()
1446 );
1447 }
1448
1449 return $this->listToText( $list, false );
1450 }
1451
1461 private function listToText( array $list, bool $sort = true ): string {
1462 if ( !$list ) {
1463 return '';
1464 }
1465 if ( $sort ) {
1466 sort( $list );
1467 }
1468
1469 return $this->getLanguage()
1470 ->listToText( array_map( [ __CLASS__, 'arrayToString' ], $list ) );
1471 }
1472
1482 public static function arrayToString( $list ) {
1483 if ( is_array( $list ) && count( $list ) == 1 ) {
1484 $list = $list[0];
1485 }
1486 if ( $list instanceof Closure ) {
1487 // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
1488 return 'Closure';
1489 } elseif ( is_object( $list ) ) {
1490 return wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
1491 } elseif ( !is_array( $list ) ) {
1492 return $list;
1493 } else {
1494 if ( is_object( $list[0] ) ) {
1495 $class = get_class( $list[0] );
1496 } else {
1497 $class = $list[0];
1498 }
1499
1500 return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
1501 }
1502 }
1503
1509 public static function getGitHeadSha1( $dir ) {
1510 wfDeprecated( __METHOD__, '1.41' );
1511 return ( new GitInfo( $dir ) )->getHeadSHA1();
1512 }
1513
1518 public function getEntryPointInfo() {
1519 $config = $this->getConfig();
1520 $scriptPath = $config->get( MainConfigNames::ScriptPath ) ?: '/';
1521
1522 $entryPoints = [
1523 'version-entrypoints-articlepath' => $config->get( MainConfigNames::ArticlePath ),
1524 'version-entrypoints-scriptpath' => $scriptPath,
1525 'version-entrypoints-index-php' => wfScript( 'index' ),
1526 'version-entrypoints-api-php' => wfScript( 'api' ),
1527 'version-entrypoints-rest-php' => wfScript( 'rest' ),
1528 ];
1529
1530 $language = $this->getLanguage();
1531 $thAttributes = [
1532 'dir' => $language->getDir(),
1533 'lang' => $language->getHtmlCode(),
1534 'scope' => 'col'
1535 ];
1536
1537 $this->addTocSection( 'version-entrypoints', 'mw-version-entrypoints' );
1538
1539 $out = Html::element(
1540 'h2',
1541 [ 'id' => 'mw-version-entrypoints' ],
1542 $this->msg( 'version-entrypoints' )->text()
1543 ) .
1544 Html::openElement( 'table',
1545 [
1546 'class' => 'wikitable plainlinks',
1547 'id' => 'mw-version-entrypoints-table',
1548 'dir' => 'ltr',
1549 'lang' => 'en'
1550 ]
1551 ) .
1552 Html::openElement( 'thead' ) .
1553 Html::openElement( 'tr' ) .
1554 Html::element(
1555 'th',
1556 $thAttributes,
1557 $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1558 ) .
1559 Html::element(
1560 'th',
1561 $thAttributes,
1562 $this->msg( 'version-entrypoints-header-url' )->text()
1563 ) .
1564 Html::closeElement( 'tr' ) .
1565 Html::closeElement( 'thead' );
1566
1567 foreach ( $entryPoints as $message => $value ) {
1568 $url = $this->urlUtils->expand( $value, PROTO_RELATIVE );
1569 $out .= Html::openElement( 'tr' ) .
1570 Html::rawElement( 'td', [], $this->msg( $message )->parse() ) .
1571 Html::rawElement( 'td', [],
1572 Html::rawElement(
1573 'code',
1574 [],
1575 $this->msg( new RawMessage( "[$url $value]" ) )->parse()
1576 )
1577 ) .
1578 Html::closeElement( 'tr' );
1579 }
1580
1581 $out .= Html::closeElement( 'table' );
1582
1583 return $out;
1584 }
1585
1586 protected function getGroupName() {
1587 return 'wiki';
1588 }
1589}
1590
1595class_alias( SpecialVersion::class, 'SpecialVersion' );
const CACHE_ANYTHING
Definition Defines.php:86
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:37
const PROTO_RELATIVE
Definition Defines.php:233
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
Definition Setup.php:565
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:82
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
Base class for language-specific code.
Definition Language.php:81
Variant of the Message class.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
static listParam(array $list, $type=ListType::AND)
Definition Message.php:1367
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
Load JSON files, and uses a Processor to extract information.
getAllThings()
Get credits information about all installed extensions and skins.
Parent class for all special pages.
Version information about MediaWiki (core, extensions, libs), PHP, and the database.
getParserFunctionHooks()
Obtains a list of installed parser function hooks and the associated H2 header.
getLibraries(array $credits)
Generate the section for installed external libraries.
getParsoidModules()
Obtains a list of installed Parsoid Modules and the associated H2 header.
static parseComposerInstalled(array $credits)
string $coreId
The current rev id/SHA hash of MediaWiki core.
static arrayToString( $list)
Convert an array or object to a string for display.
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
listAuthors( $authors, $extName, $extDir)
Return a formatted unsorted list of authors.
static getExtensionTypeName( $type)
Returns the internationalized name for an extension type.
getCreditsForExtension( $type, array $extension)
Creates and formats a version line for a single extension.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getExtensionCategory( $type, ?string $text, array $creditsGroup)
Creates and returns the HTML for a single extension category.
static getVersionLinked()
Return a wikitext-formatted string of the MediaWiki version with a link to the Git SHA1 of head if av...
static getExtensionTypes()
Returns an array with the base extension types.
compare( $a, $b)
Callback to sort extensions by type.
__construct(ParserFactory $parserFactory, UrlUtils $urlUtils, IConnectionProvider $dbProvider)
static getCopyrightAndAuthorList()
Get the "MediaWiki is copyright 2001-20xx by lots of cool folks" text.
getEntryPointInfo()
Get the list of entry points and their URLs.
static getCredits(ExtensionRegistry $reg, Config $conf)
static string[] false $extensionTypes
Lazy initialized key/value with message content.
getParserTags()
Obtains a list of installed parser tags and the associated H2 header.
getExternalLibraries(array $credits)
Generate an HTML table for external libraries that are installed.
Fetch status information from a local git repository.
Definition GitInfo.php:47
Library for creating and parsing MW-style timestamps.
A service to expand, parse, and otherwise manipulate URLs.
Definition UrlUtils.php:16
Reads an installed.json file and provides accessors to get what is installed.
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:32
Interface for configuration instances.
Definition Config.php:32
Provide primary and replica IDatabase connections.
$source
element(SerializerNode $parent, SerializerNode $node, $contents)
$header