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->setPreventClickjacking( false );
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->getHooks() );
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['[https://icu.unicode.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 );
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 getHooks() {
941  if ( $this->getConfig()->get( 'SpecialVersionShowHooks' ) && count( $this->getConfig()->get( 'Hooks' ) ) ) {
942  $myHooks = $this->getConfig()->get( 'Hooks' );
943  ksort( $myHooks );
944 
945  $ret = [];
946  $ret[] = '== {{int:version-hooks}} ==';
947  $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
948  $ret[] = Html::openElement( 'tr' );
949  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
950  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
951  $ret[] = Html::closeElement( 'tr' );
952 
953  foreach ( $myHooks as $hook => $hooks ) {
954  $ret[] = Html::openElement( 'tr' );
955  $ret[] = Html::element( 'td', [], $hook );
956  // @phan-suppress-next-line SecurityCheck-DoubleEscaped false positive
957  $ret[] = Html::element( 'td', [], $this->listToText( $hooks ) );
958  $ret[] = Html::closeElement( 'tr' );
959  }
960 
961  $ret[] = Html::closeElement( 'table' );
962 
963  return implode( "\n", $ret );
964  }
965 
966  return '';
967  }
968 
969  private function openExtType( $text = null, $name = null ) {
970  $out = '';
971 
972  $opt = [ 'colspan' => 5 ];
973  if ( $this->firstExtOpened ) {
974  // Insert a spacing line
975  $out .= Html::rawElement( 'tr', [ 'class' => 'sv-space' ],
976  Html::element( 'td', $opt )
977  );
978  }
979  $this->firstExtOpened = true;
980 
981  if ( $name ) {
982  $opt['id'] = "sv-$name";
983  }
984 
985  if ( $text !== null ) {
986  $out .= Html::rawElement( 'tr', [],
987  Html::element( 'th', $opt, $text )
988  );
989  }
990 
991  $firstHeadingMsg = ( $name === 'credits-skin' )
992  ? 'version-skin-colheader-name'
993  : 'version-ext-colheader-name';
994  $out .= Html::openElement( 'tr' );
995  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
996  $this->msg( $firstHeadingMsg )->text() );
997  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
998  $this->msg( 'version-ext-colheader-version' )->text() );
999  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
1000  $this->msg( 'version-ext-colheader-license' )->text() );
1001  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
1002  $this->msg( 'version-ext-colheader-description' )->text() );
1003  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
1004  $this->msg( 'version-ext-colheader-credits' )->text() );
1005  $out .= Html::closeElement( 'tr' );
1006 
1007  return $out;
1008  }
1009 
1015  private function IPInfo() {
1016  $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
1017 
1018  return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
1019  }
1020 
1042  public function listAuthors( $authors, $extName, $extDir ) {
1043  $hasOthers = false;
1044  $linkRenderer = $this->getLinkRenderer();
1045 
1046  $list = [];
1047  $authors = (array)$authors;
1048 
1049  // Special case: if the authors array has only one item and it is "...",
1050  // it should not be rendered as the "version-poweredby-others" i18n msg,
1051  // but rather as "version-poweredby-various" i18n msg instead.
1052  if ( count( $authors ) === 1 && $authors[0] === '...' ) {
1053  // Link to the extension's or skin's AUTHORS or CREDITS file, if there is
1054  // such a file; otherwise just return the i18n msg as-is
1055  if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1056  return $linkRenderer->makeLink(
1057  $this->getPageTitle( "Credits/$extName" ),
1058  $this->msg( 'version-poweredby-various' )->text()
1059  );
1060  } else {
1061  return $this->msg( 'version-poweredby-various' )->escaped();
1062  }
1063  }
1064 
1065  // Otherwise, if we have an actual array that has more than one item,
1066  // process each array item as usual
1067  foreach ( $authors as $item ) {
1068  if ( $item == '...' ) {
1069  $hasOthers = true;
1070 
1071  if ( $extName && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1072  $text = $linkRenderer->makeLink(
1073  $this->getPageTitle( "Credits/$extName" ),
1074  $this->msg( 'version-poweredby-others' )->text()
1075  );
1076  } else {
1077  $text = $this->msg( 'version-poweredby-others' )->escaped();
1078  }
1079  $list[] = $text;
1080  } elseif ( substr( $item, -5 ) == ' ...]' ) {
1081  $hasOthers = true;
1082  $list[] = $this->getOutput()->parseInlineAsInterface(
1083  substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
1084  );
1085  } else {
1086  $list[] = $this->getOutput()->parseInlineAsInterface( $item );
1087  }
1088  }
1089 
1090  if ( $extName && !$hasOthers && ExtensionInfo::getAuthorsFileName( $extDir ) ) {
1091  $list[] = $linkRenderer->makeLink(
1092  $this->getPageTitle( "Credits/$extName" ),
1093  $this->msg( 'version-poweredby-others' )->text()
1094  );
1095  }
1096 
1097  return $this->listToText( $list, false );
1098  }
1099 
1112  public static function getExtAuthorsFileName( $extDir ) {
1113  wfDeprecated( __METHOD__, '1.35' );
1114  return ExtensionInfo::getAuthorsFileName( $extDir );
1115  }
1116 
1129  public static function getExtLicenseFileName( $extDir ) {
1130  wfDeprecated( __METHOD__, '1.35' );
1131  $licenses = ExtensionInfo::getLicenseFileNames( $extDir );
1132  if ( count( $licenses ) === 0 ) {
1133  return false;
1134  }
1135  return $licenses[0];
1136  }
1137 
1146  public function listToText( $list, $sort = true ) {
1147  if ( !count( $list ) ) {
1148  return '';
1149  }
1150  if ( $sort ) {
1151  sort( $list );
1152  }
1153 
1154  return $this->getLanguage()
1155  ->listToText( array_map( [ __CLASS__, 'arrayToString' ], $list ) );
1156  }
1157 
1166  public static function arrayToString( $list ) {
1167  if ( is_array( $list ) && count( $list ) == 1 ) {
1168  $list = $list[0];
1169  }
1170  if ( $list instanceof Closure ) {
1171  // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
1172  return 'Closure';
1173  } elseif ( is_object( $list ) ) {
1174  $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
1175 
1176  return $class;
1177  } elseif ( !is_array( $list ) ) {
1178  return $list;
1179  } else {
1180  if ( is_object( $list[0] ) ) {
1181  $class = get_class( $list[0] );
1182  } else {
1183  $class = $list[0];
1184  }
1185 
1186  return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
1187  }
1188  }
1189 
1194  public static function getGitHeadSha1( $dir ) {
1195  $repo = new GitInfo( $dir );
1196 
1197  return $repo->getHeadSHA1();
1198  }
1199 
1204  public static function getGitCurrentBranch( $dir ) {
1205  $repo = new GitInfo( $dir );
1206  return $repo->getCurrentBranch();
1207  }
1208 
1213  public function getEntryPointInfo() {
1214  $config = $this->getConfig();
1215  $scriptPath = $config->get( 'ScriptPath' ) ?: '/';
1216 
1217  $entryPoints = [
1218  'version-entrypoints-articlepath' => $config->get( 'ArticlePath' ),
1219  'version-entrypoints-scriptpath' => $scriptPath,
1220  'version-entrypoints-index-php' => wfScript( 'index' ),
1221  'version-entrypoints-api-php' => wfScript( 'api' ),
1222  'version-entrypoints-rest-php' => wfScript( 'rest' ),
1223  ];
1224 
1225  $language = $this->getLanguage();
1226  $thAttribures = [
1227  'dir' => $language->getDir(),
1228  'lang' => $language->getHtmlCode()
1229  ];
1230  $out = Html::element(
1231  'h2',
1232  [ 'id' => 'mw-version-entrypoints' ],
1233  $this->msg( 'version-entrypoints' )->text()
1234  ) .
1235  Html::openElement( 'table',
1236  [
1237  'class' => 'wikitable plainlinks',
1238  'id' => 'mw-version-entrypoints-table',
1239  'dir' => 'ltr',
1240  'lang' => 'en'
1241  ]
1242  ) .
1243  Html::openElement( 'tr' ) .
1244  Html::element(
1245  'th',
1246  $thAttribures,
1247  $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1248  ) .
1249  Html::element(
1250  'th',
1251  $thAttribures,
1252  $this->msg( 'version-entrypoints-header-url' )->text()
1253  ) .
1254  Html::closeElement( 'tr' );
1255 
1256  foreach ( $entryPoints as $message => $value ) {
1257  $url = wfExpandUrl( $value, PROTO_RELATIVE );
1258  $out .= Html::openElement( 'tr' ) .
1259  // ->plain() looks like it should be ->parse(), but this function
1260  // returns wikitext, not HTML, boo
1261  Html::rawElement( 'td', [], $this->msg( $message )->plain() ) .
1262  Html::rawElement( 'td', [], Html::rawElement( 'code', [], "[$url $value]" ) ) .
1263  Html::closeElement( 'tr' );
1264  }
1265 
1266  $out .= Html::closeElement( 'table' );
1267 
1268  return $out;
1269  }
1270 
1271  protected function getGroupName() {
1272  return 'wiki';
1273  }
1274 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:80
SpecialVersion\openExtType
openExtType( $text=null, $name=null)
Definition: SpecialVersion.php:969
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:768
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:936
SpecialVersion\listAuthors
listAuthors( $authors, $extName, $extDir)
Return a formatted unsorted list of authors.
Definition: SpecialVersion.php:1042
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:1112
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:85
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:814
SpecialVersion\listToText
listToText( $list, $sort=true)
Convert an array of items into a list for display.
Definition: SpecialVersion.php:1146
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:811
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:1167
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:854
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
SpecialVersion\getHooks
getHooks()
Generate wikitext showing hooks in $wgHooks.
Definition: SpecialVersion.php:940
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:111
$wgLang
$wgLang
Definition: Setup.php:861
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:1166
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:319
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:1015
PROTO_RELATIVE
const PROTO_RELATIVE
Definition: Defines.php:194
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:902
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:2268
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:2186
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:42
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:642
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:1025
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
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:1271
SpecialVersion\getExtLicenseFileName
static getExtLicenseFileName( $extDir)
Obtains the full path of an extensions COPYING or LICENSE file if one exists.
Definition: SpecialVersion.php:1129
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
Definition: GitInfo.php:35
SpecialVersion\getGitCurrentBranch
static getGitCurrentBranch( $dir)
Definition: SpecialVersion.php:1204
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:804
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:1194
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:1213
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:1052
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:120
$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:255
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:213
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:235
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
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:733
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