MediaWiki  1.30.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  " . wfMessage( 'version-license-info' )->text();
173  $ret .= '</div>';
174 
175  return str_replace( "\t\t", '', $ret ) . "\n";
176  }
177 
183  public static function getCopyrightAndAuthorList() {
184  global $wgLang;
185 
186  if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
187  $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
188  wfMessage( 'version-poweredby-others' )->text() . ']';
189  } else {
190  $othersLink = '[[Special:Version/Credits|' .
191  wfMessage( 'version-poweredby-others' )->text() . ']]';
192  }
193 
194  $translatorsLink = '[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
195  wfMessage( 'version-poweredby-translators' )->text() . ']';
196 
197  $authorList = [
198  'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
199  'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
200  'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
201  'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
202  'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
203  'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
204  'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
205  'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
206  'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
207  $othersLink, $translatorsLink
208  ];
209 
210  return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
211  $wgLang->listToText( $authorList ) )->text();
212  }
213 
219  public static function softwareInformation() {
220  $dbr = wfGetDB( DB_REPLICA );
221 
222  // Put the software in an array of form 'name' => 'version'. All messages should
223  // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
224  // wikimarkup can be used.
225  $software = [];
226  $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
227  if ( wfIsHHVM() ) {
228  $software['[http://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")";
229  } else {
230  $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")";
231  }
232  $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
233 
234  if ( IcuCollation::getICUVersion() ) {
235  $software['[http://site.icu-project.org/ ICU]'] = IcuCollation::getICUVersion();
236  }
237 
238  // Allow a hook to add/remove items.
239  Hooks::run( 'SoftwareInfo', [ &$software ] );
240 
241  $out = Xml::element(
242  'h2',
243  [ 'id' => 'mw-version-software' ],
244  wfMessage( 'version-software' )->text()
245  ) .
246  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] ) .
247  "<tr>
248  <th>" . wfMessage( 'version-software-product' )->text() . "</th>
249  <th>" . wfMessage( 'version-software-version' )->text() . "</th>
250  </tr>\n";
251 
252  foreach ( $software as $name => $version ) {
253  $out .= "<tr>
254  <td>" . $name . "</td>
255  <td dir=\"ltr\">" . $version . "</td>
256  </tr>\n";
257  }
258 
259  return $out . Xml::closeElement( 'table' );
260  }
261 
269  public static function getVersion( $flags = '', $lang = null ) {
271 
272  $gitInfo = self::getGitHeadSha1( $IP );
273  if ( !$gitInfo ) {
274  $version = $wgVersion;
275  } elseif ( $flags === 'nodb' ) {
276  $shortSha1 = substr( $gitInfo, 0, 7 );
277  $version = "$wgVersion ($shortSha1)";
278  } else {
279  $shortSha1 = substr( $gitInfo, 0, 7 );
280  $msg = wfMessage( 'parentheses' );
281  if ( $lang !== null ) {
282  $msg->inLanguage( $lang );
283  }
284  $shortSha1 = $msg->params( $shortSha1 )->escaped();
285  $version = "$wgVersion $shortSha1";
286  }
287 
288  return $version;
289  }
290 
298  public static function getVersionLinked() {
300 
301  $gitVersion = self::getVersionLinkedGit();
302  if ( $gitVersion ) {
303  $v = $gitVersion;
304  } else {
305  $v = $wgVersion; // fallback
306  }
307 
308  return $v;
309  }
310 
314  private static function getwgVersionLinked() {
316  $versionUrl = "";
317  if ( Hooks::run( 'SpecialVersionVersionUrl', [ $wgVersion, &$versionUrl ] ) ) {
318  $versionParts = [];
319  preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
320  $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
321  }
322 
323  return "[$versionUrl $wgVersion]";
324  }
325 
331  private static function getVersionLinkedGit() {
332  global $IP, $wgLang;
333 
334  $gitInfo = new GitInfo( $IP );
335  $headSHA1 = $gitInfo->getHeadSHA1();
336  if ( !$headSHA1 ) {
337  return false;
338  }
339 
340  $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
341 
342  $gitHeadUrl = $gitInfo->getHeadViewUrl();
343  if ( $gitHeadUrl !== false ) {
344  $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
345  }
346 
347  $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
348  if ( $gitHeadCommitDate ) {
349  $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
350  }
351 
352  return self::getwgVersionLinked() . " $shortSHA1";
353  }
354 
365  public static function getExtensionTypes() {
366  if ( self::$extensionTypes === false ) {
367  self::$extensionTypes = [
368  'specialpage' => wfMessage( 'version-specialpages' )->text(),
369  'parserhook' => wfMessage( 'version-parserhooks' )->text(),
370  'variable' => wfMessage( 'version-variables' )->text(),
371  'media' => wfMessage( 'version-mediahandlers' )->text(),
372  'antispam' => wfMessage( 'version-antispam' )->text(),
373  'skin' => wfMessage( 'version-skins' )->text(),
374  'api' => wfMessage( 'version-api' )->text(),
375  'other' => wfMessage( 'version-other' )->text(),
376  ];
377 
378  Hooks::run( 'ExtensionTypes', [ &self::$extensionTypes ] );
379  }
380 
381  return self::$extensionTypes;
382  }
383 
393  public static function getExtensionTypeName( $type ) {
394  $types = self::getExtensionTypes();
395 
396  return isset( $types[$type] ) ? $types[$type] : $types['other'];
397  }
398 
404  public function getExtensionCredits() {
406 
407  if (
408  count( $wgExtensionCredits ) === 0 ||
409  // Skins are displayed separately, see getSkinCredits()
410  ( count( $wgExtensionCredits ) === 1 && isset( $wgExtensionCredits['skin'] ) )
411  ) {
412  return '';
413  }
414 
416 
417  $out = Xml::element(
418  'h2',
419  [ 'id' => 'mw-version-ext' ],
420  $this->msg( 'version-extensions' )->text()
421  ) .
422  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ] );
423 
424  // Make sure the 'other' type is set to an array.
425  if ( !array_key_exists( 'other', $wgExtensionCredits ) ) {
426  $wgExtensionCredits['other'] = [];
427  }
428 
429  // Find all extensions that do not have a valid type and give them the type 'other'.
430  foreach ( $wgExtensionCredits as $type => $extensions ) {
431  if ( !array_key_exists( $type, $extensionTypes ) ) {
432  $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions );
433  }
434  }
435 
436  $this->firstExtOpened = false;
437  // Loop through the extension categories to display their extensions in the list.
438  foreach ( $extensionTypes as $type => $message ) {
439  // Skins have a separate section
440  if ( $type !== 'other' && $type !== 'skin' ) {
441  $out .= $this->getExtensionCategory( $type, $message );
442  }
443  }
444 
445  // We want the 'other' type to be last in the list.
446  $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
447 
448  $out .= Xml::closeElement( 'table' );
449 
450  return $out;
451  }
452 
458  public function getSkinCredits() {
460  if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) {
461  return '';
462  }
463 
464  $out = Xml::element(
465  'h2',
466  [ 'id' => 'mw-version-skin' ],
467  $this->msg( 'version-skins' )->text()
468  ) .
469  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ] );
470 
471  $this->firstExtOpened = false;
472  $out .= $this->getExtensionCategory( 'skin', null );
473 
474  $out .= Xml::closeElement( 'table' );
475 
476  return $out;
477  }
478 
484  protected function getExternalLibraries() {
485  global $IP;
486  $path = "$IP/vendor/composer/installed.json";
487  if ( !file_exists( $path ) ) {
488  return '';
489  }
490 
491  $installed = new ComposerInstalled( $path );
493  'h2',
494  [ 'id' => 'mw-version-libraries' ],
495  $this->msg( 'version-libraries' )->text()
496  );
498  'table',
499  [ 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' ]
500  );
501  $out .= Html::openElement( 'tr' )
502  . Html::element( 'th', [], $this->msg( 'version-libraries-library' )->text() )
503  . Html::element( 'th', [], $this->msg( 'version-libraries-version' )->text() )
504  . Html::element( 'th', [], $this->msg( 'version-libraries-license' )->text() )
505  . Html::element( 'th', [], $this->msg( 'version-libraries-description' )->text() )
506  . Html::element( 'th', [], $this->msg( 'version-libraries-authors' )->text() )
507  . Html::closeElement( 'tr' );
508 
509  foreach ( $installed->getInstalledDependencies() as $name => $info ) {
510  if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
511  // Skip any extensions or skins since they'll be listed
512  // in their proper section
513  continue;
514  }
515  $authors = array_map( function ( $arr ) {
516  // If a homepage is set, link to it
517  if ( isset( $arr['homepage'] ) ) {
518  return "[{$arr['homepage']} {$arr['name']}]";
519  }
520  return $arr['name'];
521  }, $info['authors'] );
522  $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
523 
524  // We can safely assume that the libraries' names and descriptions
525  // are written in English and aren't going to be translated,
526  // so set appropriate lang and dir attributes
527  $out .= Html::openElement( 'tr' )
529  'td',
530  [],
532  "https://packagist.org/packages/$name", $name,
533  true, '',
534  [ 'class' => 'mw-version-library-name' ]
535  )
536  )
537  . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
538  . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['licenses'] ) )
539  . Html::element( 'td', [ 'lang' => 'en', 'dir' => 'ltr' ], $info['description'] )
540  . Html::rawElement( 'td', [], $authors )
541  . Html::closeElement( 'tr' );
542  }
543  $out .= Html::closeElement( 'table' );
544 
545  return $out;
546  }
547 
553  protected function getParserTags() {
555 
556  $tags = $wgParser->getTags();
557 
558  if ( count( $tags ) ) {
560  'h2',
561  [
562  'class' => 'mw-headline plainlinks',
563  'id' => 'mw-version-parser-extensiontags',
564  ],
566  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
567  $this->msg( 'version-parser-extensiontags' )->parse(),
568  false /* msg()->parse() already escapes */
569  )
570  );
571 
572  array_walk( $tags, function ( &$value ) {
573  // Bidirectional isolation improves readability in RTL wikis
575  'bdi',
576  // Prevent < and > from slipping to another line
577  [
578  'style' => 'white-space: nowrap;',
579  ],
580  "<$value>"
581  );
582  } );
583 
584  $out .= $this->listToText( $tags );
585  } else {
586  $out = '';
587  }
588 
589  return $out;
590  }
591 
597  protected function getParserFunctionHooks() {
599 
600  $fhooks = $wgParser->getFunctionHooks();
601  if ( count( $fhooks ) ) {
603  'h2',
604  [
605  'class' => 'mw-headline plainlinks',
606  'id' => 'mw-version-parser-function-hooks',
607  ],
609  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
610  $this->msg( 'version-parser-function-hooks' )->parse(),
611  false /* msg()->parse() already escapes */
612  )
613  );
614 
615  $out .= $this->listToText( $fhooks );
616  } else {
617  $out = '';
618  }
619 
620  return $out;
621  }
622 
633  protected function getExtensionCategory( $type, $message ) {
635 
636  $out = '';
637 
638  if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
639  $out .= $this->openExtType( $message, 'credits-' . $type );
640 
641  usort( $wgExtensionCredits[$type], [ $this, 'compare' ] );
642 
643  foreach ( $wgExtensionCredits[$type] as $extension ) {
644  $out .= $this->getCreditsForExtension( $type, $extension );
645  }
646  }
647 
648  return $out;
649  }
650 
657  public function compare( $a, $b ) {
658  if ( $a['name'] === $b['name'] ) {
659  return 0;
660  } else {
661  return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
662  ? 1
663  : -1;
664  }
665  }
666 
685  public function getCreditsForExtension( $type, array $extension ) {
686  $out = $this->getOutput();
687 
688  // We must obtain the information for all the bits and pieces!
689  // ... such as extension names and links
690  if ( isset( $extension['namemsg'] ) ) {
691  // Localized name of extension
692  $extensionName = $this->msg( $extension['namemsg'] )->text();
693  } elseif ( isset( $extension['name'] ) ) {
694  // Non localized version
695  $extensionName = $extension['name'];
696  } else {
697  $extensionName = $this->msg( 'version-no-ext-name' )->text();
698  }
699 
700  if ( isset( $extension['url'] ) ) {
701  $extensionNameLink = Linker::makeExternalLink(
702  $extension['url'],
703  $extensionName,
704  true,
705  '',
706  [ 'class' => 'mw-version-ext-name' ]
707  );
708  } else {
709  $extensionNameLink = $extensionName;
710  }
711 
712  // ... and the version information
713  // If the extension path is set we will check that directory for GIT
714  // metadata in an attempt to extract date and vcs commit metadata.
715  $canonicalVersion = '&ndash;';
716  $extensionPath = null;
717  $vcsVersion = null;
718  $vcsLink = null;
719  $vcsDate = null;
720 
721  if ( isset( $extension['version'] ) ) {
722  $canonicalVersion = $out->parseInline( $extension['version'] );
723  }
724 
725  if ( isset( $extension['path'] ) ) {
726  global $IP;
727  $extensionPath = dirname( $extension['path'] );
728  if ( $this->coreId == '' ) {
729  wfDebug( 'Looking up core head id' );
730  $coreHeadSHA1 = self::getGitHeadSha1( $IP );
731  if ( $coreHeadSHA1 ) {
732  $this->coreId = $coreHeadSHA1;
733  }
734  }
736  $memcKey = $cache->makeKey(
737  'specialversion-ext-version-text', $extension['path'], $this->coreId
738  );
739  list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
740 
741  if ( !$vcsVersion ) {
742  wfDebug( "Getting VCS info for extension {$extension['name']}" );
743  $gitInfo = new GitInfo( $extensionPath );
744  $vcsVersion = $gitInfo->getHeadSHA1();
745  if ( $vcsVersion !== false ) {
746  $vcsVersion = substr( $vcsVersion, 0, 7 );
747  $vcsLink = $gitInfo->getHeadViewUrl();
748  $vcsDate = $gitInfo->getHeadCommitDate();
749  }
750  $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
751  } else {
752  wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
753  }
754  }
755 
756  $versionString = Html::rawElement(
757  'span',
758  [ 'class' => 'mw-version-ext-version' ],
759  $canonicalVersion
760  );
761 
762  if ( $vcsVersion ) {
763  if ( $vcsLink ) {
764  $vcsVerString = Linker::makeExternalLink(
765  $vcsLink,
766  $this->msg( 'version-version', $vcsVersion ),
767  true,
768  '',
769  [ 'class' => 'mw-version-ext-vcs-version' ]
770  );
771  } else {
772  $vcsVerString = Html::element( 'span',
773  [ 'class' => 'mw-version-ext-vcs-version' ],
774  "({$vcsVersion})"
775  );
776  }
777  $versionString .= " {$vcsVerString}";
778 
779  if ( $vcsDate ) {
780  $vcsTimeString = Html::element( 'span',
781  [ 'class' => 'mw-version-ext-vcs-timestamp' ],
782  $this->getLanguage()->timeanddate( $vcsDate, true )
783  );
784  $versionString .= " {$vcsTimeString}";
785  }
786  $versionString = Html::rawElement( 'span',
787  [ 'class' => 'mw-version-ext-meta-version' ],
788  $versionString
789  );
790  }
791 
792  // ... and license information; if a license file exists we
793  // will link to it
794  $licenseLink = '';
795  if ( isset( $extension['name'] ) ) {
796  $licenseName = null;
797  if ( isset( $extension['license-name'] ) ) {
798  $licenseName = new HtmlArmor( $out->parseInline( $extension['license-name'] ) );
799  } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
800  $licenseName = $this->msg( 'version-ext-license' )->text();
801  }
802  if ( $licenseName !== null ) {
803  $licenseLink = $this->getLinkRenderer()->makeLink(
804  $this->getPageTitle( 'License/' . $extension['name'] ),
805  $licenseName,
806  [
807  'class' => 'mw-version-ext-license',
808  'dir' => 'auto',
809  ]
810  );
811  }
812  }
813 
814  // ... and generate the description; which can be a parameterized l10n message
815  // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
816  // up string
817  if ( isset( $extension['descriptionmsg'] ) ) {
818  // Localized description of extension
819  $descriptionMsg = $extension['descriptionmsg'];
820 
821  if ( is_array( $descriptionMsg ) ) {
822  $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
823  array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
824  array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
825  $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
826  } else {
827  $description = $this->msg( $descriptionMsg )->text();
828  }
829  } elseif ( isset( $extension['description'] ) ) {
830  // Non localized version
831  $description = $extension['description'];
832  } else {
833  $description = '';
834  }
835  $description = $out->parseInline( $description );
836 
837  // ... now get the authors for this extension
838  $authors = isset( $extension['author'] ) ? $extension['author'] : [];
839  $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
840 
841  // Finally! Create the table
842  $html = Html::openElement( 'tr', [
843  'class' => 'mw-version-ext',
844  'id' => Sanitizer::escapeIdForAttribute( 'mw-version-ext-' . $type . '-' . $extension['name'] )
845  ]
846  );
847 
848  $html .= Html::rawElement( 'td', [], $extensionNameLink );
849  $html .= Html::rawElement( 'td', [], $versionString );
850  $html .= Html::rawElement( 'td', [], $licenseLink );
851  $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-description' ], $description );
852  $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-authors' ], $authors );
853 
854  $html .= Html::closeElement( 'tr' );
855 
856  return $html;
857  }
858 
864  private function getWgHooks() {
866 
868  $myWgHooks = $wgHooks;
869  ksort( $myWgHooks );
870 
871  $ret = [];
872  $ret[] = '== {{int:version-hooks}} ==';
873  $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
874  $ret[] = Html::openElement( 'tr' );
875  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
876  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
877  $ret[] = Html::closeElement( 'tr' );
878 
879  foreach ( $myWgHooks as $hook => $hooks ) {
880  $ret[] = Html::openElement( 'tr' );
881  $ret[] = Html::element( 'td', [], $hook );
882  $ret[] = Html::element( 'td', [], $this->listToText( $hooks ) );
883  $ret[] = Html::closeElement( 'tr' );
884  }
885 
886  $ret[] = Html::closeElement( 'table' );
887 
888  return implode( "\n", $ret );
889  } else {
890  return '';
891  }
892  }
893 
894  private function openExtType( $text = null, $name = null ) {
895  $out = '';
896 
897  $opt = [ 'colspan' => 5 ];
898  if ( $this->firstExtOpened ) {
899  // Insert a spacing line
900  $out .= Html::rawElement( 'tr', [ 'class' => 'sv-space' ],
901  Html::element( 'td', $opt )
902  );
903  }
904  $this->firstExtOpened = true;
905 
906  if ( $name ) {
907  $opt['id'] = "sv-$name";
908  }
909 
910  if ( $text !== null ) {
911  $out .= Html::rawElement( 'tr', [],
912  Html::element( 'th', $opt, $text )
913  );
914  }
915 
916  $firstHeadingMsg = ( $name === 'credits-skin' )
917  ? 'version-skin-colheader-name'
918  : 'version-ext-colheader-name';
919  $out .= Html::openElement( 'tr' );
920  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
921  $this->msg( $firstHeadingMsg )->text() );
922  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
923  $this->msg( 'version-ext-colheader-version' )->text() );
924  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
925  $this->msg( 'version-ext-colheader-license' )->text() );
926  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
927  $this->msg( 'version-ext-colheader-description' )->text() );
928  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
929  $this->msg( 'version-ext-colheader-credits' )->text() );
930  $out .= Html::closeElement( 'tr' );
931 
932  return $out;
933  }
934 
940  private function IPInfo() {
941  $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
942 
943  return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
944  }
945 
967  public function listAuthors( $authors, $extName, $extDir ) {
968  $hasOthers = false;
969  $linkRenderer = $this->getLinkRenderer();
970 
971  $list = [];
972  foreach ( (array)$authors as $item ) {
973  if ( $item == '...' ) {
974  $hasOthers = true;
975 
976  if ( $extName && $this->getExtAuthorsFileName( $extDir ) ) {
977  $text = $linkRenderer->makeLink(
978  $this->getPageTitle( "Credits/$extName" ),
979  $this->msg( 'version-poweredby-others' )->text()
980  );
981  } else {
982  $text = $this->msg( 'version-poweredby-others' )->escaped();
983  }
984  $list[] = $text;
985  } elseif ( substr( $item, -5 ) == ' ...]' ) {
986  $hasOthers = true;
987  $list[] = $this->getOutput()->parseInline(
988  substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
989  );
990  } else {
991  $list[] = $this->getOutput()->parseInline( $item );
992  }
993  }
994 
995  if ( $extName && !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
996  $list[] = $text = $linkRenderer->makeLink(
997  $this->getPageTitle( "Credits/$extName" ),
998  $this->msg( 'version-poweredby-others' )->text()
999  );
1000  }
1001 
1002  return $this->listToText( $list, false );
1003  }
1004 
1016  public static function getExtAuthorsFileName( $extDir ) {
1017  if ( !$extDir ) {
1018  return false;
1019  }
1020 
1021  foreach ( scandir( $extDir ) as $file ) {
1022  $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
1023  if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt|\.wiki|\.mediawiki)?$/', $file ) &&
1024  is_readable( $fullPath ) &&
1025  is_file( $fullPath )
1026  ) {
1027  return $fullPath;
1028  }
1029  }
1030 
1031  return false;
1032  }
1033 
1045  public static function getExtLicenseFileName( $extDir ) {
1046  if ( !$extDir ) {
1047  return false;
1048  }
1049 
1050  foreach ( scandir( $extDir ) as $file ) {
1051  $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
1052  if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
1053  is_readable( $fullPath ) &&
1054  is_file( $fullPath )
1055  ) {
1056  return $fullPath;
1057  }
1058  }
1059 
1060  return false;
1061  }
1062 
1071  public function listToText( $list, $sort = true ) {
1072  if ( !count( $list ) ) {
1073  return '';
1074  }
1075  if ( $sort ) {
1076  sort( $list );
1077  }
1078 
1079  return $this->getLanguage()
1080  ->listToText( array_map( [ __CLASS__, 'arrayToString' ], $list ) );
1081  }
1082 
1091  public static function arrayToString( $list ) {
1092  if ( is_array( $list ) && count( $list ) == 1 ) {
1093  $list = $list[0];
1094  }
1095  if ( $list instanceof Closure ) {
1096  // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
1097  return 'Closure';
1098  } elseif ( is_object( $list ) ) {
1099  $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
1100 
1101  return $class;
1102  } elseif ( !is_array( $list ) ) {
1103  return $list;
1104  } else {
1105  if ( is_object( $list[0] ) ) {
1106  $class = get_class( $list[0] );
1107  } else {
1108  $class = $list[0];
1109  }
1110 
1111  return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
1112  }
1113  }
1114 
1119  public static function getGitHeadSha1( $dir ) {
1120  $repo = new GitInfo( $dir );
1121 
1122  return $repo->getHeadSHA1();
1123  }
1124 
1129  public static function getGitCurrentBranch( $dir ) {
1130  $repo = new GitInfo( $dir );
1131  return $repo->getCurrentBranch();
1132  }
1133 
1138  public function getEntryPointInfo() {
1140  $scriptPath = $wgScriptPath ? $wgScriptPath : "/";
1141  $entryPoints = [
1142  'version-entrypoints-articlepath' => $wgArticlePath,
1143  'version-entrypoints-scriptpath' => $scriptPath,
1144  'version-entrypoints-index-php' => wfScript( 'index' ),
1145  'version-entrypoints-api-php' => wfScript( 'api' ),
1146  'version-entrypoints-load-php' => wfScript( 'load' ),
1147  ];
1148 
1149  $language = $this->getLanguage();
1150  $thAttribures = [
1151  'dir' => $language->getDir(),
1152  'lang' => $language->getHtmlCode()
1153  ];
1154  $out = Html::element(
1155  'h2',
1156  [ 'id' => 'mw-version-entrypoints' ],
1157  $this->msg( 'version-entrypoints' )->text()
1158  ) .
1159  Html::openElement( 'table',
1160  [
1161  'class' => 'wikitable plainlinks',
1162  'id' => 'mw-version-entrypoints-table',
1163  'dir' => 'ltr',
1164  'lang' => 'en'
1165  ]
1166  ) .
1167  Html::openElement( 'tr' ) .
1168  Html::element(
1169  'th',
1170  $thAttribures,
1171  $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1172  ) .
1173  Html::element(
1174  'th',
1175  $thAttribures,
1176  $this->msg( 'version-entrypoints-header-url' )->text()
1177  ) .
1178  Html::closeElement( 'tr' );
1179 
1180  foreach ( $entryPoints as $message => $value ) {
1181  $url = wfExpandUrl( $value, PROTO_RELATIVE );
1182  $out .= Html::openElement( 'tr' ) .
1183  // ->text() looks like it should be ->parse(), but this function
1184  // returns wikitext, not HTML, boo
1185  Html::rawElement( 'td', [], $this->msg( $message )->text() ) .
1186  Html::rawElement( 'td', [], Html::rawElement( 'code', [], "[$url $value]" ) ) .
1187  Html::closeElement( 'tr' );
1188  }
1189 
1190  $out .= Html::closeElement( 'table' );
1191 
1192  return $out;
1193  }
1194 
1195  protected function getGroupName() {
1196  return 'wiki';
1197  }
1198 }
SpecialVersion\getExtensionCredits
getExtensionCredits()
Generate wikitext showing the name, URL, author and description of each extension.
Definition: SpecialVersion.php:404
SpecialVersion\openExtType
openExtType( $text=null, $name=null)
Definition: SpecialVersion.php:894
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:628
SpecialVersion\__construct
__construct()
Definition: SpecialVersion.php:41
SpecialVersion\listAuthors
listAuthors( $authors, $extName, $extDir)
Return a formatted unsorted list of authors.
Definition: SpecialVersion.php:967
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:746
SpecialVersion\getExtAuthorsFileName
static getExtAuthorsFileName( $extDir)
Obtains the full path of an extensions authors or credits file if one exists.
Definition: SpecialVersion.php:1016
$wgParser
$wgParser
Definition: Setup.php:824
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:675
SpecialVersion\listToText
listToText( $list, $sort=true)
Convert an array of items into a list for display.
Definition: SpecialVersion.php:1071
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:685
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:657
SpecialVersion\getCopyrightAndAuthorList
static getCopyrightAndAuthorList()
Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text.
Definition: SpecialVersion.php:183
$wgVersion
$wgVersion
MediaWiki version number.
Definition: DefaultSettings.php:78
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:705
SpecialVersion\getParserFunctionHooks
getParserFunctionHooks()
Obtains a list of installed parser function hooks and the associated H2 header.
Definition: SpecialVersion.php:597
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:298
SpecialVersion\arrayToString
static arrayToString( $list)
Convert an array or object to a string for display.
Definition: SpecialVersion.php:1091
SpecialVersion\softwareInformation
static softwareInformation()
Returns wiki text showing the third party software versions (apache, php, mysql).
Definition: SpecialVersion.php:219
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:940
SpecialVersion\getExternalLibraries
getExternalLibraries()
Generate an HTML table for external libraries that are installed.
Definition: SpecialVersion.php:484
$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:1965
SpecialVersion\getSkinCredits
getSkinCredits()
Generate wikitext showing the name, URL, author and description of each skin.
Definition: SpecialVersion.php:458
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2934
SpecialVersion\getExtensionTypeName
static getExtensionTypeName( $type)
Returns the internationalized name for an extension type.
Definition: SpecialVersion.php:393
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2856
$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:534
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:365
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:3195
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:864
$wgExtensionCredits
$wgExtensionCredits
An array of information about installed extensions keyed by their type.
Definition: DefaultSettings.php:7330
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:1047
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
$dir
$dir
Definition: Autoload.php:8
$sort
$sort
Definition: profileinfo.php:323
SpecialVersion\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialVersion.php:1195
SpecialVersion\getExtLicenseFileName
static getExtLicenseFileName( $extDir)
Obtains the full path of an extensions copying or license file if one exists.
Definition: SpecialVersion.php:1045
SpecialVersion\getwgVersionLinked
static getwgVersionLinked()
Definition: SpecialVersion.php:314
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:269
GitInfo
Definition: GitInfo.php:26
$value
$value
Definition: styleTest.css.php:45
SpecialVersion\getGitCurrentBranch
static getGitCurrentBranch( $dir)
Definition: SpecialVersion.php:1129
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:665
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:1965
SpecialVersion\getGitHeadSha1
static getGitHeadSha1( $dir)
Definition: SpecialVersion.php:1119
format
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1639
SpecialVersion\getEntryPointInfo
getEntryPointInfo()
Get the list of entry points and their URLs.
Definition: SpecialVersion.php:1138
SpecialVersion\$firstExtOpened
$firstExtOpened
Definition: SpecialVersion.php:32
$wgArticlePath
$wgArticlePath
Definition: img_auth.php:45
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:860
SpecialVersion\getVersionLinkedGit
static getVersionLinkedGit()
Definition: SpecialVersion.php:331
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:118
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
$cache
$cache
Definition: mcc.php:33
$ext
$ext
Definition: NoLocalSettings.php:25
$wgHooks
$wgHooks['ArticleShow'][]
Definition: hooks.txt:108
$path
$path
Definition: NoLocalSettings.php:26
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:553
wfIsHHVM
wfIsHHVM()
Check if we are running under HHVM.
Definition: GlobalFunctions.php:2092
$wgScriptPath
$wgScriptPath
The path we should point to.
Definition: DefaultSettings.php:141
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
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:6249
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
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2801
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:586
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:781
$type
$type
Definition: testCompression.php:48
SpecialVersion\getExtensionCategory
getExtensionCategory( $type, $message)
Creates and returns the HTML for a single extension category.
Definition: SpecialVersion.php:633