MediaWiki  master
SpecialVersion.php
Go to the documentation of this file.
1 <?php
27 
33 class SpecialVersion extends SpecialPage {
34 
38  protected $firstExtOpened = false;
39 
43  protected $coreId = '';
44 
48  protected static $extensionTypes = false;
49 
51  private $parser;
52 
56  public function __construct( Parser $parser ) {
57  parent::__construct( 'Version' );
58  $this->parser = $parser;
59  }
60 
68  public static function getCredits( ExtensionRegistry $reg, Config $conf ): array {
69  $credits = $conf->get( 'ExtensionCredits' );
70  foreach ( $reg->getAllThings() as $name => $credit ) {
71  $credits[$credit['type']][] = $credit;
72  }
73  return $credits;
74  }
75 
80  public function execute( $par ) {
81  global $IP;
82  $config = $this->getConfig();
83  $credits = self::getCredits( ExtensionRegistry::getInstance(), $config );
84 
85  $this->setHeaders();
86  $this->outputHeader();
87  $out = $this->getOutput();
88  $out->allowClickjacking();
89 
90  // Explode the sub page information into useful bits
91  $parts = explode( '/', (string)$par );
92  $extNode = null;
93  if ( isset( $parts[1] ) ) {
94  $extName = str_replace( '_', ' ', $parts[1] );
95  // Find it!
96  foreach ( $credits as $group => $extensions ) {
97  foreach ( $extensions as $ext ) {
98  if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
99  $extNode = &$ext;
100  break 2;
101  }
102  }
103  }
104  if ( !$extNode ) {
105  $out->setStatusCode( 404 );
106  }
107  } else {
108  $extName = 'MediaWiki';
109  }
110 
111  // Now figure out what to do
112  switch ( strtolower( $parts[0] ) ) {
113  case 'credits':
114  $out->addModuleStyles( 'mediawiki.special.version' );
115 
116  $wikiText = '{{int:version-credits-not-found}}';
117  if ( $extName === 'MediaWiki' ) {
118  $wikiText = file_get_contents( $IP . '/CREDITS' );
119  // Put the contributor list into columns
120  $wikiText = str_replace(
121  [ '<!-- BEGIN CONTRIBUTOR LIST -->', '<!-- END CONTRIBUTOR LIST -->' ],
122  [ '<div class="mw-version-credits">', '</div>' ],
123  $wikiText );
124  } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
125  $file = ExtensionInfo::getAuthorsFileName( dirname( $extNode['path'] ) );
126  if ( $file ) {
127  $wikiText = file_get_contents( $file );
128  if ( substr( $file, -4 ) === '.txt' ) {
129  $wikiText = Html::element(
130  'pre',
131  [
132  'lang' => 'en',
133  'dir' => 'ltr',
134  ],
135  $wikiText
136  );
137  }
138  }
139  }
140 
141  $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
142  $out->addWikiTextAsInterface( $wikiText );
143  break;
144 
145  case 'license':
146  $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
147 
148  $licenseFound = false;
149 
150  if ( $extName === 'MediaWiki' ) {
151  $out->addWikiTextAsInterface(
152  file_get_contents( $IP . '/COPYING' )
153  );
154  $licenseFound = true;
155  } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
156  $files = ExtensionInfo::getLicenseFileNames( dirname( $extNode['path'] ) );
157 
158  if ( count( $files ) ) {
159  $licenseFound = true;
160  foreach ( $files as $file ) {
161  $out->addWikiTextAsInterface(
163  'pre',
164  [
165  'lang' => 'en',
166  'dir' => 'ltr',
167  ],
168  file_get_contents( $file )
169  )
170  );
171  }
172  }
173  }
174  if ( !$licenseFound ) {
175  $out->addWikiTextAsInterface( '{{int:version-license-not-found}}' );
176  }
177  break;
178  default:
179  $out->addModuleStyles( 'mediawiki.special.version' );
180  $out->addWikiTextAsInterface(
181  self::getMediaWikiCredits() .
182  self::softwareInformation() .
183  $this->getEntryPointInfo()
184  );
185  $out->addHTML(
186  $this->getSkinCredits( $credits ) .
187  $this->getExtensionCredits( $credits ) .
188  $this->getExternalLibraries( $credits ) .
189  $this->getParserTags() .
190  $this->getParserFunctionHooks()
191  );
192  $out->addWikiTextAsInterface( $this->getWgHooks() );
193  $out->addHTML( $this->IPInfo() );
194 
195  break;
196  }
197  }
198 
204  private static function getMediaWikiCredits() {
205  $ret = Xml::element(
206  'h2',
207  [ 'id' => 'mw-version-license' ],
208  wfMessage( 'version-license' )->text()
209  );
210 
211  // This text is always left-to-right.
212  $ret .= '<div class="plainlinks">';
213  $ret .= "__NOTOC__
215  " . '<div class="mw-version-license-info">' .
216  wfMessage( 'version-license-info' )->text() .
217  '</div>';
218  $ret .= '</div>';
219 
220  return str_replace( "\t\t", '', $ret ) . "\n";
221  }
222 
228  public static function getCopyrightAndAuthorList() {
229  global $wgLang;
230 
231  if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
232  $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
233  wfMessage( 'version-poweredby-others' )->text() . ']';
234  } else {
235  $othersLink = '[[Special:Version/Credits|' .
236  wfMessage( 'version-poweredby-others' )->text() . ']]';
237  }
238 
239  $translatorsLink = '[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
240  wfMessage( 'version-poweredby-translators' )->text() . ']';
241 
242  $authorList = [
243  'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
244  'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
245  'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
246  'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
247  'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
248  'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
249  'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
250  'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
251  'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
252  'Kunal Mehta', 'James D. Forrester', 'Brian Wolff', 'Adam Shorland',
253  'DannyS712', 'Ori Livneh',
254  $othersLink, $translatorsLink
255  ];
256 
257  return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
258  $wgLang->listToText( $authorList ) )->text();
259  }
260 
266  private static function getSoftwareInformation() {
267  $dbr = wfGetDB( DB_REPLICA );
268 
269  // Put the software in an array of form 'name' => 'version'. All messages should
270  // be loaded here, so feel free to use wfMessage in the 'name'. Wikitext
271  // can be used both in the name and value.
272  $software = [
273  '[https://www.mediawiki.org/ MediaWiki]' => self::getVersionLinked(),
274  '[https://php.net/ PHP]' => PHP_VERSION . " (" . PHP_SAPI . ")",
275  $dbr->getSoftwareLink() => $dbr->getServerInfo(),
276  ];
277 
278  if ( defined( 'INTL_ICU_VERSION' ) ) {
279  $software['[http://site.icu-project.org/ ICU]'] = INTL_ICU_VERSION;
280  }
281 
282  // Allow a hook to add/remove items.
283  Hooks::runner()->onSoftwareInfo( $software );
284 
285  return $software;
286  }
287 
293  private static function softwareInformation() {
294  $out = Xml::element(
295  'h2',
296  [ 'id' => 'mw-version-software' ],
297  wfMessage( 'version-software' )->text()
298  ) .
299  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] ) .
300  "<tr>
301  <th>" . wfMessage( 'version-software-product' )->text() . "</th>
302  <th>" . wfMessage( 'version-software-version' )->text() . "</th>
303  </tr>\n";
304 
305  foreach ( self::getSoftwareInformation() as $name => $version ) {
306  $out .= "<tr>
307  <td>" . $name . "</td>
308  <td dir=\"ltr\">" . $version . "</td>
309  </tr>\n";
310  }
311 
312  return $out . Xml::closeElement( 'table' );
313  }
314 
322  public static function getVersion( $flags = '', $lang = null ) {
323  global $IP;
324 
325  $gitInfo = self::getGitHeadSha1( $IP );
326  if ( !$gitInfo ) {
327  $version = MW_VERSION;
328  } elseif ( $flags === 'nodb' ) {
329  $shortSha1 = substr( $gitInfo, 0, 7 );
330  $version = MW_VERSION . " ($shortSha1)";
331  } else {
332  $shortSha1 = substr( $gitInfo, 0, 7 );
333  $msg = wfMessage( 'parentheses' );
334  if ( $lang !== null ) {
335  $msg->inLanguage( $lang );
336  }
337  $shortSha1 = $msg->params( $shortSha1 )->escaped();
338  $version = MW_VERSION . ' ' . $shortSha1;
339  }
340 
341  return $version;
342  }
343 
351  public static function getVersionLinked() {
352  $gitVersion = self::getVersionLinkedGit();
353  if ( $gitVersion ) {
354  $v = $gitVersion;
355  } else {
356  $v = MW_VERSION; // fallback
357  }
358 
359  return $v;
360  }
361 
365  private static function getMWVersionLinked() {
366  $versionUrl = "";
367  if ( Hooks::runner()->onSpecialVersionVersionUrl( MW_VERSION, $versionUrl ) ) {
368  $versionParts = [];
369  preg_match( "/^(\d+\.\d+)/", MW_VERSION, $versionParts );
370  $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
371  }
372 
373  return '[' . $versionUrl . ' ' . MW_VERSION . ']';
374  }
375 
381  private static function getVersionLinkedGit() {
382  global $IP, $wgLang;
383 
384  $gitInfo = new GitInfo( $IP );
385  $headSHA1 = $gitInfo->getHeadSHA1();
386  if ( !$headSHA1 ) {
387  return false;
388  }
389 
390  $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
391 
392  $gitHeadUrl = $gitInfo->getHeadViewUrl();
393  if ( $gitHeadUrl !== false ) {
394  $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
395  }
396 
397  $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
398  if ( $gitHeadCommitDate ) {
399  $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
400  }
401 
402  return self::getMWVersionLinked() . " $shortSHA1";
403  }
404 
415  public static function getExtensionTypes() {
416  if ( self::$extensionTypes === false ) {
417  self::$extensionTypes = [
418  'specialpage' => wfMessage( 'version-specialpages' )->text(),
419  'editor' => wfMessage( 'version-editors' )->text(),
420  'parserhook' => wfMessage( 'version-parserhooks' )->text(),
421  'variable' => wfMessage( 'version-variables' )->text(),
422  'media' => wfMessage( 'version-mediahandlers' )->text(),
423  'antispam' => wfMessage( 'version-antispam' )->text(),
424  'skin' => wfMessage( 'version-skins' )->text(),
425  'api' => wfMessage( 'version-api' )->text(),
426  'other' => wfMessage( 'version-other' )->text(),
427  ];
428 
429  Hooks::runner()->onExtensionTypes( self::$extensionTypes );
430  }
431 
432  return self::$extensionTypes;
433  }
434 
444  public static function getExtensionTypeName( $type ) {
445  $types = self::getExtensionTypes();
446 
447  return $types[$type] ?? $types['other'];
448  }
449 
456  private function getExtensionCredits( array $credits ) {
457  if (
458  !$credits ||
459  // Skins are displayed separately, see getSkinCredits()
460  ( count( $credits ) === 1 && isset( $credits['skin'] ) )
461  ) {
462  return '';
463  }
464 
466 
467  $out = Xml::element(
468  'h2',
469  [ 'id' => 'mw-version-ext' ],
470  $this->msg( 'version-extensions' )->text()
471  ) .
472  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ] );
473 
474  // Make sure the 'other' type is set to an array.
475  if ( !array_key_exists( 'other', $credits ) ) {
476  $credits['other'] = [];
477  }
478 
479  // Find all extensions that do not have a valid type and give them the type 'other'.
480  foreach ( $credits as $type => $extensions ) {
481  if ( !array_key_exists( $type, $extensionTypes ) ) {
482  $credits['other'] = array_merge( $credits['other'], $extensions );
483  }
484  }
485 
486  $this->firstExtOpened = false;
487  // Loop through the extension categories to display their extensions in the list.
488  foreach ( $extensionTypes as $type => $message ) {
489  // Skins have a separate section
490  if ( $type !== 'other' && $type !== 'skin' ) {
491  $out .= $this->getExtensionCategory( $type, $message, $credits[$type] ?? [] );
492  }
493  }
494 
495  // We want the 'other' type to be last in the list.
496  $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'], $credits['other'] );
497 
498  $out .= Xml::closeElement( 'table' );
499 
500  return $out;
501  }
502 
509  private function getSkinCredits( array $credits ) {
510  if ( !isset( $credits['skin'] ) || count( $credits['skin'] ) === 0 ) {
511  return '';
512  }
513 
514  $out = Xml::element(
515  'h2',
516  [ 'id' => 'mw-version-skin' ],
517  $this->msg( 'version-skins' )->text()
518  ) .
519  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ] );
520 
521  $this->firstExtOpened = false;
522  $out .= $this->getExtensionCategory( 'skin', null, $credits['skin'] );
523 
524  $out .= Xml::closeElement( 'table' );
525 
526  return $out;
527  }
528 
535  protected function getExternalLibraries( array $credits ) {
536  global $IP;
537  $paths = [
538  "$IP/vendor/composer/installed.json"
539  ];
540 
542  foreach ( $extensionTypes as $type => $message ) {
543  if ( !isset( $credits[$type] ) || $credits[$type] === [] ) {
544  continue;
545  }
546  foreach ( $credits[$type] as $extension ) {
547  if ( !isset( $extension['path'] ) ) {
548  continue;
549  }
550  $paths[] = dirname( $extension['path'] ) . '/vendor/composer/installed.json';
551  }
552  }
553 
554  $dependencies = [];
555 
556  foreach ( $paths as $path ) {
557  if ( !file_exists( $path ) ) {
558  continue;
559  }
560 
561  $installed = new ComposerInstalled( $path );
562 
563  $dependencies += $installed->getInstalledDependencies();
564  }
565 
566  if ( $dependencies === [] ) {
567  return '';
568  }
569 
570  ksort( $dependencies );
571 
572  $out = Html::element(
573  'h2',
574  [ 'id' => 'mw-version-libraries' ],
575  $this->msg( 'version-libraries' )->text()
576  );
577  $out .= Html::openElement(
578  'table',
579  [ 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' ]
580  );
581  $out .= Html::openElement( 'tr' )
582  . Html::element( 'th', [], $this->msg( 'version-libraries-library' )->text() )
583  . Html::element( 'th', [], $this->msg( 'version-libraries-version' )->text() )
584  . Html::element( 'th', [], $this->msg( 'version-libraries-license' )->text() )
585  . Html::element( 'th', [], $this->msg( 'version-libraries-description' )->text() )
586  . Html::element( 'th', [], $this->msg( 'version-libraries-authors' )->text() )
587  . Html::closeElement( 'tr' );
588 
589  foreach ( $dependencies as $name => $info ) {
590  if ( !is_array( $info ) || strpos( $info['type'], 'mediawiki-' ) === 0 ) {
591  // Skip any extensions or skins since they'll be listed
592  // in their proper section
593  continue;
594  }
595  $authors = array_map( static function ( $arr ) {
596  // If a homepage is set, link to it
597  if ( isset( $arr['homepage'] ) ) {
598  return "[{$arr['homepage']} {$arr['name']}]";
599  }
600  return $arr['name'];
601  }, $info['authors'] );
602  $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
603 
604  // We can safely assume that the libraries' names and descriptions
605  // are written in English and aren't going to be translated,
606  // so set appropriate lang and dir attributes
607  $out .= Html::openElement( 'tr', [
608  // Add an anchor so docs can link easily to the version of
609  // this specific library
611  "mw-version-library-$name"
612  ) ] )
614  'td',
615  [],
617  "https://packagist.org/packages/$name", $name,
618  true, '',
619  [ 'class' => 'mw-version-library-name' ]
620  )
621  )
622  . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
623  // @phan-suppress-next-line SecurityCheck-DoubleEscaped false positive
624  . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['licenses'] ) )
625  . Html::element( 'td', [ 'lang' => 'en', 'dir' => 'ltr' ], $info['description'] )
626  . Html::rawElement( 'td', [], $authors )
627  . Html::closeElement( 'tr' );
628  }
629  $out .= Html::closeElement( 'table' );
630 
631  return $out;
632  }
633 
639  protected function getParserTags() {
640  $tags = $this->parser->getTags();
641 
642  if ( count( $tags ) ) {
643  $out = Html::rawElement(
644  'h2',
645  [
646  'class' => 'mw-headline plainlinks',
647  'id' => 'mw-version-parser-extensiontags',
648  ],
649  // @phan-suppress-next-line SecurityCheck-DoubleEscaped Using false for escape is safe
651  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
652  $this->msg( 'version-parser-extensiontags' )->parse(),
653  false /* msg()->parse() already escapes */
654  )
655  );
656 
657  array_walk( $tags, static function ( &$value ) {
658  // Bidirectional isolation improves readability in RTL wikis
659  $value = Html::element(
660  'bdi',
661  // Prevent < and > from slipping to another line
662  [
663  'style' => 'white-space: nowrap;',
664  ],
665  "<$value>"
666  );
667  } );
668 
669  $out .= $this->listToText( $tags );
670  } else {
671  $out = '';
672  }
673 
674  return $out;
675  }
676 
682  protected function getParserFunctionHooks() {
683  $fhooks = $this->parser->getFunctionHooks();
684  if ( count( $fhooks ) ) {
685  $out = Html::rawElement(
686  'h2',
687  [
688  'class' => 'mw-headline plainlinks',
689  'id' => 'mw-version-parser-function-hooks',
690  ],
691  // @phan-suppress-next-line SecurityCheck-DoubleEscaped Using false for escape is safe
693  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
694  $this->msg( 'version-parser-function-hooks' )->parse(),
695  false /* msg()->parse() already escapes */
696  )
697  );
698 
699  $out .= $this->listToText( $fhooks );
700  } else {
701  $out = '';
702  }
703 
704  return $out;
705  }
706 
716  protected function getExtensionCategory( $type, $message, array $creditsGroup ) {
717  $config = $this->getConfig();
718  $credits = $config->get( 'ExtensionCredits' );
719 
720  $out = '';
721 
722  if ( $creditsGroup ) {
723  $out .= $this->openExtType( $message, 'credits-' . $type );
724 
725  usort( $creditsGroup, [ $this, 'compare' ] );
726 
727  foreach ( $creditsGroup as $extension ) {
728  $out .= $this->getCreditsForExtension( $type, $extension );
729  }
730  }
731 
732  return $out;
733  }
734 
741  public function compare( $a, $b ) {
742  return $this->getLanguage()->lc( $a['name'] ) <=> $this->getLanguage()->lc( $b['name'] );
743  }
744 
763  public function getCreditsForExtension( $type, array $extension ) {
764  $out = $this->getOutput();
765 
766  // We must obtain the information for all the bits and pieces!
767  // ... such as extension names and links
768  if ( isset( $extension['namemsg'] ) ) {
769  // Localized name of extension
770  $extensionName = $this->msg( $extension['namemsg'] )->text();
771  } elseif ( isset( $extension['name'] ) ) {
772  // Non localized version
773  $extensionName = $extension['name'];
774  } else {
775  $extensionName = $this->msg( 'version-no-ext-name' )->text();
776  }
777 
778  if ( isset( $extension['url'] ) ) {
779  $extensionNameLink = Linker::makeExternalLink(
780  $extension['url'],
781  $extensionName,
782  true,
783  '',
784  [ 'class' => 'mw-version-ext-name' ]
785  );
786  } else {
787  $extensionNameLink = htmlspecialchars( $extensionName );
788  }
789 
790  // ... and the version information
791  // If the extension path is set we will check that directory for GIT
792  // metadata in an attempt to extract date and vcs commit metadata.
793  $canonicalVersion = '&ndash;';
794  $extensionPath = null;
795  $vcsVersion = null;
796  $vcsLink = null;
797  $vcsDate = null;
798 
799  if ( isset( $extension['version'] ) ) {
800  $canonicalVersion = $out->parseInlineAsInterface( $extension['version'] );
801  }
802 
803  if ( isset( $extension['path'] ) ) {
804  global $IP;
805  $extensionPath = dirname( $extension['path'] );
806  if ( $this->coreId == '' ) {
807  wfDebug( 'Looking up core head id' );
808  $coreHeadSHA1 = self::getGitHeadSha1( $IP );
809  if ( $coreHeadSHA1 ) {
810  $this->coreId = $coreHeadSHA1;
811  }
812  }
814  $memcKey = $cache->makeKey(
815  'specialversion-ext-version-text', $extension['path'], $this->coreId
816  );
817  list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
818 
819  if ( !$vcsVersion ) {
820  wfDebug( "Getting VCS info for extension {$extension['name']}" );
821  $gitInfo = new GitInfo( $extensionPath );
822  $vcsVersion = $gitInfo->getHeadSHA1();
823  if ( $vcsVersion !== false ) {
824  $vcsVersion = substr( $vcsVersion, 0, 7 );
825  $vcsLink = $gitInfo->getHeadViewUrl();
826  $vcsDate = $gitInfo->getHeadCommitDate();
827  }
828  $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
829  } else {
830  wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
831  }
832  }
833 
834  $versionString = Html::rawElement(
835  'span',
836  [ 'class' => 'mw-version-ext-version' ],
837  $canonicalVersion
838  );
839 
840  if ( $vcsVersion ) {
841  if ( $vcsLink ) {
842  $vcsVerString = Linker::makeExternalLink(
843  $vcsLink,
844  $this->msg( 'version-version', $vcsVersion )->text(),
845  true,
846  '',
847  [ 'class' => 'mw-version-ext-vcs-version' ]
848  );
849  } else {
850  $vcsVerString = Html::element( 'span',
851  [ 'class' => 'mw-version-ext-vcs-version' ],
852  "({$vcsVersion})"
853  );
854  }
855  $versionString .= " {$vcsVerString}";
856 
857  if ( $vcsDate ) {
858  $versionString .= ' ' . Html::element( 'span', [
859  'class' => 'mw-version-ext-vcs-timestamp',
860  'dir' => $this->getLanguage()->getDir(),
861  ], $this->getLanguage()->timeanddate( $vcsDate, true ) );
862  }
863  $versionString = Html::rawElement( 'span',
864  [ 'class' => 'mw-version-ext-meta-version' ],
865  $versionString
866  );
867  }
868 
869  // ... and license information; if a license file exists we
870  // will link to it
871  $licenseLink = '';
872  if ( isset( $extension['name'] ) ) {
873  $licenseName = null;
874  if ( isset( $extension['license-name'] ) ) {
875  $licenseName = new HtmlArmor( $out->parseInlineAsInterface( $extension['license-name'] ) );
876  } elseif ( ExtensionInfo::getLicenseFileNames( $extensionPath ) ) {
877  $licenseName = $this->msg( 'version-ext-license' )->text();
878  }
879  if ( $licenseName !== null ) {
880  $licenseLink = $this->getLinkRenderer()->makeLink(
881  $this->getPageTitle( 'License/' . $extension['name'] ),
882  $licenseName,
883  [
884  'class' => 'mw-version-ext-license',
885  'dir' => 'auto',
886  ]
887  );
888  }
889  }
890 
891  // ... and generate the description; which can be a parameterized l10n message
892  // in the form [ <msgname>, <parameter>, <parameter>... ] or just a straight
893  // up string
894  if ( isset( $extension['descriptionmsg'] ) ) {
895  // Localized description of extension
896  $descriptionMsg = $extension['descriptionmsg'];
897 
898  if ( is_array( $descriptionMsg ) ) {
899  $descriptionMsgKey = array_shift( $descriptionMsg );
900  $descriptionMsg = array_map( 'htmlspecialchars', $descriptionMsg ); // For sanity
901  $description = $this->msg( $descriptionMsgKey, ...$descriptionMsg )->text();
902  } else {
903  $description = $this->msg( $descriptionMsg )->text();
904  }
905  } elseif ( isset( $extension['description'] ) ) {
906  // Non localized version
907  $description = $extension['description'];
908  } else {
909  $description = '';
910  }
911  $description = $out->parseInlineAsInterface( $description );
912 
913  // ... now get the authors for this extension
914  $authors = $extension['author'] ?? [];
915  $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
916 
917  // Finally! Create the table
918  $html = Html::openElement( 'tr', [
919  'class' => 'mw-version-ext',
920  'id' => Sanitizer::escapeIdForAttribute( 'mw-version-ext-' . $type . '-' . $extension['name'] )
921  ]
922  );
923 
924  $html .= Html::rawElement( 'td', [], $extensionNameLink );
925  $html .= Html::rawElement( 'td', [], $versionString );
926  $html .= Html::rawElement( 'td', [], $licenseLink );
927  $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-description' ], $description );
928  $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-authors' ], $authors );
929 
930  $html .= Html::closeElement( 'tr' );
931 
932  return $html;
933  }
934 
940  private function getWgHooks() {
942 
943  if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
944  $myWgHooks = $wgHooks;
945  ksort( $myWgHooks );
946 
947  $ret = [];
948  $ret[] = '== {{int:version-hooks}} ==';
949  $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
950  $ret[] = Html::openElement( 'tr' );
951  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
952  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
953  $ret[] = Html::closeElement( 'tr' );
954 
955  foreach ( $myWgHooks as $hook => $hooks ) {
956  $ret[] = Html::openElement( 'tr' );
957  $ret[] = Html::element( 'td', [], $hook );
958  // @phan-suppress-next-line SecurityCheck-DoubleEscaped false positive
959  $ret[] = Html::element( 'td', [], $this->listToText( $hooks ) );
960  $ret[] = Html::closeElement( 'tr' );
961  }
962 
963  $ret[] = Html::closeElement( 'table' );
964 
965  return implode( "\n", $ret );
966  }
967 
968  return '';
969  }
970 
971  private function openExtType( $text = null, $name = null ) {
972  $out = '';
973 
974  $opt = [ 'colspan' => 5 ];
975  if ( $this->firstExtOpened ) {
976  // Insert a spacing line
977  $out .= Html::rawElement( 'tr', [ 'class' => 'sv-space' ],
978  Html::element( 'td', $opt )
979  );
980  }
981  $this->firstExtOpened = true;
982 
983  if ( $name ) {
984  $opt['id'] = "sv-$name";
985  }
986 
987  if ( $text !== null ) {
988  $out .= Html::rawElement( 'tr', [],
989  Html::element( 'th', $opt, $text )
990  );
991  }
992 
993  $firstHeadingMsg = ( $name === 'credits-skin' )
994  ? 'version-skin-colheader-name'
995  : 'version-ext-colheader-name';
996  $out .= Html::openElement( 'tr' );
997  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
998  $this->msg( $firstHeadingMsg )->text() );
999  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
1000  $this->msg( 'version-ext-colheader-version' )->text() );
1001  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
1002  $this->msg( 'version-ext-colheader-license' )->text() );
1003  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
1004  $this->msg( 'version-ext-colheader-description' )->text() );
1005  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
1006  $this->msg( 'version-ext-colheader-credits' )->text() );
1007  $out .= Html::closeElement( 'tr' );
1008 
1009  return $out;
1010  }
1011 
1017  private function IPInfo() {
1018  $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
1019 
1020  return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1021  }
1022 
1044  public function listAuthors( $authors, $extName, $extDir ) {
1045  $hasOthers = false;
1046  $linkRenderer = $this->getLinkRenderer();
1047 
1048  $list = [];
1049  $authors = (array)$authors;
1050 
1051  // Special case: if the authors array has only one item and it is "...",
1052  // it should not be rendered as the "version-poweredby-others" i18n msg,
1053  // but rather as "version-poweredby-various" i18n msg instead.
1054  if ( count( $authors ) === 1 && $authors[0] === '...' ) {
1055  // Link to the extension's or skin's AUTHORS or CREDITS file, if there is
1056  // such a file; otherwise just return the i18n msg as-is
1057  if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1058  return $linkRenderer->makeLink(
1059  $this->getPageTitle( "Credits/$extName" ),
1060  $this->msg( 'version-poweredby-various' )->text()
1061  );
1062  } else {
1063  return $this->msg( 'version-poweredby-various' )->escaped();
1064  }
1065  }
1066 
1067  // Otherwise, if we have an actual array that has more than one item,
1068  // process each array item as usual
1069  foreach ( $authors as $item ) {
1070  if ( $item == '...' ) {
1071  $hasOthers = true;
1072 
1073  if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1074  $text = $linkRenderer->makeLink(
1075  $this->getPageTitle( "Credits/$extName" ),
1076  $this->msg( 'version-poweredby-others' )->text()
1077  );
1078  } else {
1079  $text = $this->msg( 'version-poweredby-others' )->escaped();
1080  }
1081  $list[] = $text;
1082  } elseif ( substr( $item, -5 ) == ' ...]' ) {
1083  $hasOthers = true;
1084  $list[] = $this->getOutput()->parseInlineAsInterface(
1085  substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
1086  );
1087  } else {
1088  $list[] = $this->getOutput()->parseInlineAsInterface( $item );
1089  }
1090  }
1091 
1092  if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1093  $list[] = $linkRenderer->makeLink(
1094  $this->getPageTitle( "Credits/$extName" ),
1095  $this->msg( 'version-poweredby-others' )->text()
1096  );
1097  }
1098 
1099  return $this->listToText( $list, false );
1100  }
1101 
1114  public static function getExtAuthorsFileName( $extDir ) {
1115  wfDeprecated( __METHOD__, '1.35' );
1116  return ExtensionInfo::getAuthorsFileName( $extDir );
1117  }
1118 
1131  public static function getExtLicenseFileName( $extDir ) {
1132  wfDeprecated( __METHOD__, '1.35' );
1133  $licenses = ExtensionInfo::getLicenseFileNames( $extDir );
1134  if ( count( $licenses ) === 0 ) {
1135  return false;
1136  }
1137  return $licenses[0];
1138  }
1139 
1148  public function listToText( $list, $sort = true ) {
1149  if ( !count( $list ) ) {
1150  return '';
1151  }
1152  if ( $sort ) {
1153  sort( $list );
1154  }
1155 
1156  return $this->getLanguage()
1157  ->listToText( array_map( [ __CLASS__, 'arrayToString' ], $list ) );
1158  }
1159 
1168  public static function arrayToString( $list ) {
1169  if ( is_array( $list ) && count( $list ) == 1 ) {
1170  $list = $list[0];
1171  }
1172  if ( $list instanceof Closure ) {
1173  // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
1174  return 'Closure';
1175  } elseif ( is_object( $list ) ) {
1176  $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
1177 
1178  return $class;
1179  } elseif ( !is_array( $list ) ) {
1180  return $list;
1181  } else {
1182  if ( is_object( $list[0] ) ) {
1183  $class = get_class( $list[0] );
1184  } else {
1185  $class = $list[0];
1186  }
1187 
1188  return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
1189  }
1190  }
1191 
1196  public static function getGitHeadSha1( $dir ) {
1197  $repo = new GitInfo( $dir );
1198 
1199  return $repo->getHeadSHA1();
1200  }
1201 
1206  public static function getGitCurrentBranch( $dir ) {
1207  $repo = new GitInfo( $dir );
1208  return $repo->getCurrentBranch();
1209  }
1210 
1215  public function getEntryPointInfo() {
1216  $config = $this->getConfig();
1217  $scriptPath = $config->get( 'ScriptPath' ) ?: '/';
1218 
1219  $entryPoints = [
1220  'version-entrypoints-articlepath' => $config->get( 'ArticlePath' ),
1221  'version-entrypoints-scriptpath' => $scriptPath,
1222  'version-entrypoints-index-php' => wfScript( 'index' ),
1223  'version-entrypoints-api-php' => wfScript( 'api' ),
1224  'version-entrypoints-rest-php' => wfScript( 'rest' ),
1225  ];
1226 
1227  $language = $this->getLanguage();
1228  $thAttribures = [
1229  'dir' => $language->getDir(),
1230  'lang' => $language->getHtmlCode()
1231  ];
1232  $out = Html::element(
1233  'h2',
1234  [ 'id' => 'mw-version-entrypoints' ],
1235  $this->msg( 'version-entrypoints' )->text()
1236  ) .
1237  Html::openElement( 'table',
1238  [
1239  'class' => 'wikitable plainlinks',
1240  'id' => 'mw-version-entrypoints-table',
1241  'dir' => 'ltr',
1242  'lang' => 'en'
1243  ]
1244  ) .
1245  Html::openElement( 'tr' ) .
1246  Html::element(
1247  'th',
1248  $thAttribures,
1249  $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1250  ) .
1251  Html::element(
1252  'th',
1253  $thAttribures,
1254  $this->msg( 'version-entrypoints-header-url' )->text()
1255  ) .
1256  Html::closeElement( 'tr' );
1257 
1258  foreach ( $entryPoints as $message => $value ) {
1259  $url = wfExpandUrl( $value, PROTO_RELATIVE );
1260  $out .= Html::openElement( 'tr' ) .
1261  // ->plain() looks like it should be ->parse(), but this function
1262  // returns wikitext, not HTML, boo
1263  Html::rawElement( 'td', [], $this->msg( $message )->plain() ) .
1264  Html::rawElement( 'td', [], Html::rawElement( 'code', [], "[$url $value]" ) ) .
1265  Html::closeElement( 'tr' );
1266  }
1267 
1268  $out .= Html::closeElement( 'table' );
1269 
1270  return $out;
1271  }
1272 
1273  protected function getGroupName() {
1274  return 'wiki';
1275  }
1276 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:80
SpecialVersion\openExtType
openExtType( $text=null, $name=null)
Definition: SpecialVersion.php:971
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:744
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:912
SpecialVersion\listAuthors
listAuthors( $authors, $extName, $extDir)
Return a formatted unsorted list of authors.
Definition: SpecialVersion.php:1044
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
SpecialVersion\getExtAuthorsFileName
static getExtAuthorsFileName( $extDir)
Obtains the full path of an extensions AUTHORS or CREDITS file if one exists.
Definition: SpecialVersion.php:1114
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:85
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:790
SpecialVersion\listToText
listToText( $list, $sort=true)
Convert an array of items into a list for display.
Definition: SpecialVersion.php:1148
SpecialVersion\getMediaWikiCredits
static getMediaWikiCredits()
Returns wiki text showing the license information.
Definition: SpecialVersion.php:204
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
SpecialVersion\getCreditsForExtension
getCreditsForExtension( $type, array $extension)
Creates and formats a version line for a single extension.
Definition: SpecialVersion.php:763
SpecialVersion\$coreId
string $coreId
The current rev id/SHA hash of MediaWiki core.
Definition: SpecialVersion.php:43
Sanitizer\escapeIdForAttribute
static escapeIdForAttribute( $id, $mode=self::ID_PRIMARY)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid HTM...
Definition: Sanitizer.php:812
SpecialVersion\compare
compare( $a, $b)
Callback to sort extensions by type.
Definition: SpecialVersion.php:741
SpecialVersion\getCopyrightAndAuthorList
static getCopyrightAndAuthorList()
Get the "MediaWiki is copyright 2001-20xx by lots of cool folks" text.
Definition: SpecialVersion.php:228
SpecialVersion\getExtensionCategory
getExtensionCategory( $type, $message, array $creditsGroup)
Creates and returns the HTML for a single extension category.
Definition: SpecialVersion.php:716
ExtensionRegistry
The Registry loads JSON files, and uses a Processor to extract information from them.
Definition: ExtensionRegistry.php:15
MW_VERSION
const MW_VERSION
The running version of MediaWiki.
Definition: Defines.php:36
ExtensionRegistry\getAllThings
getAllThings()
Get credits information about all installed extensions and skins.
Definition: ExtensionRegistry.php:650
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1186
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:830
SpecialVersion\getParserFunctionHooks
getParserFunctionHooks()
Obtains a list of installed parser function hooks and the associated H2 header.
Definition: SpecialVersion.php:682
SpecialVersion\getExtensionCredits
getExtensionCredits(array $credits)
Generate wikitext showing the name, URL, author and description of each extension.
Definition: SpecialVersion.php:456
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:110
$wgLang
$wgLang
Definition: Setup.php:831
SpecialVersion\getVersionLinked
static getVersionLinked()
Return a wikitext-formatted string of the MediaWiki version with a link to the Git SHA1 of head if av...
Definition: SpecialVersion.php:351
SpecialVersion\getSkinCredits
getSkinCredits(array $credits)
Generate wikitext showing the name, URL, author and description of each skin.
Definition: SpecialVersion.php:509
$dbr
$dbr
Definition: testCompression.php:54
SpecialVersion\arrayToString
static arrayToString( $list)
Convert an array or object to a string for display.
Definition: SpecialVersion.php:1168
SpecialVersion\softwareInformation
static softwareInformation()
Returns HTML showing the third party software versions (apache, php, mysql).
Definition: SpecialVersion.php:293
SpecialVersion\__construct
__construct(Parser $parser)
Definition: SpecialVersion.php:56
Html\closeElement
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:316
ExtensionRegistry\getInstance
static getInstance()
Definition: ExtensionRegistry.php:134
Config
Interface for configuration instances.
Definition: Config.php:30
SpecialVersion\getExternalLibraries
getExternalLibraries(array $credits)
Generate an HTML table for external libraries that are installed.
Definition: SpecialVersion.php:535
SpecialVersion\IPInfo
IPInfo()
Get information about client's IP address.
Definition: SpecialVersion.php:1017
PROTO_RELATIVE
const PROTO_RELATIVE
Definition: Defines.php:194
$wgHooks
$wgHooks
Global list of hooks.
Definition: DefaultSettings.php:8677
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:878
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
SpecialVersion\getSoftwareInformation
static getSoftwareInformation()
Helper for self::softwareInformation().
Definition: SpecialVersion.php:266
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2284
SpecialVersion\getExtensionTypeName
static getExtensionTypeName( $type)
Returns the internationalized name for an extension type.
Definition: SpecialVersion.php:444
Config\get
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2202
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
ObjectCache\getInstance
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:74
SpecialVersion\getMWVersionLinked
static getMWVersionLinked()
Definition: SpecialVersion.php:365
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:618
SpecialVersion\getExtensionTypes
static getExtensionTypes()
Returns an array with the base extension types.
Definition: SpecialVersion.php:415
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:1011
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
SpecialVersion\getWgHooks
getWgHooks()
Generate wikitext showing hooks in $wgHooks.
Definition: SpecialVersion.php:940
SpecialVersion\$extensionTypes
static string[] false $extensionTypes
Lazy initialized key/value with message content.
Definition: SpecialVersion.php:48
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
SpecialVersion\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialVersion.php:1273
SpecialVersion\getExtLicenseFileName
static getExtLicenseFileName( $extDir)
Obtains the full path of an extensions COPYING or LICENSE file if one exists.
Definition: SpecialVersion.php:1131
SpecialVersion\execute
execute( $par)
main()
Definition: SpecialVersion.php:80
SpecialVersion\getVersion
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
Definition: SpecialVersion.php:322
GitInfo
@newable
Definition: GitInfo.php:34
SpecialVersion\getGitCurrentBranch
static getGitCurrentBranch( $dir)
Definition: SpecialVersion.php:1206
SpecialVersion\getCredits
static getCredits(ExtensionRegistry $reg, Config $conf)
Definition: SpecialVersion.php:68
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:43
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:780
SpecialVersion
Give information about the version of MediaWiki, PHP, the DB and extensions.
Definition: SpecialVersion.php:33
SpecialVersion\getGitHeadSha1
static getGitHeadSha1( $dir)
Definition: SpecialVersion.php:1196
Parser
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition: Parser.php:91
SpecialVersion\getEntryPointInfo
getEntryPointInfo()
Get the list of entry points and their URLs.
Definition: SpecialVersion.php:1215
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:1028
SpecialVersion\$firstExtOpened
bool $firstExtOpened
Definition: SpecialVersion.php:38
SpecialVersion\getVersionLinkedGit
static getVersionLinkedGit()
Definition: SpecialVersion.php:381
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:119
$cache
$cache
Definition: mcc.php:33
$path
$path
Definition: NoLocalSettings.php:25
Html\openElement
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:252
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
SpecialVersion\$parser
Parser $parser
Definition: SpecialVersion.php:51
$ext
if(!is_readable( $file)) $ext
Definition: router.php:48
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
SpecialVersion\getParserTags
getParserTags()
Obtains a list of installed parser tags and the associated H2 header.
Definition: SpecialVersion.php:639
$IP
$IP
Definition: WebStart.php:49
MWTimestamp\getLocalInstance
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
Definition: MWTimestamp.php:173
$wgSpecialVersionShowHooks
$wgSpecialVersionShowHooks
Show the contents of $wgHooks in Special:Version.
Definition: DefaultSettings.php:7407
MediaWiki\ExtensionInfo
Definition: ExtensionInfo.php:8
ComposerInstalled
Reads an installed.json file and provides accessors to get what is installed.
Definition: ComposerInstalled.php:9
SpecialPage\outputHeader
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Definition: SpecialPage.php:709
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:474
$type
$type
Definition: testCompression.php:52