MediaWiki master
SpecialVersion.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Specials;
10
11use Closure;
28use Symfony\Component\Yaml\Yaml;
31use Wikimedia\Parsoid\Core\SectionMetadata;
32use Wikimedia\Parsoid\Core\TOCData;
34
41
45 protected $coreId = '';
46
50 protected static $extensionTypes = false;
51
53 protected $tocData;
54
56 protected $tocIndex;
57
59 protected $tocSection;
60
62 protected $tocSubSection;
63
64 private ParserFactory $parserFactory;
65 private UrlUtils $urlUtils;
66 private IConnectionProvider $dbProvider;
67
68 public function __construct(
69 ParserFactory $parserFactory,
70 UrlUtils $urlUtils,
71 IConnectionProvider $dbProvider
72 ) {
73 parent::__construct( 'Version' );
74 $this->parserFactory = $parserFactory;
75 $this->urlUtils = $urlUtils;
76 $this->dbProvider = $dbProvider;
77 }
78
86 public static function getCredits( ExtensionRegistry $reg, Config $conf ): array {
87 $credits = $conf->get( MainConfigNames::ExtensionCredits );
88 foreach ( $reg->getAllThings() as $credit ) {
89 $credits[$credit['type']][] = $credit;
90 }
91 return $credits;
92 }
93
97 public function execute( $par ) {
98 $config = $this->getConfig();
99 $credits = self::getCredits( ExtensionRegistry::getInstance(), $config );
100
101 $this->setHeaders();
102 $this->outputHeader();
103 $out = $this->getOutput();
104 $out->getMetadata()->setPreventClickjacking( false );
105
106 // Explode the subpage information into useful bits
107 $parts = explode( '/', (string)$par );
108 $extNode = null;
109 if ( isset( $parts[1] ) ) {
110 $extName = str_replace( '_', ' ', $parts[1] );
111 // Find it!
112 foreach ( $credits as $extensions ) {
113 foreach ( $extensions as $ext ) {
114 if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
115 $extNode = &$ext;
116 break 2;
117 }
118 }
119 }
120 if ( !$extNode ) {
121 $out->setStatusCode( 404 );
122 }
123 } else {
124 $extName = 'MediaWiki';
125 }
126
127 // Now figure out what to do
128 switch ( strtolower( $parts[0] ) ) {
129 case 'credits':
130 $out->addModuleStyles( 'mediawiki.special' );
131
132 $wikiText = '{{int:version-credits-not-found}}';
133 if ( $extName === 'MediaWiki' ) {
134 $wikiText = file_get_contents( MW_INSTALL_PATH . '/CREDITS' );
135 // Put the contributor list into columns
136 $wikiText = str_replace(
137 [ '<!-- BEGIN CONTRIBUTOR LIST -->', '<!-- END CONTRIBUTOR LIST -->' ],
138 [ '<div class="mw-version-credits">', '</div>' ],
139 $wikiText
140 );
141 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
142 $file = ExtensionInfo::getAuthorsFileName( dirname( $extNode['path'] ) );
143 if ( $file ) {
144 $wikiText = file_get_contents( $file );
145 if ( str_ends_with( $file, '.txt' ) ) {
146 $wikiText = Html::element(
147 'pre',
148 [
149 'lang' => 'en',
150 'dir' => 'ltr',
151 ],
152 $wikiText
153 );
154 }
155 }
156 }
157
158 $out->setPageTitleMsg( $this->msg( 'version-credits-title' )->plaintextParams( $extName ) );
159 $out->addWikiTextAsInterface( $wikiText );
160 break;
161
162 case 'license':
163 $out->setPageTitleMsg( $this->msg( 'version-license-title' )->plaintextParams( $extName ) );
164
165 $licenseFound = false;
166
167 if ( $extName === 'MediaWiki' ) {
168 $out->addWikiTextAsInterface(
169 file_get_contents( MW_INSTALL_PATH . '/COPYING' )
170 );
171 $licenseFound = true;
172 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
173 $files = ExtensionInfo::getLicenseFileNames( dirname( $extNode['path'] ) );
174 if ( $files ) {
175 $licenseFound = true;
176 foreach ( $files as $file ) {
177 $out->addWikiTextAsInterface(
179 'pre',
180 [
181 'lang' => 'en',
182 'dir' => 'ltr',
183 ],
184 file_get_contents( $file )
185 )
186 );
187 }
188 }
189 }
190 if ( !$licenseFound ) {
191 $out->addWikiTextAsInterface( '{{int:version-license-not-found}}' );
192 }
193 break;
194
195 default:
196 $out->addModuleStyles( 'mediawiki.special' );
197
198 $out->addHTML( $this->getMediaWikiCredits() );
199
200 $this->tocData = new TOCData();
201 $this->tocIndex = 0;
202 $this->tocSection = 0;
203 $this->tocSubSection = 0;
204
205 // Build the page contents (this also fills in TOCData)
206 $sections = [
207 $this->softwareInformation(),
208 $this->getEntryPointInfo(),
209 $this->getSkinCredits( $credits ),
210 $this->getExtensionCredits( $credits ),
211 $this->getLibraries( $credits ),
212 $this->getParserTags(),
213 $this->getParserFunctionHooks(),
214 $this->getParsoidModules(),
215 $this->getHooks(),
216 $this->IPInfo(),
217 ];
218
219 // Insert TOC first
220 $out->addTOCPlaceholder( $this->tocData );
221
222 // Insert contents
223 foreach ( $sections as $content ) {
224 $out->addHTML( $content );
225 }
226
227 break;
228 }
229 }
230
238 private function addTocSection( $labelMsg, $id ) {
239 $this->tocIndex++;
240 $this->tocSection++;
241 $this->tocSubSection = 0;
242 $this->tocData->addSection( new SectionMetadata(
243 1,
244 2,
245 $this->msg( $labelMsg )->escaped(),
246 $this->getLanguage()->formatNum( $this->tocSection ),
247 (string)$this->tocIndex,
248 null,
249 null,
250 $id,
251 $id
252 ) );
253 }
254
262 private function addTocSubSection( $label, $id ) {
263 $this->tocIndex++;
264 $this->tocSubSection++;
265 $this->tocData->addSection( new SectionMetadata(
266 2,
267 3,
268 htmlspecialchars( $label ),
269 // See Parser::localizeTOC
270 $this->getLanguage()->formatNum( $this->tocSection ) . '.' .
271 $this->getLanguage()->formatNum( $this->tocSubSection ),
272 (string)$this->tocIndex,
273 null,
274 null,
275 $id,
276 $id
277 ) );
278 }
279
285 private function getMediaWikiCredits() {
286 // No TOC entry for this heading, we treat it like the lede section
287
288 $ret = Html::element(
289 'h2',
290 [ 'id' => 'mw-version-license' ],
291 $this->msg( 'version-license' )->text()
292 );
293
294 $ret .= Html::rawElement( 'div', [ 'class' => 'plainlinks' ],
295 $this->msg( new RawMessage( self::getCopyrightAndAuthorList() ) )->parseAsBlock() .
296 Html::rawElement( 'div', [ 'class' => 'mw-version-license-info' ],
297 $this->msg( 'version-license-info' )->parseAsBlock()
298 )
299 );
300
301 return $ret;
302 }
303
310 public static function getCopyrightAndAuthorList() {
311 if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
312 $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
313 wfMessage( 'version-poweredby-others' )->plain() . ']';
314 } else {
315 $othersLink = '[[Special:Version/Credits|' .
316 wfMessage( 'version-poweredby-others' )->plain() . ']]';
317 }
318
319 $translatorsLink = '[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
320 wfMessage( 'version-poweredby-translators' )->plain() . ']';
321
322 $authorList = [
323 'Magnus Manske', 'Brooke Vibber', 'Lee Daniel Crocker',
324 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
325 'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
326 'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
327 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
328 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
329 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
330 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
331 'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
332 'Kunal Mehta', 'James D. Forrester', 'Brian Wolff', 'Adam Shorland',
333 'DannyS712', 'Ori Livneh', 'Max Semenik', 'Amir Sarabadani',
334 'Derk-Jan Hartman', 'Petr Pchelko', 'Umherirrender', 'C. Scott Ananian',
335 'fomafix', 'Thiemo Kreuz', 'Gergő Tisza', 'Volker E.',
336 'Jack Phoenix', 'Isarra Yos',
337 $othersLink, $translatorsLink
338 ];
339
340 return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
341 Message::listParam( $authorList ) )->plain();
342 }
343
349 private function getSoftwareInformation() {
350 $dbr = $this->dbProvider->getReplicaDatabase();
351
352 // Put the software in an array of form 'name' => 'version'. All messages should
353 // be loaded here, so feel free to use wfMessage in the 'name'. Wikitext
354 // can be used both in the name and value.
355 $software = [
356 '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked(),
357 '[https://php.net/ PHP]' => PHP_VERSION . " (" . PHP_SAPI . ")",
358 '[https://icu.unicode.org/ ICU]' => INTL_ICU_VERSION,
359 $dbr->getSoftwareLink() => $dbr->getServerInfo(),
360 ];
361
362 // T339915: If wikidiff2 is installed, show version
363 if ( phpversion( "wikidiff2" ) ) {
364 $software[ '[https://www.mediawiki.org/wiki/Wikidiff2 wikidiff2]' ] = phpversion( "wikidiff2" );
365 }
366
367 // Allow a hook to add/remove items.
368 $this->getHookRunner()->onSoftwareInfo( $software );
369
370 return $software;
371 }
372
378 private function softwareInformation() {
379 $this->addTocSection( 'version-software', 'mw-version-software' );
380
381 $out = Html::element(
382 'h2',
383 [ 'id' => 'mw-version-software' ],
384 $this->msg( 'version-software' )->text()
385 );
386
387 $out .= Html::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] );
388
389 $out .= $this->getTableHeaderHtml( [
390 $this->msg( 'version-software-product' )->text(),
391 $this->msg( 'version-software-version' )->text()
392 ] );
393
394 foreach ( $this->getSoftwareInformation() as $name => $version ) {
395 $out .= Html::rawElement(
396 'tr',
397 [],
398 Html::rawElement( 'td', [], $this->msg( new RawMessage( $name ) )->parse() ) .
399 Html::rawElement( 'td', [ 'dir' => 'ltr' ], $this->msg( new RawMessage( $version ) )->parse() )
400 );
401 }
402
403 $out .= Html::closeElement( 'table' );
404
405 return $out;
406 }
407
417 public static function getVersion( $flags = '', $lang = null ) {
418 $gitInfo = GitInfo::repo()->getHeadSHA1();
419 if ( !$gitInfo ) {
420 $version = MW_VERSION;
421 } elseif ( $flags === 'nodb' ) {
422 $shortSha1 = substr( $gitInfo, 0, 7 );
423 $version = MW_VERSION . " ($shortSha1)";
424 } else {
425 $shortSha1 = substr( $gitInfo, 0, 7 );
426 $msg = wfMessage( 'parentheses' );
427 if ( $lang !== null ) {
428 $msg->inLanguage( $lang );
429 }
430 $shortSha1 = $msg->params( $shortSha1 )->text();
431 $version = MW_VERSION . ' ' . $shortSha1;
432 }
433
434 return $version;
435 }
436
444 public static function getVersionLinked() {
445 return self::getVersionLinkedGit() ?: MW_VERSION;
446 }
447
451 private static function getMWVersionLinked() {
452 $versionUrl = "";
453 $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
454 if ( $hookRunner->onSpecialVersionVersionUrl( MW_VERSION, $versionUrl ) ) {
455 $versionParts = [];
456 preg_match( "/^(\d+\.\d+)/", MW_VERSION, $versionParts );
457 $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
458 }
459
460 return '[' . $versionUrl . ' ' . MW_VERSION . ']';
461 }
462
468 private static function getVersionLinkedGit() {
469 global $wgLang;
470
471 $gitInfo = new GitInfo( MW_INSTALL_PATH );
472 $headSHA1 = $gitInfo->getHeadSHA1();
473 if ( !$headSHA1 ) {
474 return false;
475 }
476
477 $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
478
479 $gitHeadUrl = $gitInfo->getHeadViewUrl();
480 if ( $gitHeadUrl !== false ) {
481 $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
482 }
483
484 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
485 if ( $gitHeadCommitDate ) {
486 $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( (string)$gitHeadCommitDate, true );
487 }
488
489 return self::getMWVersionLinked() . " $shortSHA1";
490 }
491
501 public static function getExtensionTypes(): array {
502 if ( self::$extensionTypes === false ) {
503 self::$extensionTypes = [
504 'specialpage' => wfMessage( 'version-specialpages' )->text(),
505 'editor' => wfMessage( 'version-editors' )->text(),
506 'parserhook' => wfMessage( 'version-parserhooks' )->text(),
507 'variable' => wfMessage( 'version-variables' )->text(),
508 'media' => wfMessage( 'version-mediahandlers' )->text(),
509 'antispam' => wfMessage( 'version-antispam' )->text(),
510 'skin' => wfMessage( 'version-skins' )->text(),
511 'api' => wfMessage( 'version-api' )->text(),
512 'other' => wfMessage( 'version-other' )->text(),
513 ];
514
515 ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
516 ->onExtensionTypes( self::$extensionTypes );
517 }
518
519 return self::$extensionTypes;
520 }
521
531 public static function getExtensionTypeName( $type ) {
532 $types = self::getExtensionTypes();
533
534 return $types[$type] ?? $types['other'];
535 }
536
543 private function getExtensionCredits( array $credits ) {
544 $extensionTypes = self::getExtensionTypes();
545
546 $this->addTocSection( 'version-extensions', 'mw-version-ext' );
547
548 $out = Html::element(
549 'h2',
550 [ 'id' => 'mw-version-ext' ],
551 $this->msg( 'version-extensions' )->text()
552 );
553
554 if (
555 !$credits ||
556 // Skins are displayed separately, see getSkinCredits()
557 ( count( $credits ) === 1 && isset( $credits['skin'] ) )
558 ) {
559 $out .= Html::element(
560 'p',
561 [],
562 $this->msg( 'version-extensions-no-ext' )->text()
563 );
564
565 return $out;
566 }
567
568 // Find all extensions that do not have a valid type and give them the type 'other'.
569 $credits['other'] ??= [];
570 foreach ( $credits as $type => $extensions ) {
571 if ( !array_key_exists( $type, $extensionTypes ) ) {
572 $credits['other'] = array_merge( $credits['other'], $extensions );
573 }
574 }
575
576 // Loop through the extension categories to display their extensions in the list.
577 foreach ( $extensionTypes as $type => $text ) {
578 // Skins have a separate section
579 if ( $type !== 'other' && $type !== 'skin' ) {
580 $out .= $this->getExtensionCategory( $type, $text, $credits[$type] ?? [] );
581 }
582 }
583
584 // We want the 'other' type to be last in the list.
585 $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'], $credits['other'] );
586
587 return $out;
588 }
589
596 private function getSkinCredits( array $credits ) {
597 $this->addTocSection( 'version-skins', 'mw-version-skin' );
598
599 $out = Html::element(
600 'h2',
601 [ 'id' => 'mw-version-skin' ],
602 $this->msg( 'version-skins' )->text()
603 );
604
605 if ( !isset( $credits['skin'] ) || !$credits['skin'] ) {
606 $out .= Html::element(
607 'p',
608 [],
609 $this->msg( 'version-skins-no-skin' )->text()
610 );
611
612 return $out;
613 }
614 $out .= $this->getExtensionCategory( 'skin', null, $credits['skin'] );
615
616 return $out;
617 }
618
625 protected function getLibraries( array $credits ) {
626 $this->addTocSection( 'version-libraries', 'mw-version-libraries' );
627
628 $out = Html::element(
629 'h2',
630 [ 'id' => 'mw-version-libraries' ],
631 $this->msg( 'version-libraries' )->text()
632 );
633
634 return $out
635 . $this->getExternalLibraries( $credits )
636 . $this->getClientSideLibraries();
637 }
638
645 public static function parseComposerInstalled( array $credits ) {
646 $paths = [
647 MW_INSTALL_PATH . '/vendor/composer/installed.json'
648 ];
649
650 $extensionTypes = self::getExtensionTypes();
651 foreach ( $extensionTypes as $type => $message ) {
652 if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
653 continue;
654 }
655 foreach ( $credits[$type] as $extension ) {
656 if ( !isset( $extension['path'] ) ) {
657 continue;
658 }
659 $paths[] = dirname( $extension['path'] ) . '/vendor/composer/installed.json';
660 }
661 }
662
663 $dependencies = [];
664
665 foreach ( $paths as $path ) {
666 if ( !file_exists( $path ) ) {
667 continue;
668 }
669
670 $installed = new ComposerInstalled( $path );
671
672 $dependencies += $installed->getInstalledDependencies();
673 }
674
675 ksort( $dependencies );
676 return $dependencies;
677 }
678
685 protected function getExternalLibraries( array $credits ) {
686 $dependencies = self::parseComposerInstalled( $credits );
687 if ( $dependencies === [] ) {
688 return '';
689 }
690
691 $this->addTocSubSection( $this->msg( 'version-libraries-server' )->text(), 'mw-version-libraries-server' );
692
693 $out = Html::element(
694 'h3',
695 [ 'id' => 'mw-version-libraries-server' ],
696 $this->msg( 'version-libraries-server' )->text()
697 );
698 $out .= Html::openElement(
699 'table',
700 [ 'class' => 'wikitable plainlinks mw-installed-software', 'id' => 'sv-libraries' ]
701 );
702
703 $out .= $this->getTableHeaderHtml( [
704 $this->msg( 'version-libraries-library' )->text(),
705 $this->msg( 'version-libraries-version' )->text(),
706 $this->msg( 'version-libraries-license' )->text(),
707 $this->msg( 'version-libraries-description' )->text(),
708 $this->msg( 'version-libraries-authors' )->text(),
709 ] );
710
711 foreach ( $dependencies as $name => $info ) {
712 if ( !is_array( $info ) || str_starts_with( $info['type'], 'mediawiki-' ) ) {
713 // Skip any extensions or skins since they'll be listed
714 // in their proper section
715 continue;
716 }
717 $authors = array_map( static function ( $arr ) {
718 return new HtmlArmor( isset( $arr['homepage'] ) ?
719 Html::element( 'a', [ 'href' => $arr['homepage'] ], $arr['name'] ) :
720 htmlspecialchars( $arr['name'] )
721 );
722 }, $info['authors'] );
723 $authors = $this->listAuthors( $authors, false, MW_INSTALL_PATH . "/vendor/$name" );
724
725 // We can safely assume that the libraries' names and descriptions
726 // are written in English and aren't going to be translated,
727 // so set appropriate lang and dir attributes
728 $out .= Html::openElement( 'tr', [
729 // Add an anchor so docs can link easily to the version of
730 // this specific library
731 'id' => Sanitizer::escapeIdForAttribute(
732 "mw-version-library-$name"
733 ) ] )
734 . Html::rawElement(
735 'td',
736 [],
737 $this->getLinkRenderer()->makeExternalLink(
738 "https://packagist.org/packages/$name",
739 $name,
740 $this->getFullTitle(),
741 '',
742 [ 'class' => 'mw-version-library-name' ]
743 )
744 )
745 . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
746 // @phan-suppress-next-line SecurityCheck-DoubleEscaped See FIXME in listToText
747 . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['licenses'] ) )
748 . Html::element( 'td', [ 'lang' => 'en', 'dir' => 'ltr' ], $info['description'] )
749 . Html::rawElement( 'td', [], $authors )
750 . Html::closeElement( 'tr' );
751 }
752 $out .= Html::closeElement( 'table' );
753
754 return $out;
755 }
756
762 public static function parseForeignResources() {
763 $registryDirs = [ 'MediaWiki' => MW_INSTALL_PATH . '/resources/lib' ]
764 + ExtensionRegistry::getInstance()->getAttribute( 'ForeignResourcesDir' );
765
766 $modules = [];
767 foreach ( $registryDirs as $source => $registryDir ) {
768 $foreignResources = Yaml::parseFile( "$registryDir/foreign-resources.yaml" );
769 foreach ( $foreignResources as $name => $module ) {
770 $key = $name . $module['version'];
771 if ( isset( $modules[$key] ) ) {
772 $modules[$key]['source'][] = $source;
773 continue;
774 }
775 $modules[$key] = $module + [ 'name' => $name, 'source' => [ $source ] ];
776 }
777 }
778 ksort( $modules );
779 return $modules;
780 }
781
787 private function getClientSideLibraries() {
788 $this->addTocSubSection( $this->msg( 'version-libraries-client' )->text(), 'mw-version-libraries-client' );
789
790 $out = Html::element(
791 'h3',
792 [ 'id' => 'mw-version-libraries-client' ],
793 $this->msg( 'version-libraries-client' )->text()
794 );
795 $out .= Html::openElement(
796 'table',
797 [ 'class' => 'wikitable plainlinks mw-installed-software', 'id' => 'sv-libraries-client' ]
798 );
799
800 $out .= $this->getTableHeaderHtml( [
801 $this->msg( 'version-libraries-library' )->text(),
802 $this->msg( 'version-libraries-version' )->text(),
803 $this->msg( 'version-libraries-license' )->text(),
804 $this->msg( 'version-libraries-authors' )->text(),
805 $this->msg( 'version-libraries-source' )->text()
806 ] );
807
808 foreach ( self::parseForeignResources() as $name => $info ) {
809 // We can safely assume that the libraries' names and descriptions
810 // are written in English and aren't going to be translated,
811 // so set appropriate lang and dir attributes
812 $out .= Html::openElement( 'tr', [
813 // Add an anchor so docs can link easily to the version of
814 // this specific library
815 'id' => Sanitizer::escapeIdForAttribute(
816 "mw-version-library-$name"
817 ) ] )
818 . Html::rawElement(
819 'td',
820 [],
821 $this->getLinkRenderer()->makeExternalLink(
822 $info['homepage'],
823 $info['name'],
824 $this->getFullTitle(),
825 '',
826 [ 'class' => 'mw-version-library-name' ]
827 )
828 )
829 . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
830 . Html::element( 'td', [ 'dir' => 'auto' ], $info['license'] )
831 . Html::element( 'td', [ 'dir' => 'auto' ], $info['authors'] ?? '—' )
832 // @phan-suppress-next-line SecurityCheck-DoubleEscaped See FIXME in listToText
833 . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['source'] ) )
834 . Html::closeElement( 'tr' );
835 }
836 $out .= Html::closeElement( 'table' );
837
838 return $out;
839 }
840
846 protected function getParserTags() {
847 $tags = $this->parserFactory->getMainInstance()->getTags();
848 if ( !$tags ) {
849 return '';
850 }
851
852 $this->addTocSection( 'version-parser-extensiontags', 'mw-version-parser-extensiontags' );
853
854 $out = Html::rawElement(
855 'h2',
856 [ 'id' => 'mw-version-parser-extensiontags' ],
857 Html::rawElement(
858 'span',
859 [ 'class' => 'plainlinks' ],
860 $this->getLinkRenderer()->makeExternalLink(
861 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
862 $this->msg( 'version-parser-extensiontags' ),
863 $this->getFullTitle()
864 )
865 )
866 );
867
868 array_walk( $tags, static function ( &$value ) {
869 // Bidirectional isolation improves readability in RTL wikis
870 $value = Html::rawElement(
871 'bdi',
872 // Prevent < and > from slipping to another line
873 [
874 'style' => 'white-space: nowrap;',
875 ],
876 Html::element( 'code', [], "<$value>" )
877 );
878 } );
879
880 $out .= $this->listToText( $tags );
881
882 return $out;
883 }
884
890 protected function getParserFunctionHooks() {
891 $funcHooks = $this->parserFactory->getMainInstance()->getFunctionHooks();
892 if ( !$funcHooks ) {
893 return '';
894 }
895
896 $this->addTocSection( 'version-parser-function-hooks', 'mw-version-parser-function-hooks' );
897
898 $out = Html::rawElement(
899 'h2',
900 [ 'id' => 'mw-version-parser-function-hooks' ],
901 Html::rawElement(
902 'span',
903 [ 'class' => 'plainlinks' ],
904 $this->getLinkRenderer()->makeExternalLink(
905 'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
906 $this->msg( 'version-parser-function-hooks' ),
907 $this->getFullTitle()
908 )
909 )
910 );
911
912 $funcSynonyms = $this->parserFactory->getMainInstance()->getFunctionSynonyms();
913 // This will give us the preferred synonyms in the content language, as if
914 // we used MagicWord::getSynonym( 0 ), because they appear first in the arrays.
915 // We can't use MagicWord directly, because only Parser knows whether a function
916 // uses the leading "#" or not. Case-sensitive functions ("1") win over
917 // case-insensitive ones ("0"), like in Parser::callParserFunction().
918 // There should probably be a better API for this.
919 $preferredSynonyms = array_flip( array_reverse( $funcSynonyms[1] + $funcSynonyms[0] ) );
920 array_walk( $funcHooks, static function ( &$value ) use ( $preferredSynonyms ) {
921 $value = $preferredSynonyms[$value];
922 } );
923 $legacyHooks = array_flip( $funcHooks );
924
925 // Sort case-insensitively, ignoring the leading '#' if present
926 $cmpHooks = static function ( $a, $b ) {
927 return strcasecmp( ltrim( $a, '#' ), ltrim( $b, '#' ) );
928 };
929 usort( $funcHooks, $cmpHooks );
930
931 $formatHooks = static function ( &$value ) {
932 // Bidirectional isolation ensures it displays as {{#ns}} and not {{ns#}} in RTL wikis
933 $value = Html::rawElement(
934 'bdi',
935 [],
936 Html::element( 'code', [], '{{' . $value . '}}' )
937 );
938 };
939 array_walk( $funcHooks, $formatHooks );
940
941 $out .= $this->getLanguage()->listToText( $funcHooks );
942
943 # Get a list of parser functions from Parsoid as well.
944 $parsoidHooks = [];
945 $services = MediaWikiServices::getInstance();
946 $siteConfig = $services->getParsoidSiteConfig();
947 $magicWordFactory = $services->getMagicWordFactory();
948 foreach ( $siteConfig->getPFragmentHandlerKeys() as $key ) {
949 $config = $siteConfig->getPFragmentHandlerConfig( $key );
950 if ( !( $config['options']['parserFunction'] ?? false ) ) {
951 continue;
952 }
953 $mw = $magicWordFactory->get( $key );
954 foreach ( $mw->getSynonyms() as $local ) {
955 if ( !( $config['options']['nohash'] ?? false ) ) {
956 $local = '#' . $local;
957 }
958 // Skip hooks already present in legacy hooks (they will
959 // also work in parsoid)
960 if ( isset( $legacyHooks[$local] ) ) {
961 continue;
962 }
963 $parsoidHooks[] = $local;
964 }
965 }
966 if ( $parsoidHooks ) {
967 $out .= Html::element(
968 'h3',
969 [ 'id' => 'mw-version-parser-function-hooks-parsoid' ],
970 $this->msg( 'version-parser-function-hooks-parsoid' )->text()
971 );
972 usort( $parsoidHooks, $cmpHooks );
973 array_walk( $parsoidHooks, $formatHooks );
974 $out .= $this->getLanguage()->listToText( $parsoidHooks );
975 }
976
977 return $out;
978 }
979
985 protected function getParsoidModules() {
986 $siteConfig = MediaWikiServices::getInstance()->getParsoidSiteConfig();
987 $modules = $siteConfig->getExtensionModules();
988
989 if ( !$modules ) {
990 return '';
991 }
992
993 $this->addTocSection( 'version-parsoid-modules', 'mw-version-parsoid-modules' );
994
995 $out = Html::rawElement(
996 'h2',
997 [ 'id' => 'mw-version-parsoid-modules' ],
998 Html::rawElement(
999 'span',
1000 [ 'class' => 'plainlinks' ],
1001 $this->getLinkRenderer()->makeExternalLink(
1002 'https://www.mediawiki.org/wiki/Special:MyLanguage/Parsoid',
1003 $this->msg( 'version-parsoid-modules' ),
1004 $this->getFullTitle()
1005 )
1006 )
1007 );
1008
1009 $moduleNames = array_map(
1010 static fn ( $m )=>Html::element( 'code', [
1011 'title' => $m->getConfig()['extension-name'] ?? null,
1012 ], $m->getConfig()['name'] ),
1013 $modules
1014 );
1015
1016 $out .= $this->getLanguage()->listToText( $moduleNames );
1017
1018 return $out;
1019 }
1020
1030 protected function getExtensionCategory( $type, ?string $text, array $creditsGroup ) {
1031 $out = '';
1032
1033 if ( $creditsGroup ) {
1034 $out .= $this->openExtType( $text, 'credits-' . $type );
1035
1036 usort( $creditsGroup, $this->compare( ... ) );
1037
1038 foreach ( $creditsGroup as $extension ) {
1039 $out .= $this->getCreditsForExtension( $type, $extension );
1040 }
1041
1042 $out .= Html::closeElement( 'table' );
1043 }
1044
1045 return $out;
1046 }
1047
1054 private function compare( $a, $b ) {
1055 return $this->getLanguage()->lc( $a['name'] ) <=> $this->getLanguage()->lc( $b['name'] );
1056 }
1057
1076 public function getCreditsForExtension( $type, array $extension ) {
1077 $out = $this->getOutput();
1078
1079 // We must obtain the information for all the bits and pieces!
1080 // ... such as extension names and links
1081 if ( isset( $extension['namemsg'] ) ) {
1082 // Localized name of extension
1083 $extensionName = $this->msg( $extension['namemsg'] )->text();
1084 } elseif ( isset( $extension['name'] ) ) {
1085 // Non localized version
1086 $extensionName = $extension['name'];
1087 } else {
1088 $extensionName = $this->msg( 'version-no-ext-name' )->text();
1089 }
1090
1091 if ( isset( $extension['url'] ) ) {
1092 $extensionNameLink = $this->getLinkRenderer()->makeExternalLink(
1093 $extension['url'],
1094 $extensionName,
1095 $this->getFullTitle(),
1096 '',
1097 [ 'class' => 'mw-version-ext-name' ]
1098 );
1099 } else {
1100 $extensionNameLink = htmlspecialchars( $extensionName );
1101 }
1102
1103 // ... and the version information
1104 // If the extension path is set we will check that directory for GIT
1105 // metadata in an attempt to extract date and vcs commit metadata.
1106 $canonicalVersion = '&ndash;';
1107 $extensionPath = null;
1108 $vcsVersion = null;
1109 $vcsLink = null;
1110 $vcsDate = null;
1111
1112 if ( isset( $extension['version'] ) ) {
1113 $canonicalVersion = $out->parseInlineAsInterface( $extension['version'] );
1114 }
1115
1116 if ( isset( $extension['path'] ) ) {
1117 $extensionPath = dirname( $extension['path'] );
1118 if ( $this->coreId == '' ) {
1119 wfDebug( 'Looking up core head id' );
1120 $coreHeadSHA1 = GitInfo::repo()->getHeadSHA1();
1121 if ( $coreHeadSHA1 ) {
1122 $this->coreId = $coreHeadSHA1;
1123 }
1124 }
1125 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance( CACHE_ANYTHING );
1126 $memcKey = $cache->makeKey(
1127 'specialversion-ext-version-text', $extension['path'], $this->coreId
1128 );
1129 [ $vcsVersion, $vcsLink, $vcsDate ] = $cache->get( $memcKey );
1130
1131 if ( !$vcsVersion ) {
1132 wfDebug( "Getting VCS info for extension {$extension['name']}" );
1133 $gitInfo = new GitInfo( $extensionPath );
1134 $vcsVersion = $gitInfo->getHeadSHA1();
1135 if ( $vcsVersion !== false ) {
1136 $vcsVersion = substr( $vcsVersion, 0, 7 );
1137 $vcsLink = $gitInfo->getHeadViewUrl();
1138 $vcsDate = $gitInfo->getHeadCommitDate();
1139 }
1140 $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
1141 } else {
1142 wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
1143 }
1144 }
1145
1146 $versionString = Html::rawElement(
1147 'span',
1148 [ 'class' => 'mw-version-ext-version' ],
1149 $canonicalVersion
1150 );
1151
1152 if ( $vcsVersion ) {
1153 if ( $vcsLink ) {
1154 $vcsVerString = $this->getLinkRenderer()->makeExternalLink(
1155 $vcsLink,
1156 $this->msg( 'version-version', $vcsVersion ),
1157 $this->getFullTitle(),
1158 '',
1159 [ 'class' => 'mw-version-ext-vcs-version' ]
1160 );
1161 } else {
1162 $vcsVerString = Html::element( 'span',
1163 [ 'class' => 'mw-version-ext-vcs-version' ],
1164 "({$vcsVersion})"
1165 );
1166 }
1167 $versionString .= " {$vcsVerString}";
1168
1169 if ( $vcsDate ) {
1170 $versionString .= ' ' . Html::element( 'span', [
1171 'class' => 'mw-version-ext-vcs-timestamp',
1172 'dir' => $this->getLanguage()->getDir(),
1173 ], $this->getLanguage()->timeanddate( $vcsDate, true ) );
1174 }
1175 $versionString = Html::rawElement( 'span',
1176 [ 'class' => 'mw-version-ext-meta-version' ],
1177 $versionString
1178 );
1179 }
1180
1181 // ... and license information; if a license file exists we
1182 // will link to it
1183 $licenseLink = '';
1184 if ( isset( $extension['name'] ) ) {
1185 $licenseName = null;
1186 if ( isset( $extension['license-name'] ) ) {
1187 $licenseName = new HtmlArmor( $out->parseInlineAsInterface( $extension['license-name'] ) );
1188 } elseif ( $extensionPath !== null && ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
1189 $licenseName = $this->msg( 'version-ext-license' )->text();
1190 }
1191 if ( $licenseName !== null ) {
1192 $licenseLink = $this->getLinkRenderer()->makeLink(
1193 $this->getPageTitle( 'License/' . $extension['name'] ),
1194 $licenseName,
1195 [
1196 'class' => 'mw-version-ext-license',
1197 'dir' => 'auto',
1198 ]
1199 );
1200 }
1201 }
1202
1203 // ... and generate the description; which can be a parameterized l10n message
1204 // in the form [ <msgname>, <parameter>, <parameter>... ] or just a straight
1205 // up string
1206 if ( isset( $extension['descriptionmsg'] ) ) {
1207 // Localized description of extension
1208 $descriptionMsg = $extension['descriptionmsg'];
1209
1210 if ( is_array( $descriptionMsg ) ) {
1211 $descriptionMsgKey = array_shift( $descriptionMsg );
1212 $descriptionMsg = array_map( 'htmlspecialchars', $descriptionMsg );
1213 $description = $this->msg( $descriptionMsgKey, ...$descriptionMsg )->text();
1214 } else {
1215 $description = $this->msg( $descriptionMsg )->text();
1216 }
1217 } elseif ( isset( $extension['description'] ) ) {
1218 // Non localized version
1219 $description = $extension['description'];
1220 } else {
1221 $description = '';
1222 }
1223 $description = $out->parseInlineAsInterface( $description );
1224
1225 // ... now get the authors for this extension
1226 $authors = $extension['author'] ?? [];
1227 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable path is set when there is a name
1228 $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
1229
1230 // Finally! Create the table
1231 $html = Html::openElement( 'tr', [
1232 'class' => 'mw-version-ext',
1233 'id' => Sanitizer::escapeIdForAttribute( 'mw-version-ext-' . $type . '-' . $extension['name'] )
1234 ]
1235 );
1236
1237 $html .= Html::rawElement( 'td', [], $extensionNameLink );
1238 $html .= Html::rawElement( 'td', [], $versionString );
1239 $html .= Html::rawElement( 'td', [], $licenseLink );
1240 $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-description' ], $description );
1241 $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-authors' ], $authors );
1242
1243 $html .= Html::closeElement( 'tr' );
1244
1245 return $html;
1246 }
1247
1253 private function getHooks() {
1254 if ( !$this->getConfig()->get( MainConfigNames::SpecialVersionShowHooks ) ) {
1255 return '';
1256 }
1257
1258 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1259 $hookNames = $hookContainer->getHookNames();
1260
1261 if ( !$hookNames ) {
1262 return '';
1263 }
1264
1265 sort( $hookNames );
1266
1267 $ret = [];
1268 $this->addTocSection( 'version-hooks', 'mw-version-hooks' );
1269 $ret[] = Html::element(
1270 'h2',
1271 [ 'id' => 'mw-version-hooks' ],
1272 $this->msg( 'version-hooks' )->text()
1273 );
1274 $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
1275 $ret[] = Html::openElement( 'tr' );
1276 $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
1277 $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
1278 $ret[] = Html::closeElement( 'tr' );
1279
1280 foreach ( $hookNames as $name ) {
1281 $handlers = $hookContainer->getHandlerDescriptions( $name );
1282
1283 $ret[] = Html::openElement( 'tr' );
1284 $ret[] = Html::element( 'td', [], $name );
1285 // @phan-suppress-next-line SecurityCheck-DoubleEscaped See FIXME in listToText
1286 $ret[] = Html::element( 'td', [], $this->listToText( $handlers ) );
1287 $ret[] = Html::closeElement( 'tr' );
1288 }
1289
1290 $ret[] = Html::closeElement( 'table' );
1291
1292 return implode( "\n", $ret );
1293 }
1294
1295 private function openExtType( ?string $text = null, ?string $name = null ): string {
1296 $out = '';
1297
1298 $opt = [ 'class' => 'wikitable plainlinks mw-installed-software' ];
1299
1300 if ( $name ) {
1301 $opt['id'] = "sv-$name";
1302 }
1303
1304 $out .= Html::openElement( 'table', $opt );
1305
1306 if ( $text !== null ) {
1307 $out .= Html::element( 'caption', [], $text );
1308 }
1309
1310 if ( $name && $text !== null ) {
1311 $this->addTocSubSection( $text, "sv-$name" );
1312 }
1313
1314 $firstHeadingMsg = ( $name === 'credits-skin' )
1315 ? 'version-skin-colheader-name'
1316 : 'version-ext-colheader-name';
1317
1318 $out .= $this->getTableHeaderHtml( [
1319 $this->msg( $firstHeadingMsg )->text(),
1320 $this->msg( 'version-ext-colheader-version' )->text(),
1321 $this->msg( 'version-ext-colheader-license' )->text(),
1322 $this->msg( 'version-ext-colheader-description' )->text(),
1323 $this->msg( 'version-ext-colheader-credits' )->text()
1324 ] );
1325
1326 return $out;
1327 }
1328
1337 private function getTableHeaderHtml( $headers ): string {
1338 $out = '';
1339 $out .= Html::openElement( 'thead' );
1340 $out .= Html::openElement( 'tr' );
1341 foreach ( $headers as $header ) {
1342 $out .= Html::element( 'th', [ 'scope' => 'col' ], $header );
1343 }
1344 $out .= Html::closeElement( 'tr' );
1345 $out .= Html::closeElement( 'thead' );
1346 return $out;
1347 }
1348
1354 private function IPInfo() {
1355 $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
1356
1357 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1358 }
1359
1380 public function listAuthors( $authors, $extName, $extDir ): string {
1381 $hasOthers = false;
1382 $linkRenderer = $this->getLinkRenderer();
1383
1384 $list = [];
1385 $authors = (array)$authors;
1386
1387 // Special case: if the authors array has only one item and it is "...",
1388 // it should not be rendered as the "version-poweredby-others" i18n msg,
1389 // but rather as "version-poweredby-various" i18n msg instead.
1390 if ( count( $authors ) === 1 && $authors[0] === '...' ) {
1391 // Link to the extension's or skin's AUTHORS or CREDITS file, if there is
1392 // such a file; otherwise just return the i18n msg as-is
1393 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1394 return $linkRenderer->makeLink(
1395 $this->getPageTitle( "Credits/$extName" ),
1396 $this->msg( 'version-poweredby-various' )->text()
1397 );
1398 } else {
1399 return $this->msg( 'version-poweredby-various' )->escaped();
1400 }
1401 }
1402
1403 // Otherwise, if we have an actual array that has more than one item,
1404 // process each array item as usual
1405 foreach ( $authors as $item ) {
1406 if ( $item instanceof HtmlArmor ) {
1407 $list[] = HtmlArmor::getHtml( $item );
1408 } elseif ( $item === '...' ) {
1409 $hasOthers = true;
1410
1411 if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1412 $text = $linkRenderer->makeLink(
1413 $this->getPageTitle( "Credits/$extName" ),
1414 $this->msg( 'version-poweredby-others' )->text()
1415 );
1416 } else {
1417 $text = $this->msg( 'version-poweredby-others' )->escaped();
1418 }
1419 $list[] = $text;
1420 } elseif ( str_ends_with( $item, ' ...]' ) ) {
1421 $hasOthers = true;
1422 $list[] = $this->getOutput()->parseInlineAsInterface(
1423 substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
1424 );
1425 } else {
1426 $list[] = $this->getOutput()->parseInlineAsInterface( $item );
1427 }
1428 }
1429
1430 if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1431 $list[] = $linkRenderer->makeLink(
1432 $this->getPageTitle( "Credits/$extName" ),
1433 $this->msg( 'version-poweredby-others' )->text()
1434 );
1435 }
1436
1437 return $this->listToText( $list, false );
1438 }
1439
1449 private function listToText( array $list, bool $sort = true ): string {
1450 if ( !$list ) {
1451 return '';
1452 }
1453 if ( $sort ) {
1454 sort( $list );
1455 }
1456
1457 return $this->getLanguage()
1458 ->listToText( array_map( [ self::class, 'arrayToString' ], $list ) );
1459 }
1460
1470 public static function arrayToString( $list ) {
1471 if ( is_array( $list ) && count( $list ) == 1 ) {
1472 $list = $list[0];
1473 }
1474 if ( $list instanceof Closure ) {
1475 // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
1476 return 'Closure';
1477 } elseif ( is_object( $list ) ) {
1478 return wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
1479 } elseif ( !is_array( $list ) ) {
1480 return $list;
1481 } else {
1482 if ( is_object( $list[0] ) ) {
1483 $class = get_class( $list[0] );
1484 } else {
1485 $class = $list[0];
1486 }
1487
1488 return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
1489 }
1490 }
1491
1497 public static function getGitHeadSha1( $dir ) {
1498 wfDeprecated( __METHOD__, '1.41' );
1499 return ( new GitInfo( $dir ) )->getHeadSHA1();
1500 }
1501
1506 public function getEntryPointInfo() {
1507 $config = $this->getConfig();
1508 $scriptPath = $config->get( MainConfigNames::ScriptPath ) ?: '/';
1509
1510 $entryPoints = [
1511 'version-entrypoints-articlepath' => $config->get( MainConfigNames::ArticlePath ),
1512 'version-entrypoints-scriptpath' => $scriptPath,
1513 'version-entrypoints-index-php' => wfScript( 'index' ),
1514 'version-entrypoints-api-php' => wfScript( 'api' ),
1515 'version-entrypoints-rest-php' => wfScript( 'rest' ),
1516 ];
1517
1518 $language = $this->getLanguage();
1519 $thAttributes = [
1520 'dir' => $language->getDir(),
1521 'lang' => $language->getHtmlCode(),
1522 'scope' => 'col'
1523 ];
1524
1525 $this->addTocSection( 'version-entrypoints', 'mw-version-entrypoints' );
1526
1527 $out = Html::element(
1528 'h2',
1529 [ 'id' => 'mw-version-entrypoints' ],
1530 $this->msg( 'version-entrypoints' )->text()
1531 ) .
1532 Html::openElement( 'table',
1533 [
1534 'class' => 'wikitable plainlinks',
1535 'id' => 'mw-version-entrypoints-table',
1536 'dir' => 'ltr',
1537 'lang' => 'en'
1538 ]
1539 ) .
1540 Html::openElement( 'thead' ) .
1541 Html::openElement( 'tr' ) .
1542 Html::element(
1543 'th',
1544 $thAttributes,
1545 $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1546 ) .
1547 Html::element(
1548 'th',
1549 $thAttributes,
1550 $this->msg( 'version-entrypoints-header-url' )->text()
1551 ) .
1552 Html::closeElement( 'tr' ) .
1553 Html::closeElement( 'thead' );
1554
1555 foreach ( $entryPoints as $message => $value ) {
1556 $url = $this->urlUtils->expand( $value, PROTO_RELATIVE );
1557 $out .= Html::openElement( 'tr' ) .
1558 Html::rawElement( 'td', [], $this->msg( $message )->parse() ) .
1559 Html::rawElement( 'td', [],
1560 Html::rawElement(
1561 'code',
1562 [],
1563 $this->msg( new RawMessage( "[$url $value]" ) )->parse()
1564 )
1565 ) .
1566 Html::closeElement( 'tr' );
1567 }
1568
1569 $out .= Html::closeElement( 'table' );
1570
1571 return $out;
1572 }
1573
1575 protected function getGroupName() {
1576 return 'wiki';
1577 }
1578}
1579
1584class_alias( SpecialVersion::class, 'SpecialVersion' );
const CACHE_ANYTHING
Definition Defines.php:72
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:23
const PROTO_RELATIVE
Definition Defines.php:219
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(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
Definition Setup.php:551
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
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:43
Base class for language-specific code.
Definition Language.php:69
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:144
static listParam(array $list, $type=ListType::AND)
Definition Message.php:1352
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:32
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.
__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:33
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:18
Interface for configuration instances.
Definition Config.php:18
Provide primary and replica IDatabase connections.
$source
element(SerializerNode $parent, SerializerNode $node, $contents)
msg( $key,... $params)