MediaWiki  master
SpecialVersion.php
Go to the documentation of this file.
1 <?php
27 
33 class SpecialVersion extends SpecialPage {
34  protected $firstExtOpened = false;
35 
39  protected $coreId = '';
40 
41  protected static $extensionTypes = false;
42 
43  public function __construct() {
44  parent::__construct( 'Version' );
45  }
46 
51  public function execute( $par ) {
52  global $IP;
53  $config = $this->getConfig();
54  $extensionCredits = $config->get( 'ExtensionCredits' );
55 
56  $this->setHeaders();
57  $this->outputHeader();
58  $out = $this->getOutput();
59  $out->allowClickjacking();
60 
61  // Explode the sub page information into useful bits
62  $parts = explode( '/', (string)$par );
63  $extNode = null;
64  if ( isset( $parts[1] ) ) {
65  $extName = str_replace( '_', ' ', $parts[1] );
66  // Find it!
67  foreach ( $extensionCredits as $group => $extensions ) {
68  foreach ( $extensions as $ext ) {
69  if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
70  $extNode = &$ext;
71  break 2;
72  }
73  }
74  }
75  if ( !$extNode ) {
76  $out->setStatusCode( 404 );
77  }
78  } else {
79  $extName = 'MediaWiki';
80  }
81 
82  // Now figure out what to do
83  switch ( strtolower( $parts[0] ) ) {
84  case 'credits':
85  $out->addModuleStyles( 'mediawiki.special.version' );
86 
87  $wikiText = '{{int:version-credits-not-found}}';
88  if ( $extName === 'MediaWiki' ) {
89  $wikiText = file_get_contents( $IP . '/CREDITS' );
90  // Put the contributor list into columns
91  $wikiText = str_replace(
92  [ '<!-- BEGIN CONTRIBUTOR LIST -->', '<!-- END CONTRIBUTOR LIST -->' ],
93  [ '<div class="mw-version-credits">', '</div>' ],
94  $wikiText );
95  } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
96  $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
97  if ( $file ) {
98  $wikiText = file_get_contents( $file );
99  if ( substr( $file, -4 ) === '.txt' ) {
100  $wikiText = Html::element(
101  'pre',
102  [
103  'lang' => 'en',
104  'dir' => 'ltr',
105  ],
106  $wikiText
107  );
108  }
109  }
110  }
111 
112  $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
113  $out->addWikiTextAsInterface( $wikiText );
114  break;
115 
116  case 'license':
117  $wikiText = '{{int:version-license-not-found}}';
118  if ( $extName === 'MediaWiki' ) {
119  $wikiText = file_get_contents( $IP . '/COPYING' );
120  } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
121  $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
122  if ( $file ) {
123  $wikiText = file_get_contents( $file );
124  $wikiText = Html::element(
125  'pre',
126  [
127  'lang' => 'en',
128  'dir' => 'ltr',
129  ],
130  $wikiText
131  );
132  }
133  }
134 
135  $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
136  $out->addWikiTextAsInterface( $wikiText );
137  break;
138 
139  default:
140  $out->addModuleStyles( 'mediawiki.special.version' );
141  $out->addWikiTextAsInterface(
142  $this->getMediaWikiCredits() .
143  $this->softwareInformation() .
144  $this->getEntryPointInfo()
145  );
146  $out->addHTML(
147  $this->getSkinCredits() .
148  $this->getExtensionCredits() .
149  $this->getExternalLibraries() .
150  $this->getParserTags() .
151  $this->getParserFunctionHooks()
152  );
153  $out->addWikiTextAsInterface( $this->getWgHooks() );
154  $out->addHTML( $this->IPInfo() );
155 
156  break;
157  }
158  }
159 
165  private static function getMediaWikiCredits() {
166  $ret = Xml::element(
167  'h2',
168  [ 'id' => 'mw-version-license' ],
169  wfMessage( 'version-license' )->text()
170  );
171 
172  // This text is always left-to-right.
173  $ret .= '<div class="plainlinks">';
174  $ret .= "__NOTOC__
175  " . self::getCopyrightAndAuthorList() . "\n
176  " . '<div class="mw-version-license-info">' .
177  wfMessage( 'version-license-info' )->text() .
178  '</div>';
179  $ret .= '</div>';
180 
181  return str_replace( "\t\t", '', $ret ) . "\n";
182  }
183 
189  public static function getCopyrightAndAuthorList() {
190  global $wgLang;
191 
192  if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
193  $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
194  wfMessage( 'version-poweredby-others' )->text() . ']';
195  } else {
196  $othersLink = '[[Special:Version/Credits|' .
197  wfMessage( 'version-poweredby-others' )->text() . ']]';
198  }
199 
200  $translatorsLink = '[https://translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
201  wfMessage( 'version-poweredby-translators' )->text() . ']';
202 
203  $authorList = [
204  'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
205  'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
206  'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
207  'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
208  'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
209  'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
210  'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
211  'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
212  'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
213  'Kunal Mehta', 'James D. Forrester', 'Brian Wolff', 'Adam Shorland',
214  $othersLink, $translatorsLink
215  ];
216 
217  return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
218  $wgLang->listToText( $authorList ) )->text();
219  }
220 
226  public static function softwareInformation() {
227  $dbr = wfGetDB( DB_REPLICA );
228 
229  // Put the software in an array of form 'name' => 'version'. All messages should
230  // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
231  // wikimarkup can be used.
232  $software = [];
233  $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
234  if ( wfIsHHVM() ) {
235  $software['[https://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")";
236  } else {
237  $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")";
238  }
239  $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
240 
241  if ( defined( 'INTL_ICU_VERSION' ) ) {
242  $software['[http://site.icu-project.org/ ICU]'] = INTL_ICU_VERSION;
243  }
244 
245  // Allow a hook to add/remove items.
246  Hooks::run( 'SoftwareInfo', [ &$software ] );
247 
248  $out = Xml::element(
249  'h2',
250  [ 'id' => 'mw-version-software' ],
251  wfMessage( 'version-software' )->text()
252  ) .
253  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] ) .
254  "<tr>
255  <th>" . wfMessage( 'version-software-product' )->text() . "</th>
256  <th>" . wfMessage( 'version-software-version' )->text() . "</th>
257  </tr>\n";
258 
259  foreach ( $software as $name => $version ) {
260  $out .= "<tr>
261  <td>" . $name . "</td>
262  <td dir=\"ltr\">" . $version . "</td>
263  </tr>\n";
264  }
265 
266  return $out . Xml::closeElement( 'table' );
267  }
268 
276  public static function getVersion( $flags = '', $lang = null ) {
277  global $wgVersion, $IP;
278 
279  $gitInfo = self::getGitHeadSha1( $IP );
280  if ( !$gitInfo ) {
281  $version = $wgVersion;
282  } elseif ( $flags === 'nodb' ) {
283  $shortSha1 = substr( $gitInfo, 0, 7 );
284  $version = "$wgVersion ($shortSha1)";
285  } else {
286  $shortSha1 = substr( $gitInfo, 0, 7 );
287  $msg = wfMessage( 'parentheses' );
288  if ( $lang !== null ) {
289  $msg->inLanguage( $lang );
290  }
291  $shortSha1 = $msg->params( $shortSha1 )->escaped();
292  $version = "$wgVersion $shortSha1";
293  }
294 
295  return $version;
296  }
297 
305  public static function getVersionLinked() {
306  global $wgVersion;
307 
308  $gitVersion = self::getVersionLinkedGit();
309  if ( $gitVersion ) {
310  $v = $gitVersion;
311  } else {
312  $v = $wgVersion; // fallback
313  }
314 
315  return $v;
316  }
317 
321  private static function getwgVersionLinked() {
322  global $wgVersion;
323  $versionUrl = "";
324  if ( Hooks::run( 'SpecialVersionVersionUrl', [ $wgVersion, &$versionUrl ] ) ) {
325  $versionParts = [];
326  preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
327  $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
328  }
329 
330  return "[$versionUrl $wgVersion]";
331  }
332 
338  private static function getVersionLinkedGit() {
339  global $IP, $wgLang;
340 
341  $gitInfo = new GitInfo( $IP );
342  $headSHA1 = $gitInfo->getHeadSHA1();
343  if ( !$headSHA1 ) {
344  return false;
345  }
346 
347  $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
348 
349  $gitHeadUrl = $gitInfo->getHeadViewUrl();
350  if ( $gitHeadUrl !== false ) {
351  $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
352  }
353 
354  $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
355  if ( $gitHeadCommitDate ) {
356  $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
357  }
358 
359  return self::getwgVersionLinked() . " $shortSHA1";
360  }
361 
372  public static function getExtensionTypes() {
373  if ( self::$extensionTypes === false ) {
374  self::$extensionTypes = [
375  'specialpage' => wfMessage( 'version-specialpages' )->text(),
376  'editor' => wfMessage( 'version-editors' )->text(),
377  'parserhook' => wfMessage( 'version-parserhooks' )->text(),
378  'variable' => wfMessage( 'version-variables' )->text(),
379  'media' => wfMessage( 'version-mediahandlers' )->text(),
380  'antispam' => wfMessage( 'version-antispam' )->text(),
381  'skin' => wfMessage( 'version-skins' )->text(),
382  'api' => wfMessage( 'version-api' )->text(),
383  'other' => wfMessage( 'version-other' )->text(),
384  ];
385 
386  Hooks::run( 'ExtensionTypes', [ &self::$extensionTypes ] );
387  }
388 
389  return self::$extensionTypes;
390  }
391 
401  public static function getExtensionTypeName( $type ) {
402  $types = self::getExtensionTypes();
403 
404  return $types[$type] ?? $types['other'];
405  }
406 
412  public function getExtensionCredits() {
413  $config = $this->getConfig();
414  $extensionCredits = $config->get( 'ExtensionCredits' );
415 
416  if (
417  count( $extensionCredits ) === 0 ||
418  // Skins are displayed separately, see getSkinCredits()
419  ( count( $extensionCredits ) === 1 && isset( $extensionCredits['skin'] ) )
420  ) {
421  return '';
422  }
423 
424  $extensionTypes = self::getExtensionTypes();
425 
426  $out = Xml::element(
427  'h2',
428  [ 'id' => 'mw-version-ext' ],
429  $this->msg( 'version-extensions' )->text()
430  ) .
431  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ] );
432 
433  // Make sure the 'other' type is set to an array.
434  if ( !array_key_exists( 'other', $extensionCredits ) ) {
435  $extensionCredits['other'] = [];
436  }
437 
438  // Find all extensions that do not have a valid type and give them the type 'other'.
439  foreach ( $extensionCredits as $type => $extensions ) {
440  if ( !array_key_exists( $type, $extensionTypes ) ) {
441  $extensionCredits['other'] = array_merge( $extensionCredits['other'], $extensions );
442  }
443  }
444 
445  $this->firstExtOpened = false;
446  // Loop through the extension categories to display their extensions in the list.
447  foreach ( $extensionTypes as $type => $message ) {
448  // Skins have a separate section
449  if ( $type !== 'other' && $type !== 'skin' ) {
450  $out .= $this->getExtensionCategory( $type, $message );
451  }
452  }
453 
454  // We want the 'other' type to be last in the list.
455  $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
456 
457  $out .= Xml::closeElement( 'table' );
458 
459  return $out;
460  }
461 
467  public function getSkinCredits() {
468  global $wgExtensionCredits;
469  if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) {
470  return '';
471  }
472 
473  $out = Xml::element(
474  'h2',
475  [ 'id' => 'mw-version-skin' ],
476  $this->msg( 'version-skins' )->text()
477  ) .
478  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ] );
479 
480  $this->firstExtOpened = false;
481  $out .= $this->getExtensionCategory( 'skin', null );
482 
483  $out .= Xml::closeElement( 'table' );
484 
485  return $out;
486  }
487 
493  protected function getExternalLibraries() {
494  global $IP;
495  $path = "$IP/vendor/composer/installed.json";
496  if ( !file_exists( $path ) ) {
497  return '';
498  }
499 
500  $installed = new ComposerInstalled( $path );
502  'h2',
503  [ 'id' => 'mw-version-libraries' ],
504  $this->msg( 'version-libraries' )->text()
505  );
507  'table',
508  [ 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' ]
509  );
510  $out .= Html::openElement( 'tr' )
511  . Html::element( 'th', [], $this->msg( 'version-libraries-library' )->text() )
512  . Html::element( 'th', [], $this->msg( 'version-libraries-version' )->text() )
513  . Html::element( 'th', [], $this->msg( 'version-libraries-license' )->text() )
514  . Html::element( 'th', [], $this->msg( 'version-libraries-description' )->text() )
515  . Html::element( 'th', [], $this->msg( 'version-libraries-authors' )->text() )
516  . Html::closeElement( 'tr' );
517 
518  foreach ( $installed->getInstalledDependencies() as $name => $info ) {
519  if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
520  // Skip any extensions or skins since they'll be listed
521  // in their proper section
522  continue;
523  }
524  $authors = array_map( function ( $arr ) {
525  // If a homepage is set, link to it
526  if ( isset( $arr['homepage'] ) ) {
527  return "[{$arr['homepage']} {$arr['name']}]";
528  }
529  return $arr['name'];
530  }, $info['authors'] );
531  $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
532 
533  // We can safely assume that the libraries' names and descriptions
534  // are written in English and aren't going to be translated,
535  // so set appropriate lang and dir attributes
536  $out .= Html::openElement( 'tr' )
538  'td',
539  [],
541  "https://packagist.org/packages/$name", $name,
542  true, '',
543  [ 'class' => 'mw-version-library-name' ]
544  )
545  )
546  . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
547  . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['licenses'] ) )
548  . Html::element( 'td', [ 'lang' => 'en', 'dir' => 'ltr' ], $info['description'] )
549  . Html::rawElement( 'td', [], $authors )
550  . Html::closeElement( 'tr' );
551  }
552  $out .= Html::closeElement( 'table' );
553 
554  return $out;
555  }
556 
562  protected function getParserTags() {
563  $tags = MediaWikiServices::getInstance()->getParser()->getTags();
564 
565  if ( count( $tags ) ) {
567  'h2',
568  [
569  'class' => 'mw-headline plainlinks',
570  'id' => 'mw-version-parser-extensiontags',
571  ],
573  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
574  $this->msg( 'version-parser-extensiontags' )->parse(),
575  false /* msg()->parse() already escapes */
576  )
577  );
578 
579  array_walk( $tags, function ( &$value ) {
580  // Bidirectional isolation improves readability in RTL wikis
582  'bdi',
583  // Prevent < and > from slipping to another line
584  [
585  'style' => 'white-space: nowrap;',
586  ],
587  "<$value>"
588  );
589  } );
590 
591  $out .= $this->listToText( $tags );
592  } else {
593  $out = '';
594  }
595 
596  return $out;
597  }
598 
604  protected function getParserFunctionHooks() {
605  $fhooks = MediaWikiServices::getInstance()->getParser()->getFunctionHooks();
606  if ( count( $fhooks ) ) {
608  'h2',
609  [
610  'class' => 'mw-headline plainlinks',
611  'id' => 'mw-version-parser-function-hooks',
612  ],
614  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
615  $this->msg( 'version-parser-function-hooks' )->parse(),
616  false /* msg()->parse() already escapes */
617  )
618  );
619 
620  $out .= $this->listToText( $fhooks );
621  } else {
622  $out = '';
623  }
624 
625  return $out;
626  }
627 
638  protected function getExtensionCategory( $type, $message ) {
639  $config = $this->getConfig();
640  $extensionCredits = $config->get( 'ExtensionCredits' );
641 
642  $out = '';
643 
644  if ( array_key_exists( $type, $extensionCredits ) && count( $extensionCredits[$type] ) > 0 ) {
645  $out .= $this->openExtType( $message, 'credits-' . $type );
646 
647  usort( $extensionCredits[$type], [ $this, 'compare' ] );
648 
649  foreach ( $extensionCredits[$type] as $extension ) {
650  $out .= $this->getCreditsForExtension( $type, $extension );
651  }
652  }
653 
654  return $out;
655  }
656 
663  public function compare( $a, $b ) {
664  return $this->getLanguage()->lc( $a['name'] ) <=> $this->getLanguage()->lc( $b['name'] );
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 = htmlspecialchars( $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->parseInlineAsInterface( $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->parseInlineAsInterface( $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->parseInlineAsInterface( $description );
836 
837  // ... now get the authors for this extension
838  $authors = $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 
867  if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
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  }
890 
891  return '';
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()->parseInlineAsInterface(
988  substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
989  );
990  } else {
991  $list[] = $this->getOutput()->parseInlineAsInterface( $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() {
1139  $config = $this->getConfig();
1140  $scriptPath = $config->get( 'ScriptPath' ) ?: '/';
1141 
1142  $entryPoints = [
1143  'version-entrypoints-articlepath' => $config->get( 'ArticlePath' ),
1144  'version-entrypoints-scriptpath' => $scriptPath,
1145  'version-entrypoints-index-php' => wfScript( 'index' ),
1146  'version-entrypoints-api-php' => wfScript( 'api' ),
1147  'version-entrypoints-load-php' => wfScript( 'load' ),
1148  ];
1149 
1150  $language = $this->getLanguage();
1151  $thAttribures = [
1152  'dir' => $language->getDir(),
1153  'lang' => $language->getHtmlCode()
1154  ];
1155  $out = Html::element(
1156  'h2',
1157  [ 'id' => 'mw-version-entrypoints' ],
1158  $this->msg( 'version-entrypoints' )->text()
1159  ) .
1160  Html::openElement( 'table',
1161  [
1162  'class' => 'wikitable plainlinks',
1163  'id' => 'mw-version-entrypoints-table',
1164  'dir' => 'ltr',
1165  'lang' => 'en'
1166  ]
1167  ) .
1168  Html::openElement( 'tr' ) .
1169  Html::element(
1170  'th',
1171  $thAttribures,
1172  $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1173  ) .
1174  Html::element(
1175  'th',
1176  $thAttribures,
1177  $this->msg( 'version-entrypoints-header-url' )->text()
1178  ) .
1179  Html::closeElement( 'tr' );
1180 
1181  foreach ( $entryPoints as $message => $value ) {
1182  $url = wfExpandUrl( $value, PROTO_RELATIVE );
1183  $out .= Html::openElement( 'tr' ) .
1184  // ->plain() looks like it should be ->parse(), but this function
1185  // returns wikitext, not HTML, boo
1186  Html::rawElement( 'td', [], $this->msg( $message )->plain() ) .
1187  Html::rawElement( 'td', [], Html::rawElement( 'code', [], "[$url $value]" ) ) .
1188  Html::closeElement( 'tr' );
1189  }
1190 
1191  $out .= Html::closeElement( 'table' );
1192 
1193  return $out;
1194  }
1195 
1196  protected function getGroupName() {
1197  return 'wiki';
1198  }
1199 }
static getExtensionTypes()
Returns an array with the base extension types.
static getVersionLinkedGit()
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
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:1982
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
compare( $a, $b)
Callback to sort extensions by type.
$wgExtensionCredits
An array of information about installed extensions keyed by their type.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
either a plain
Definition: hooks.txt:2043
$wgVersion
MediaWiki version number.
wfIsHHVM()
Check if we are running under HHVM.
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
$IP
Definition: WebStart.php:41
getExtensionCategory( $type, $message)
Creates and returns the HTML for a single extension category.
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:1982
openExtType( $text=null, $name=null)
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfGetCache( $cacheType)
Get a specific cache object.
if(!isset( $args[0])) $lang
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing &#39;/&#39;...
Definition: Html.php:252
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
$sort
$value
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 MediaWikiServices
Definition: injection.txt:23
getOutput()
Get the OutputPage being used for this instance.
getParserTags()
Obtains a list of installed parser tags and the associated H2 header.
getParserFunctionHooks()
Obtains a list of installed parser function hooks and the associated H2 header.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
static getExtensionTypeName( $type)
Returns the internationalized name for an extension type.
IPInfo()
Get information about client&#39;s IP address.
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:780
$wgSpecialVersionShowHooks
Show the contents of $wgHooks in Special:Version.
$wgLang
Definition: Setup.php:931
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 use $formDescriptor instead 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
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
$wgHooks['ArticleShow'][]
Definition: hooks.txt:108
static getExtAuthorsFileName( $extDir)
Obtains the full path of an extensions authors or credits file if one exists.
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:1288
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
listToText( $list, $sort=true)
Convert an array of items into a list for display.
getExternalLibraries()
Generate an HTML table for external libraries that are installed.
static $extensionTypes
getWgHooks()
Generate wikitext showing hooks in $wgHooks.
$cache
Definition: mcc.php:33
getSkinCredits()
Generate wikitext showing the name, URL, author and description of each skin.
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:316
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:850
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes! ...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
static getwgVersionLinked()
static getGitHeadSha1( $dir)
const PROTO_RELATIVE
Definition: Defines.php:217
static getExtLicenseFileName( $extDir)
Obtains the full path of an extensions copying or license file if one exists.
static getVersionLinked()
Return a wikitext-formatted string of the MediaWiki version with a link to the Git SHA1 of head if av...
static arrayToString( $list)
Convert an array or object to a string for display.
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
static getCopyrightAndAuthorList()
Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text.
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
execute( $par)
main()
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:117
getCreditsForExtension( $type, array $extension)
Creates and formats a version line for a single extension.
getExtensionCredits()
Generate wikitext showing the name, URL, author and description of each extension.
static getMediaWikiCredits()
Returns wiki text showing the license information.
getConfig()
Shortcut to get main config object.
const CACHE_ANYTHING
Definition: Defines.php:97
if(!is_readable( $file)) $ext
Definition: router.php:48
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
getLanguage()
Shortcut to get user&#39;s language.
msg( $key)
Wrapper around wfMessage that sets the current context.
listAuthors( $authors, $extName, $extDir)
Return a formatted unsorted list of authors.
Give information about the version of MediaWiki, PHP, the DB and extensions.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
static softwareInformation()
Returns wiki text showing the third party software versions (apache, php, mysql). ...
Reads an installed.json file and provides accessors to get what is installed.
const DB_REPLICA
Definition: defines.php:25
getRequest()
Get the WebRequest being used for this instance.
static getGitCurrentBranch( $dir)
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
getPageTitle( $subpage=false)
Get a self-referential title object.
getEntryPointInfo()
Get the list of entry points and their URLs.
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1641
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
$coreId
Stores the current rev id/SHA hash of MediaWiki core.
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:67