MediaWiki  1.31.0
SpecialVersion.php
Go to the documentation of this file.
1 <?php
31 class SpecialVersion extends SpecialPage {
32  protected $firstExtOpened = false;
33 
37  protected $coreId = '';
38 
39  protected static $extensionTypes = false;
40 
41  public function __construct() {
42  parent::__construct( 'Version' );
43  }
44 
49  public function execute( $par ) {
51 
52  $this->setHeaders();
53  $this->outputHeader();
54  $out = $this->getOutput();
55  $out->allowClickjacking();
56 
57  // Explode the sub page information into useful bits
58  $parts = explode( '/', (string)$par );
59  $extNode = null;
60  if ( isset( $parts[1] ) ) {
61  $extName = str_replace( '_', ' ', $parts[1] );
62  // Find it!
63  foreach ( $wgExtensionCredits as $group => $extensions ) {
64  foreach ( $extensions as $ext ) {
65  if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
66  $extNode = &$ext;
67  break 2;
68  }
69  }
70  }
71  if ( !$extNode ) {
72  $out->setStatusCode( 404 );
73  }
74  } else {
75  $extName = 'MediaWiki';
76  }
77 
78  // Now figure out what to do
79  switch ( strtolower( $parts[0] ) ) {
80  case 'credits':
81  $out->addModuleStyles( 'mediawiki.special.version' );
82 
83  $wikiText = '{{int:version-credits-not-found}}';
84  if ( $extName === 'MediaWiki' ) {
85  $wikiText = file_get_contents( $IP . '/CREDITS' );
86  // Put the contributor list into columns
87  $wikiText = str_replace(
88  [ '<!-- BEGIN CONTRIBUTOR LIST -->', '<!-- END CONTRIBUTOR LIST -->' ],
89  [ '<div class="mw-version-credits">', '</div>' ],
90  $wikiText );
91  } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
92  $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
93  if ( $file ) {
94  $wikiText = file_get_contents( $file );
95  if ( substr( $file, -4 ) === '.txt' ) {
96  $wikiText = Html::element(
97  'pre',
98  [
99  'lang' => 'en',
100  'dir' => 'ltr',
101  ],
102  $wikiText
103  );
104  }
105  }
106  }
107 
108  $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
109  $out->addWikiText( $wikiText );
110  break;
111 
112  case 'license':
113  $wikiText = '{{int:version-license-not-found}}';
114  if ( $extName === 'MediaWiki' ) {
115  $wikiText = file_get_contents( $IP . '/COPYING' );
116  } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
117  $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
118  if ( $file ) {
119  $wikiText = file_get_contents( $file );
120  $wikiText = Html::element(
121  'pre',
122  [
123  'lang' => 'en',
124  'dir' => 'ltr',
125  ],
126  $wikiText
127  );
128  }
129  }
130 
131  $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
132  $out->addWikiText( $wikiText );
133  break;
134 
135  default:
136  $out->addModuleStyles( 'mediawiki.special.version' );
137  $out->addWikiText(
138  $this->getMediaWikiCredits() .
139  $this->softwareInformation() .
140  $this->getEntryPointInfo()
141  );
142  $out->addHTML(
143  $this->getSkinCredits() .
144  $this->getExtensionCredits() .
145  $this->getExternalLibraries() .
146  $this->getParserTags() .
147  $this->getParserFunctionHooks()
148  );
149  $out->addWikiText( $this->getWgHooks() );
150  $out->addHTML( $this->IPInfo() );
151 
152  break;
153  }
154  }
155 
161  private static function getMediaWikiCredits() {
162  $ret = Xml::element(
163  'h2',
164  [ 'id' => 'mw-version-license' ],
165  wfMessage( 'version-license' )->text()
166  );
167 
168  // This text is always left-to-right.
169  $ret .= '<div class="plainlinks">';
170  $ret .= "__NOTOC__
172  " . '<div class="mw-version-license-info">' .
173  wfMessage( 'version-license-info' )->text() .
174  '</div>';
175  $ret .= '</div>';
176 
177  return str_replace( "\t\t", '', $ret ) . "\n";
178  }
179 
185  public static function getCopyrightAndAuthorList() {
186  global $wgLang;
187 
188  if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
189  $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
190  wfMessage( 'version-poweredby-others' )->text() . ']';
191  } else {
192  $othersLink = '[[Special:Version/Credits|' .
193  wfMessage( 'version-poweredby-others' )->text() . ']]';
194  }
195 
196  $translatorsLink = '[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
197  wfMessage( 'version-poweredby-translators' )->text() . ']';
198 
199  $authorList = [
200  'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
201  'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
202  'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
203  'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
204  'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
205  'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
206  'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
207  'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
208  'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
209  $othersLink, $translatorsLink
210  ];
211 
212  return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
213  $wgLang->listToText( $authorList ) )->text();
214  }
215 
221  public static function softwareInformation() {
222  $dbr = wfGetDB( DB_REPLICA );
223 
224  // Put the software in an array of form 'name' => 'version'. All messages should
225  // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
226  // wikimarkup can be used.
227  $software = [];
228  $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
229  if ( wfIsHHVM() ) {
230  $software['[http://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")";
231  } else {
232  $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")";
233  }
234  $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
235 
236  if ( IcuCollation::getICUVersion() ) {
237  $software['[http://site.icu-project.org/ ICU]'] = IcuCollation::getICUVersion();
238  }
239 
240  // Allow a hook to add/remove items.
241  Hooks::run( 'SoftwareInfo', [ &$software ] );
242 
243  $out = Xml::element(
244  'h2',
245  [ 'id' => 'mw-version-software' ],
246  wfMessage( 'version-software' )->text()
247  ) .
248  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] ) .
249  "<tr>
250  <th>" . wfMessage( 'version-software-product' )->text() . "</th>
251  <th>" . wfMessage( 'version-software-version' )->text() . "</th>
252  </tr>\n";
253 
254  foreach ( $software as $name => $version ) {
255  $out .= "<tr>
256  <td>" . $name . "</td>
257  <td dir=\"ltr\">" . $version . "</td>
258  </tr>\n";
259  }
260 
261  return $out . Xml::closeElement( 'table' );
262  }
263 
271  public static function getVersion( $flags = '', $lang = null ) {
273 
274  $gitInfo = self::getGitHeadSha1( $IP );
275  if ( !$gitInfo ) {
276  $version = $wgVersion;
277  } elseif ( $flags === 'nodb' ) {
278  $shortSha1 = substr( $gitInfo, 0, 7 );
279  $version = "$wgVersion ($shortSha1)";
280  } else {
281  $shortSha1 = substr( $gitInfo, 0, 7 );
282  $msg = wfMessage( 'parentheses' );
283  if ( $lang !== null ) {
284  $msg->inLanguage( $lang );
285  }
286  $shortSha1 = $msg->params( $shortSha1 )->escaped();
287  $version = "$wgVersion $shortSha1";
288  }
289 
290  return $version;
291  }
292 
300  public static function getVersionLinked() {
302 
303  $gitVersion = self::getVersionLinkedGit();
304  if ( $gitVersion ) {
305  $v = $gitVersion;
306  } else {
307  $v = $wgVersion; // fallback
308  }
309 
310  return $v;
311  }
312 
316  private static function getwgVersionLinked() {
318  $versionUrl = "";
319  if ( Hooks::run( 'SpecialVersionVersionUrl', [ $wgVersion, &$versionUrl ] ) ) {
320  $versionParts = [];
321  preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
322  $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
323  }
324 
325  return "[$versionUrl $wgVersion]";
326  }
327 
333  private static function getVersionLinkedGit() {
334  global $IP, $wgLang;
335 
336  $gitInfo = new GitInfo( $IP );
337  $headSHA1 = $gitInfo->getHeadSHA1();
338  if ( !$headSHA1 ) {
339  return false;
340  }
341 
342  $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
343 
344  $gitHeadUrl = $gitInfo->getHeadViewUrl();
345  if ( $gitHeadUrl !== false ) {
346  $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
347  }
348 
349  $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
350  if ( $gitHeadCommitDate ) {
351  $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
352  }
353 
354  return self::getwgVersionLinked() . " $shortSHA1";
355  }
356 
367  public static function getExtensionTypes() {
368  if ( self::$extensionTypes === false ) {
369  self::$extensionTypes = [
370  'specialpage' => wfMessage( 'version-specialpages' )->text(),
371  'editor' => wfMessage( 'version-editors' )->text(),
372  'parserhook' => wfMessage( 'version-parserhooks' )->text(),
373  'variable' => wfMessage( 'version-variables' )->text(),
374  'media' => wfMessage( 'version-mediahandlers' )->text(),
375  'antispam' => wfMessage( 'version-antispam' )->text(),
376  'skin' => wfMessage( 'version-skins' )->text(),
377  'api' => wfMessage( 'version-api' )->text(),
378  'other' => wfMessage( 'version-other' )->text(),
379  ];
380 
381  Hooks::run( 'ExtensionTypes', [ &self::$extensionTypes ] );
382  }
383 
384  return self::$extensionTypes;
385  }
386 
396  public static function getExtensionTypeName( $type ) {
397  $types = self::getExtensionTypes();
398 
399  return isset( $types[$type] ) ? $types[$type] : $types['other'];
400  }
401 
407  public function getExtensionCredits() {
409 
410  if (
411  count( $wgExtensionCredits ) === 0 ||
412  // Skins are displayed separately, see getSkinCredits()
413  ( count( $wgExtensionCredits ) === 1 && isset( $wgExtensionCredits['skin'] ) )
414  ) {
415  return '';
416  }
417 
419 
420  $out = Xml::element(
421  'h2',
422  [ 'id' => 'mw-version-ext' ],
423  $this->msg( 'version-extensions' )->text()
424  ) .
425  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ] );
426 
427  // Make sure the 'other' type is set to an array.
428  if ( !array_key_exists( 'other', $wgExtensionCredits ) ) {
429  $wgExtensionCredits['other'] = [];
430  }
431 
432  // Find all extensions that do not have a valid type and give them the type 'other'.
433  foreach ( $wgExtensionCredits as $type => $extensions ) {
434  if ( !array_key_exists( $type, $extensionTypes ) ) {
435  $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions );
436  }
437  }
438 
439  $this->firstExtOpened = false;
440  // Loop through the extension categories to display their extensions in the list.
441  foreach ( $extensionTypes as $type => $message ) {
442  // Skins have a separate section
443  if ( $type !== 'other' && $type !== 'skin' ) {
444  $out .= $this->getExtensionCategory( $type, $message );
445  }
446  }
447 
448  // We want the 'other' type to be last in the list.
449  $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
450 
451  $out .= Xml::closeElement( 'table' );
452 
453  return $out;
454  }
455 
461  public function getSkinCredits() {
463  if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) {
464  return '';
465  }
466 
467  $out = Xml::element(
468  'h2',
469  [ 'id' => 'mw-version-skin' ],
470  $this->msg( 'version-skins' )->text()
471  ) .
472  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ] );
473 
474  $this->firstExtOpened = false;
475  $out .= $this->getExtensionCategory( 'skin', null );
476 
477  $out .= Xml::closeElement( 'table' );
478 
479  return $out;
480  }
481 
487  protected function getExternalLibraries() {
488  global $IP;
489  $path = "$IP/vendor/composer/installed.json";
490  if ( !file_exists( $path ) ) {
491  return '';
492  }
493 
494  $installed = new ComposerInstalled( $path );
496  'h2',
497  [ 'id' => 'mw-version-libraries' ],
498  $this->msg( 'version-libraries' )->text()
499  );
501  'table',
502  [ 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' ]
503  );
504  $out .= Html::openElement( 'tr' )
505  . Html::element( 'th', [], $this->msg( 'version-libraries-library' )->text() )
506  . Html::element( 'th', [], $this->msg( 'version-libraries-version' )->text() )
507  . Html::element( 'th', [], $this->msg( 'version-libraries-license' )->text() )
508  . Html::element( 'th', [], $this->msg( 'version-libraries-description' )->text() )
509  . Html::element( 'th', [], $this->msg( 'version-libraries-authors' )->text() )
510  . Html::closeElement( 'tr' );
511 
512  foreach ( $installed->getInstalledDependencies() as $name => $info ) {
513  if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
514  // Skip any extensions or skins since they'll be listed
515  // in their proper section
516  continue;
517  }
518  $authors = array_map( function ( $arr ) {
519  // If a homepage is set, link to it
520  if ( isset( $arr['homepage'] ) ) {
521  return "[{$arr['homepage']} {$arr['name']}]";
522  }
523  return $arr['name'];
524  }, $info['authors'] );
525  $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
526 
527  // We can safely assume that the libraries' names and descriptions
528  // are written in English and aren't going to be translated,
529  // so set appropriate lang and dir attributes
530  $out .= Html::openElement( 'tr' )
532  'td',
533  [],
535  "https://packagist.org/packages/$name", $name,
536  true, '',
537  [ 'class' => 'mw-version-library-name' ]
538  )
539  )
540  . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
541  . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['licenses'] ) )
542  . Html::element( 'td', [ 'lang' => 'en', 'dir' => 'ltr' ], $info['description'] )
543  . Html::rawElement( 'td', [], $authors )
544  . Html::closeElement( 'tr' );
545  }
546  $out .= Html::closeElement( 'table' );
547 
548  return $out;
549  }
550 
556  protected function getParserTags() {
558 
559  $tags = $wgParser->getTags();
560 
561  if ( count( $tags ) ) {
563  'h2',
564  [
565  'class' => 'mw-headline plainlinks',
566  'id' => 'mw-version-parser-extensiontags',
567  ],
569  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
570  $this->msg( 'version-parser-extensiontags' )->parse(),
571  false /* msg()->parse() already escapes */
572  )
573  );
574 
575  array_walk( $tags, function ( &$value ) {
576  // Bidirectional isolation improves readability in RTL wikis
578  'bdi',
579  // Prevent < and > from slipping to another line
580  [
581  'style' => 'white-space: nowrap;',
582  ],
583  "<$value>"
584  );
585  } );
586 
587  $out .= $this->listToText( $tags );
588  } else {
589  $out = '';
590  }
591 
592  return $out;
593  }
594 
600  protected function getParserFunctionHooks() {
602 
603  $fhooks = $wgParser->getFunctionHooks();
604  if ( count( $fhooks ) ) {
606  'h2',
607  [
608  'class' => 'mw-headline plainlinks',
609  'id' => 'mw-version-parser-function-hooks',
610  ],
612  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
613  $this->msg( 'version-parser-function-hooks' )->parse(),
614  false /* msg()->parse() already escapes */
615  )
616  );
617 
618  $out .= $this->listToText( $fhooks );
619  } else {
620  $out = '';
621  }
622 
623  return $out;
624  }
625 
636  protected function getExtensionCategory( $type, $message ) {
638 
639  $out = '';
640 
641  if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
642  $out .= $this->openExtType( $message, 'credits-' . $type );
643 
644  usort( $wgExtensionCredits[$type], [ $this, 'compare' ] );
645 
646  foreach ( $wgExtensionCredits[$type] as $extension ) {
647  $out .= $this->getCreditsForExtension( $type, $extension );
648  }
649  }
650 
651  return $out;
652  }
653 
660  public function compare( $a, $b ) {
661  if ( $a['name'] === $b['name'] ) {
662  return 0;
663  } else {
664  return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
665  ? 1
666  : -1;
667  }
668  }
669 
688  public function getCreditsForExtension( $type, array $extension ) {
689  $out = $this->getOutput();
690 
691  // We must obtain the information for all the bits and pieces!
692  // ... such as extension names and links
693  if ( isset( $extension['namemsg'] ) ) {
694  // Localized name of extension
695  $extensionName = $this->msg( $extension['namemsg'] )->text();
696  } elseif ( isset( $extension['name'] ) ) {
697  // Non localized version
698  $extensionName = $extension['name'];
699  } else {
700  $extensionName = $this->msg( 'version-no-ext-name' )->text();
701  }
702 
703  if ( isset( $extension['url'] ) ) {
704  $extensionNameLink = Linker::makeExternalLink(
705  $extension['url'],
706  $extensionName,
707  true,
708  '',
709  [ 'class' => 'mw-version-ext-name' ]
710  );
711  } else {
712  $extensionNameLink = $extensionName;
713  }
714 
715  // ... and the version information
716  // If the extension path is set we will check that directory for GIT
717  // metadata in an attempt to extract date and vcs commit metadata.
718  $canonicalVersion = '&ndash;';
719  $extensionPath = null;
720  $vcsVersion = null;
721  $vcsLink = null;
722  $vcsDate = null;
723 
724  if ( isset( $extension['version'] ) ) {
725  $canonicalVersion = $out->parseInline( $extension['version'] );
726  }
727 
728  if ( isset( $extension['path'] ) ) {
729  global $IP;
730  $extensionPath = dirname( $extension['path'] );
731  if ( $this->coreId == '' ) {
732  wfDebug( 'Looking up core head id' );
733  $coreHeadSHA1 = self::getGitHeadSha1( $IP );
734  if ( $coreHeadSHA1 ) {
735  $this->coreId = $coreHeadSHA1;
736  }
737  }
739  $memcKey = $cache->makeKey(
740  'specialversion-ext-version-text', $extension['path'], $this->coreId
741  );
742  list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
743 
744  if ( !$vcsVersion ) {
745  wfDebug( "Getting VCS info for extension {$extension['name']}" );
746  $gitInfo = new GitInfo( $extensionPath );
747  $vcsVersion = $gitInfo->getHeadSHA1();
748  if ( $vcsVersion !== false ) {
749  $vcsVersion = substr( $vcsVersion, 0, 7 );
750  $vcsLink = $gitInfo->getHeadViewUrl();
751  $vcsDate = $gitInfo->getHeadCommitDate();
752  }
753  $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
754  } else {
755  wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
756  }
757  }
758 
759  $versionString = Html::rawElement(
760  'span',
761  [ 'class' => 'mw-version-ext-version' ],
762  $canonicalVersion
763  );
764 
765  if ( $vcsVersion ) {
766  if ( $vcsLink ) {
767  $vcsVerString = Linker::makeExternalLink(
768  $vcsLink,
769  $this->msg( 'version-version', $vcsVersion ),
770  true,
771  '',
772  [ 'class' => 'mw-version-ext-vcs-version' ]
773  );
774  } else {
775  $vcsVerString = Html::element( 'span',
776  [ 'class' => 'mw-version-ext-vcs-version' ],
777  "({$vcsVersion})"
778  );
779  }
780  $versionString .= " {$vcsVerString}";
781 
782  if ( $vcsDate ) {
783  $vcsTimeString = Html::element( 'span',
784  [ 'class' => 'mw-version-ext-vcs-timestamp' ],
785  $this->getLanguage()->timeanddate( $vcsDate, true )
786  );
787  $versionString .= " {$vcsTimeString}";
788  }
789  $versionString = Html::rawElement( 'span',
790  [ 'class' => 'mw-version-ext-meta-version' ],
791  $versionString
792  );
793  }
794 
795  // ... and license information; if a license file exists we
796  // will link to it
797  $licenseLink = '';
798  if ( isset( $extension['name'] ) ) {
799  $licenseName = null;
800  if ( isset( $extension['license-name'] ) ) {
801  $licenseName = new HtmlArmor( $out->parseInline( $extension['license-name'] ) );
802  } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
803  $licenseName = $this->msg( 'version-ext-license' )->text();
804  }
805  if ( $licenseName !== null ) {
806  $licenseLink = $this->getLinkRenderer()->makeLink(
807  $this->getPageTitle( 'License/' . $extension['name'] ),
808  $licenseName,
809  [
810  'class' => 'mw-version-ext-license',
811  'dir' => 'auto',
812  ]
813  );
814  }
815  }
816 
817  // ... and generate the description; which can be a parameterized l10n message
818  // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
819  // up string
820  if ( isset( $extension['descriptionmsg'] ) ) {
821  // Localized description of extension
822  $descriptionMsg = $extension['descriptionmsg'];
823 
824  if ( is_array( $descriptionMsg ) ) {
825  $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
826  array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
827  array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
828  $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
829  } else {
830  $description = $this->msg( $descriptionMsg )->text();
831  }
832  } elseif ( isset( $extension['description'] ) ) {
833  // Non localized version
834  $description = $extension['description'];
835  } else {
836  $description = '';
837  }
838  $description = $out->parseInline( $description );
839 
840  // ... now get the authors for this extension
841  $authors = isset( $extension['author'] ) ? $extension['author'] : [];
842  $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
843 
844  // Finally! Create the table
845  $html = Html::openElement( 'tr', [
846  'class' => 'mw-version-ext',
847  'id' => Sanitizer::escapeIdForAttribute( 'mw-version-ext-' . $type . '-' . $extension['name'] )
848  ]
849  );
850 
851  $html .= Html::rawElement( 'td', [], $extensionNameLink );
852  $html .= Html::rawElement( 'td', [], $versionString );
853  $html .= Html::rawElement( 'td', [], $licenseLink );
854  $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-description' ], $description );
855  $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-authors' ], $authors );
856 
857  $html .= Html::closeElement( 'tr' );
858 
859  return $html;
860  }
861 
867  private function getWgHooks() {
869 
871  $myWgHooks = $wgHooks;
872  ksort( $myWgHooks );
873 
874  $ret = [];
875  $ret[] = '== {{int:version-hooks}} ==';
876  $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
877  $ret[] = Html::openElement( 'tr' );
878  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
879  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
880  $ret[] = Html::closeElement( 'tr' );
881 
882  foreach ( $myWgHooks as $hook => $hooks ) {
883  $ret[] = Html::openElement( 'tr' );
884  $ret[] = Html::element( 'td', [], $hook );
885  $ret[] = Html::element( 'td', [], $this->listToText( $hooks ) );
886  $ret[] = Html::closeElement( 'tr' );
887  }
888 
889  $ret[] = Html::closeElement( 'table' );
890 
891  return implode( "\n", $ret );
892  } else {
893  return '';
894  }
895  }
896 
897  private function openExtType( $text = null, $name = null ) {
898  $out = '';
899 
900  $opt = [ 'colspan' => 5 ];
901  if ( $this->firstExtOpened ) {
902  // Insert a spacing line
903  $out .= Html::rawElement( 'tr', [ 'class' => 'sv-space' ],
904  Html::element( 'td', $opt )
905  );
906  }
907  $this->firstExtOpened = true;
908 
909  if ( $name ) {
910  $opt['id'] = "sv-$name";
911  }
912 
913  if ( $text !== null ) {
914  $out .= Html::rawElement( 'tr', [],
915  Html::element( 'th', $opt, $text )
916  );
917  }
918 
919  $firstHeadingMsg = ( $name === 'credits-skin' )
920  ? 'version-skin-colheader-name'
921  : 'version-ext-colheader-name';
922  $out .= Html::openElement( 'tr' );
923  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
924  $this->msg( $firstHeadingMsg )->text() );
925  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
926  $this->msg( 'version-ext-colheader-version' )->text() );
927  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
928  $this->msg( 'version-ext-colheader-license' )->text() );
929  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
930  $this->msg( 'version-ext-colheader-description' )->text() );
931  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
932  $this->msg( 'version-ext-colheader-credits' )->text() );
933  $out .= Html::closeElement( 'tr' );
934 
935  return $out;
936  }
937 
943  private function IPInfo() {
944  $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
945 
946  return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
947  }
948 
970  public function listAuthors( $authors, $extName, $extDir ) {
971  $hasOthers = false;
972  $linkRenderer = $this->getLinkRenderer();
973 
974  $list = [];
975  foreach ( (array)$authors as $item ) {
976  if ( $item == '...' ) {
977  $hasOthers = true;
978 
979  if ( $extName && $this->getExtAuthorsFileName( $extDir ) ) {
980  $text = $linkRenderer->makeLink(
981  $this->getPageTitle( "Credits/$extName" ),
982  $this->msg( 'version-poweredby-others' )->text()
983  );
984  } else {
985  $text = $this->msg( 'version-poweredby-others' )->escaped();
986  }
987  $list[] = $text;
988  } elseif ( substr( $item, -5 ) == ' ...]' ) {
989  $hasOthers = true;
990  $list[] = $this->getOutput()->parseInline(
991  substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
992  );
993  } else {
994  $list[] = $this->getOutput()->parseInline( $item );
995  }
996  }
997 
998  if ( $extName && !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
999  $list[] = $text = $linkRenderer->makeLink(
1000  $this->getPageTitle( "Credits/$extName" ),
1001  $this->msg( 'version-poweredby-others' )->text()
1002  );
1003  }
1004 
1005  return $this->listToText( $list, false );
1006  }
1007 
1019  public static function getExtAuthorsFileName( $extDir ) {
1020  if ( !$extDir ) {
1021  return false;
1022  }
1023 
1024  foreach ( scandir( $extDir ) as $file ) {
1025  $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
1026  if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt|\.wiki|\.mediawiki)?$/', $file ) &&
1027  is_readable( $fullPath ) &&
1028  is_file( $fullPath )
1029  ) {
1030  return $fullPath;
1031  }
1032  }
1033 
1034  return false;
1035  }
1036 
1048  public static function getExtLicenseFileName( $extDir ) {
1049  if ( !$extDir ) {
1050  return false;
1051  }
1052 
1053  foreach ( scandir( $extDir ) as $file ) {
1054  $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
1055  if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
1056  is_readable( $fullPath ) &&
1057  is_file( $fullPath )
1058  ) {
1059  return $fullPath;
1060  }
1061  }
1062 
1063  return false;
1064  }
1065 
1074  public function listToText( $list, $sort = true ) {
1075  if ( !count( $list ) ) {
1076  return '';
1077  }
1078  if ( $sort ) {
1079  sort( $list );
1080  }
1081 
1082  return $this->getLanguage()
1083  ->listToText( array_map( [ __CLASS__, 'arrayToString' ], $list ) );
1084  }
1085 
1094  public static function arrayToString( $list ) {
1095  if ( is_array( $list ) && count( $list ) == 1 ) {
1096  $list = $list[0];
1097  }
1098  if ( $list instanceof Closure ) {
1099  // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
1100  return 'Closure';
1101  } elseif ( is_object( $list ) ) {
1102  $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
1103 
1104  return $class;
1105  } elseif ( !is_array( $list ) ) {
1106  return $list;
1107  } else {
1108  if ( is_object( $list[0] ) ) {
1109  $class = get_class( $list[0] );
1110  } else {
1111  $class = $list[0];
1112  }
1113 
1114  return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
1115  }
1116  }
1117 
1122  public static function getGitHeadSha1( $dir ) {
1123  $repo = new GitInfo( $dir );
1124 
1125  return $repo->getHeadSHA1();
1126  }
1127 
1132  public static function getGitCurrentBranch( $dir ) {
1133  $repo = new GitInfo( $dir );
1134  return $repo->getCurrentBranch();
1135  }
1136 
1141  public function getEntryPointInfo() {
1143  $scriptPath = $wgScriptPath ? $wgScriptPath : "/";
1144  $entryPoints = [
1145  'version-entrypoints-articlepath' => $wgArticlePath,
1146  'version-entrypoints-scriptpath' => $scriptPath,
1147  'version-entrypoints-index-php' => wfScript( 'index' ),
1148  'version-entrypoints-api-php' => wfScript( 'api' ),
1149  'version-entrypoints-load-php' => wfScript( 'load' ),
1150  ];
1151 
1152  $language = $this->getLanguage();
1153  $thAttribures = [
1154  'dir' => $language->getDir(),
1155  'lang' => $language->getHtmlCode()
1156  ];
1157  $out = Html::element(
1158  'h2',
1159  [ 'id' => 'mw-version-entrypoints' ],
1160  $this->msg( 'version-entrypoints' )->text()
1161  ) .
1162  Html::openElement( 'table',
1163  [
1164  'class' => 'wikitable plainlinks',
1165  'id' => 'mw-version-entrypoints-table',
1166  'dir' => 'ltr',
1167  'lang' => 'en'
1168  ]
1169  ) .
1170  Html::openElement( 'tr' ) .
1171  Html::element(
1172  'th',
1173  $thAttribures,
1174  $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1175  ) .
1176  Html::element(
1177  'th',
1178  $thAttribures,
1179  $this->msg( 'version-entrypoints-header-url' )->text()
1180  ) .
1181  Html::closeElement( 'tr' );
1182 
1183  foreach ( $entryPoints as $message => $value ) {
1184  $url = wfExpandUrl( $value, PROTO_RELATIVE );
1185  $out .= Html::openElement( 'tr' ) .
1186  // ->text() looks like it should be ->parse(), but this function
1187  // returns wikitext, not HTML, boo
1188  Html::rawElement( 'td', [], $this->msg( $message )->text() ) .
1189  Html::rawElement( 'td', [], Html::rawElement( 'code', [], "[$url $value]" ) ) .
1190  Html::closeElement( 'tr' );
1191  }
1192 
1193  $out .= Html::closeElement( 'table' );
1194 
1195  return $out;
1196  }
1197 
1198  protected function getGroupName() {
1199  return 'wiki';
1200  }
1201 }
SpecialVersion\getExtensionCredits
getExtensionCredits()
Generate wikitext showing the name, URL, author and description of each extension.
Definition: SpecialVersion.php:407
SpecialVersion\openExtType
openExtType( $text=null, $name=null)
Definition: SpecialVersion.php:897
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:629
SpecialVersion\__construct
__construct()
Definition: SpecialVersion.php:41
$wgExtensionCredits
$wgExtensionCredits['specialpage'][]
Definition: ReplaceText.php:40
SpecialVersion\listAuthors
listAuthors( $authors, $extName, $extDir)
Return a formatted unsorted list of authors.
Definition: SpecialVersion.php:970
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
SpecialPage\msg
msg( $key)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:747
SpecialVersion\getExtAuthorsFileName
static getExtAuthorsFileName( $extDir)
Obtains the full path of an extensions authors or credits file if one exists.
Definition: SpecialVersion.php:1019
$wgParser
$wgParser
Definition: Setup.php:909
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:676
SpecialVersion\listToText
listToText( $list, $sort=true)
Convert an array of items into a list for display.
Definition: SpecialVersion.php:1074
SpecialVersion\getMediaWikiCredits
static getMediaWikiCredits()
Returns wiki text showing the license information.
Definition: SpecialVersion.php:161
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
$opt
$opt
Definition: postprocess-phan.php:115
SpecialVersion\getCreditsForExtension
getCreditsForExtension( $type, array $extension)
Creates and formats a version line for a single extension.
Definition: SpecialVersion.php:688
captcha-old.count
count
Definition: captcha-old.py:249
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
SpecialVersion\compare
compare( $a, $b)
Callback to sort extensions by type.
Definition: SpecialVersion.php:660
SpecialVersion\getCopyrightAndAuthorList
static getCopyrightAndAuthorList()
Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text.
Definition: SpecialVersion.php:185
$wgVersion
$wgVersion
MediaWiki version number.
Definition: DefaultSettings.php:74
SpecialVersion\$coreId
$coreId
Stores the current rev id/SHA hash of MediaWiki core.
Definition: SpecialVersion.php:37
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:706
SpecialVersion\getParserFunctionHooks
getParserFunctionHooks()
Obtains a list of installed parser function hooks and the associated H2 header.
Definition: SpecialVersion.php:600
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:109
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
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:300
$dbr
$dbr
Definition: testCompression.php:50
SpecialVersion\arrayToString
static arrayToString( $list)
Convert an array or object to a string for display.
Definition: SpecialVersion.php:1094
SpecialVersion\softwareInformation
static softwareInformation()
Returns wiki text showing the third party software versions (apache, php, mysql).
Definition: SpecialVersion.php:221
Html\closeElement
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:309
SpecialVersion\$extensionTypes
static $extensionTypes
Definition: SpecialVersion.php:39
SpecialVersion\IPInfo
IPInfo()
Get information about client's IP address.
Definition: SpecialVersion.php:943
SpecialVersion\getExternalLibraries
getExternalLibraries()
Generate an HTML table for external libraries that are installed.
Definition: SpecialVersion.php:487
$html
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1987
SpecialVersion\getSkinCredits
getSkinCredits()
Generate wikitext showing the name, URL, author and description of each skin.
Definition: SpecialVersion.php:461
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2878
SpecialVersion\getExtensionTypeName
static getExtensionTypeName( $type)
Returns the internationalized name for an extension type.
Definition: SpecialVersion.php:396
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2800
$IP
$IP
Definition: update.php:3
IcuCollation\getICUVersion
static getICUVersion()
Return the version of ICU library used by PHP's intl extension, or false when the extension is not in...
Definition: IcuCollation.php:541
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
$wgLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:484
SpecialVersion\getExtensionTypes
static getExtensionTypes()
Returns an array with the base extension types.
Definition: SpecialVersion.php:367
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:843
wfGetCache
wfGetCache( $cacheType)
Get a specific cache object.
Definition: GlobalFunctions.php:3126
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
SpecialVersion\getWgHooks
getWgHooks()
Generate wikitext showing hooks in $wgHooks.
Definition: SpecialVersion.php:867
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:982
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
$sort
$sort
Definition: profileinfo.php:317
SpecialVersion\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialVersion.php:1198
SpecialVersion\getExtLicenseFileName
static getExtLicenseFileName( $extDir)
Obtains the full path of an extensions copying or license file if one exists.
Definition: SpecialVersion.php:1048
SpecialVersion\getwgVersionLinked
static getwgVersionLinked()
Definition: SpecialVersion.php:316
SpecialVersion\execute
execute( $par)
main()
Definition: SpecialVersion.php:49
SpecialVersion\getVersion
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
Definition: SpecialVersion.php:271
GitInfo
Definition: GitInfo.php:28
$value
$value
Definition: styleTest.css.php:45
SpecialVersion\getGitCurrentBranch
static getGitCurrentBranch( $dir)
Definition: SpecialVersion.php:1132
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:36
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:666
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:102
SpecialVersion
Give information about the version of MediaWiki, PHP, the DB and extensions.
Definition: SpecialVersion.php:31
PROTO_RELATIVE
const PROTO_RELATIVE
Definition: Defines.php:222
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1987
SpecialVersion\getGitHeadSha1
static getGitHeadSha1( $dir)
Definition: SpecialVersion.php:1122
format
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1649
SpecialVersion\getEntryPointInfo
getEntryPointInfo()
Get the list of entry points and their URLs.
Definition: SpecialVersion.php:1141
SpecialVersion\$firstExtOpened
$firstExtOpened
Definition: SpecialVersion.php:32
$wgArticlePath
$wgArticlePath
Definition: img_auth.php:45
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:861
SpecialVersion\getVersionLinkedGit
static getVersionLinkedGit()
Definition: SpecialVersion.php:333
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:118
$cache
$cache
Definition: mcc.php:33
$wgHooks
$wgHooks['ArticleShow'][]
Definition: hooks.txt:108
$path
$path
Definition: NoLocalSettings.php:25
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
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:251
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
SpecialPage\$linkRenderer
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:66
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
SpecialVersion\getParserTags
getParserTags()
Obtains a list of installed parser tags and the associated H2 header.
Definition: SpecialVersion.php:556
wfIsHHVM
wfIsHHVM()
Check if we are running under HHVM.
Definition: GlobalFunctions.php:2020
$wgScriptPath
$wgScriptPath
The path we should point to.
Definition: DefaultSettings.php:137
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
$ext
$ext
Definition: router.php:55
MWTimestamp\getLocalInstance
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
Definition: MWTimestamp.php:204
$wgSpecialVersionShowHooks
$wgSpecialVersionShowHooks
Show the contents of $wgHooks in Special:Version.
Definition: DefaultSettings.php:6233
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:583
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:521
array
the array() calling protocol came about after MediaWiki 1.4rc1.
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:783
$type
$type
Definition: testCompression.php:48
SpecialVersion\getExtensionCategory
getExtensionCategory( $type, $message)
Creates and returns the HTML for a single extension category.
Definition: SpecialVersion.php:636