MediaWiki  1.27.2
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 ) {
50  global $IP, $wgExtensionCredits;
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  $wikiText = '{{int:version-credits-not-found}}';
82  if ( $extName === 'MediaWiki' ) {
83  $wikiText = file_get_contents( $IP . '/CREDITS' );
84  } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
85  $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
86  if ( $file ) {
87  $wikiText = file_get_contents( $file );
88  if ( substr( $file, -4 ) === '.txt' ) {
89  $wikiText = Html::element(
90  'pre',
91  [
92  'lang' => 'en',
93  'dir' => 'ltr',
94  ],
95  $wikiText
96  );
97  }
98  }
99  }
100 
101  $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
102  $out->addWikiText( $wikiText );
103  break;
104 
105  case 'license':
106  $wikiText = '{{int:version-license-not-found}}';
107  if ( $extName === 'MediaWiki' ) {
108  $wikiText = file_get_contents( $IP . '/COPYING' );
109  } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
110  $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
111  if ( $file ) {
112  $wikiText = file_get_contents( $file );
113  $wikiText = Html::element(
114  'pre',
115  [
116  'lang' => 'en',
117  'dir' => 'ltr',
118  ],
119  $wikiText
120  );
121  }
122  }
123 
124  $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
125  $out->addWikiText( $wikiText );
126  break;
127 
128  default:
129  $out->addModuleStyles( 'mediawiki.special.version' );
130  $out->addWikiText(
131  $this->getMediaWikiCredits() .
132  $this->softwareInformation() .
133  $this->getEntryPointInfo()
134  );
135  $out->addHTML(
136  $this->getSkinCredits() .
137  $this->getExtensionCredits() .
138  $this->getExternalLibraries() .
139  $this->getParserTags() .
140  $this->getParserFunctionHooks()
141  );
142  $out->addWikiText( $this->getWgHooks() );
143  $out->addHTML( $this->IPInfo() );
144 
145  break;
146  }
147  }
148 
154  private static function getMediaWikiCredits() {
155  $ret = Xml::element(
156  'h2',
157  [ 'id' => 'mw-version-license' ],
158  wfMessage( 'version-license' )->text()
159  );
160 
161  // This text is always left-to-right.
162  $ret .= '<div class="plainlinks">';
163  $ret .= "__NOTOC__
164  " . self::getCopyrightAndAuthorList() . "\n
165  " . wfMessage( 'version-license-info' )->text();
166  $ret .= '</div>';
167 
168  return str_replace( "\t\t", '', $ret ) . "\n";
169  }
170 
176  public static function getCopyrightAndAuthorList() {
177  global $wgLang;
178 
179  if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
180  $othersLink = '[https://www.mediawiki.org/wiki/Special:Version/Credits ' .
181  wfMessage( 'version-poweredby-others' )->text() . ']';
182  } else {
183  $othersLink = '[[Special:Version/Credits|' .
184  wfMessage( 'version-poweredby-others' )->text() . ']]';
185  }
186 
187  $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
188  wfMessage( 'version-poweredby-translators' )->text() . ']';
189 
190  $authorList = [
191  'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
192  'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
193  'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
194  'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
195  'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
196  'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
197  'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
198  'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
199  $othersLink, $translatorsLink
200  ];
201 
202  return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
203  $wgLang->listToText( $authorList ) )->text();
204  }
205 
211  public static function softwareInformation() {
212  $dbr = wfGetDB( DB_SLAVE );
213 
214  // Put the software in an array of form 'name' => 'version'. All messages should
215  // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
216  // wikimarkup can be used.
217  $software = [];
218  $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
219  if ( wfIsHHVM() ) {
220  $software['[http://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")";
221  } else {
222  $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")";
223  }
224  $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
225 
226  if ( IcuCollation::getICUVersion() ) {
227  $software['[http://site.icu-project.org/ ICU]'] = IcuCollation::getICUVersion();
228  }
229 
230  // Allow a hook to add/remove items.
231  Hooks::run( 'SoftwareInfo', [ &$software ] );
232 
233  $out = Xml::element(
234  'h2',
235  [ 'id' => 'mw-version-software' ],
236  wfMessage( 'version-software' )->text()
237  ) .
238  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ] ) .
239  "<tr>
240  <th>" . wfMessage( 'version-software-product' )->text() . "</th>
241  <th>" . wfMessage( 'version-software-version' )->text() . "</th>
242  </tr>\n";
243 
244  foreach ( $software as $name => $version ) {
245  $out .= "<tr>
246  <td>" . $name . "</td>
247  <td dir=\"ltr\">" . $version . "</td>
248  </tr>\n";
249  }
250 
251  return $out . Xml::closeElement( 'table' );
252  }
253 
261  public static function getVersion( $flags = '', $lang = null ) {
263 
264  $gitInfo = self::getGitHeadSha1( $IP );
265  if ( !$gitInfo ) {
267  } elseif ( $flags === 'nodb' ) {
268  $shortSha1 = substr( $gitInfo, 0, 7 );
269  $version = "$wgVersion ($shortSha1)";
270  } else {
271  $shortSha1 = substr( $gitInfo, 0, 7 );
272  $msg = wfMessage( 'parentheses' );
273  if ( $lang !== null ) {
274  $msg->inLanguage( $lang );
275  }
276  $shortSha1 = $msg->params( $shortSha1 )->escaped();
277  $version = "$wgVersion $shortSha1";
278  }
279 
280  return $version;
281  }
282 
290  public static function getVersionLinked() {
292 
293  $gitVersion = self::getVersionLinkedGit();
294  if ( $gitVersion ) {
295  $v = $gitVersion;
296  } else {
297  $v = $wgVersion; // fallback
298  }
299 
300  return $v;
301  }
302 
306  private static function getwgVersionLinked() {
308  $versionUrl = "";
309  if ( Hooks::run( 'SpecialVersionVersionUrl', [ $wgVersion, &$versionUrl ] ) ) {
310  $versionParts = [];
311  preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
312  $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
313  }
314 
315  return "[$versionUrl $wgVersion]";
316  }
317 
323  private static function getVersionLinkedGit() {
324  global $IP, $wgLang;
325 
326  $gitInfo = new GitInfo( $IP );
327  $headSHA1 = $gitInfo->getHeadSHA1();
328  if ( !$headSHA1 ) {
329  return false;
330  }
331 
332  $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
333 
334  $gitHeadUrl = $gitInfo->getHeadViewUrl();
335  if ( $gitHeadUrl !== false ) {
336  $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
337  }
338 
339  $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
340  if ( $gitHeadCommitDate ) {
341  $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
342  }
343 
344  return self::getwgVersionLinked() . " $shortSHA1";
345  }
346 
357  public static function getExtensionTypes() {
358  if ( self::$extensionTypes === false ) {
359  self::$extensionTypes = [
360  'specialpage' => wfMessage( 'version-specialpages' )->text(),
361  'parserhook' => wfMessage( 'version-parserhooks' )->text(),
362  'variable' => wfMessage( 'version-variables' )->text(),
363  'media' => wfMessage( 'version-mediahandlers' )->text(),
364  'antispam' => wfMessage( 'version-antispam' )->text(),
365  'skin' => wfMessage( 'version-skins' )->text(),
366  'api' => wfMessage( 'version-api' )->text(),
367  'other' => wfMessage( 'version-other' )->text(),
368  ];
369 
370  Hooks::run( 'ExtensionTypes', [ &self::$extensionTypes ] );
371  }
372 
373  return self::$extensionTypes;
374  }
375 
385  public static function getExtensionTypeName( $type ) {
386  $types = self::getExtensionTypes();
387 
388  return isset( $types[$type] ) ? $types[$type] : $types['other'];
389  }
390 
396  public function getExtensionCredits() {
397  global $wgExtensionCredits;
398 
399  if (
400  count( $wgExtensionCredits ) === 0 ||
401  // Skins are displayed separately, see getSkinCredits()
402  ( count( $wgExtensionCredits ) === 1 && isset( $wgExtensionCredits['skin'] ) )
403  ) {
404  return '';
405  }
406 
407  $extensionTypes = self::getExtensionTypes();
408 
409  $out = Xml::element(
410  'h2',
411  [ 'id' => 'mw-version-ext' ],
412  $this->msg( 'version-extensions' )->text()
413  ) .
414  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ] );
415 
416  // Make sure the 'other' type is set to an array.
417  if ( !array_key_exists( 'other', $wgExtensionCredits ) ) {
418  $wgExtensionCredits['other'] = [];
419  }
420 
421  // Find all extensions that do not have a valid type and give them the type 'other'.
422  foreach ( $wgExtensionCredits as $type => $extensions ) {
423  if ( !array_key_exists( $type, $extensionTypes ) ) {
424  $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions );
425  }
426  }
427 
428  $this->firstExtOpened = false;
429  // Loop through the extension categories to display their extensions in the list.
430  foreach ( $extensionTypes as $type => $message ) {
431  // Skins have a separate section
432  if ( $type !== 'other' && $type !== 'skin' ) {
433  $out .= $this->getExtensionCategory( $type, $message );
434  }
435  }
436 
437  // We want the 'other' type to be last in the list.
438  $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
439 
440  $out .= Xml::closeElement( 'table' );
441 
442  return $out;
443  }
444 
450  public function getSkinCredits() {
451  global $wgExtensionCredits;
452  if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) {
453  return '';
454  }
455 
456  $out = Xml::element(
457  'h2',
458  [ 'id' => 'mw-version-skin' ],
459  $this->msg( 'version-skins' )->text()
460  ) .
461  Xml::openElement( 'table', [ 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ] );
462 
463  $this->firstExtOpened = false;
464  $out .= $this->getExtensionCategory( 'skin', null );
465 
466  $out .= Xml::closeElement( 'table' );
467 
468  return $out;
469  }
470 
476  protected function getExternalLibraries() {
477  global $IP;
478  $path = "$IP/vendor/composer/installed.json";
479  if ( !file_exists( $path ) ) {
480  return '';
481  }
482 
483  $installed = new ComposerInstalled( $path );
485  'h2',
486  [ 'id' => 'mw-version-libraries' ],
487  $this->msg( 'version-libraries' )->text()
488  );
490  'table',
491  [ 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' ]
492  );
493  $out .= Html::openElement( 'tr' )
494  . Html::element( 'th', [], $this->msg( 'version-libraries-library' )->text() )
495  . Html::element( 'th', [], $this->msg( 'version-libraries-version' )->text() )
496  . Html::element( 'th', [], $this->msg( 'version-libraries-license' )->text() )
497  . Html::element( 'th', [], $this->msg( 'version-libraries-description' )->text() )
498  . Html::element( 'th', [], $this->msg( 'version-libraries-authors' )->text() )
499  . Html::closeElement( 'tr' );
500 
501  foreach ( $installed->getInstalledDependencies() as $name => $info ) {
502  if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
503  // Skip any extensions or skins since they'll be listed
504  // in their proper section
505  continue;
506  }
507  $authors = array_map( function( $arr ) {
508  // If a homepage is set, link to it
509  if ( isset( $arr['homepage'] ) ) {
510  return "[{$arr['homepage']} {$arr['name']}]";
511  }
512  return $arr['name'];
513  }, $info['authors'] );
514  $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
515 
516  // We can safely assume that the libraries' names and descriptions
517  // are written in English and aren't going to be translated,
518  // so set appropriate lang and dir attributes
519  $out .= Html::openElement( 'tr' )
521  'td',
522  [],
524  "https://packagist.org/packages/$name", $name,
525  true, '',
526  [ 'class' => 'mw-version-library-name' ]
527  )
528  )
529  . Html::element( 'td', [ 'dir' => 'auto' ], $info['version'] )
530  . Html::element( 'td', [ 'dir' => 'auto' ], $this->listToText( $info['licenses'] ) )
531  . Html::element( 'td', [ 'lang' => 'en', 'dir' => 'ltr' ], $info['description'] )
532  . Html::rawElement( 'td', [], $authors )
533  . Html::closeElement( 'tr' );
534  }
535  $out .= Html::closeElement( 'table' );
536 
537  return $out;
538  }
539 
545  protected function getParserTags() {
547 
548  $tags = $wgParser->getTags();
549 
550  if ( count( $tags ) ) {
552  'h2',
553  [
554  'class' => 'mw-headline plainlinks',
555  'id' => 'mw-version-parser-extensiontags',
556  ],
558  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
559  $this->msg( 'version-parser-extensiontags' )->parse(),
560  false /* msg()->parse() already escapes */
561  )
562  );
563 
564  array_walk( $tags, function ( &$value ) {
565  // Bidirectional isolation improves readability in RTL wikis
567  'bdi',
568  // Prevent < and > from slipping to another line
569  [
570  'style' => 'white-space: nowrap;',
571  ],
572  "<$value>"
573  );
574  } );
575 
576  $out .= $this->listToText( $tags );
577  } else {
578  $out = '';
579  }
580 
581  return $out;
582  }
583 
589  protected function getParserFunctionHooks() {
591 
592  $fhooks = $wgParser->getFunctionHooks();
593  if ( count( $fhooks ) ) {
595  'h2',
596  [
597  'class' => 'mw-headline plainlinks',
598  'id' => 'mw-version-parser-function-hooks',
599  ],
601  'https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
602  $this->msg( 'version-parser-function-hooks' )->parse(),
603  false /* msg()->parse() already escapes */
604  )
605  );
606 
607  $out .= $this->listToText( $fhooks );
608  } else {
609  $out = '';
610  }
611 
612  return $out;
613  }
614 
625  protected function getExtensionCategory( $type, $message ) {
626  global $wgExtensionCredits;
627 
628  $out = '';
629 
630  if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
631  $out .= $this->openExtType( $message, 'credits-' . $type );
632 
633  usort( $wgExtensionCredits[$type], [ $this, 'compare' ] );
634 
635  foreach ( $wgExtensionCredits[$type] as $extension ) {
636  $out .= $this->getCreditsForExtension( $extension );
637  }
638  }
639 
640  return $out;
641  }
642 
649  public function compare( $a, $b ) {
650  if ( $a['name'] === $b['name'] ) {
651  return 0;
652  } else {
653  return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
654  ? 1
655  : -1;
656  }
657  }
658 
676  public function getCreditsForExtension( array $extension ) {
677  $out = $this->getOutput();
678 
679  // We must obtain the information for all the bits and pieces!
680  // ... such as extension names and links
681  if ( isset( $extension['namemsg'] ) ) {
682  // Localized name of extension
683  $extensionName = $this->msg( $extension['namemsg'] )->text();
684  } elseif ( isset( $extension['name'] ) ) {
685  // Non localized version
686  $extensionName = $extension['name'];
687  } else {
688  $extensionName = $this->msg( 'version-no-ext-name' )->text();
689  }
690 
691  if ( isset( $extension['url'] ) ) {
692  $extensionNameLink = Linker::makeExternalLink(
693  $extension['url'],
694  $extensionName,
695  true,
696  '',
697  [ 'class' => 'mw-version-ext-name' ]
698  );
699  } else {
700  $extensionNameLink = $extensionName;
701  }
702 
703  // ... and the version information
704  // If the extension path is set we will check that directory for GIT
705  // metadata in an attempt to extract date and vcs commit metadata.
706  $canonicalVersion = '&ndash;';
707  $extensionPath = null;
708  $vcsVersion = null;
709  $vcsLink = null;
710  $vcsDate = null;
711 
712  if ( isset( $extension['version'] ) ) {
713  $canonicalVersion = $out->parseInline( $extension['version'] );
714  }
715 
716  if ( isset( $extension['path'] ) ) {
717  global $IP;
718  $extensionPath = dirname( $extension['path'] );
719  if ( $this->coreId == '' ) {
720  wfDebug( 'Looking up core head id' );
721  $coreHeadSHA1 = self::getGitHeadSha1( $IP );
722  if ( $coreHeadSHA1 ) {
723  $this->coreId = $coreHeadSHA1;
724  }
725  }
727  $memcKey = wfMemcKey( 'specialversion-ext-version-text', $extension['path'], $this->coreId );
728  list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
729 
730  if ( !$vcsVersion ) {
731  wfDebug( "Getting VCS info for extension {$extension['name']}" );
732  $gitInfo = new GitInfo( $extensionPath );
733  $vcsVersion = $gitInfo->getHeadSHA1();
734  if ( $vcsVersion !== false ) {
735  $vcsVersion = substr( $vcsVersion, 0, 7 );
736  $vcsLink = $gitInfo->getHeadViewUrl();
737  $vcsDate = $gitInfo->getHeadCommitDate();
738  }
739  $cache->set( $memcKey, [ $vcsVersion, $vcsLink, $vcsDate ], 60 * 60 * 24 );
740  } else {
741  wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
742  }
743  }
744 
745  $versionString = Html::rawElement(
746  'span',
747  [ 'class' => 'mw-version-ext-version' ],
748  $canonicalVersion
749  );
750 
751  if ( $vcsVersion ) {
752  if ( $vcsLink ) {
753  $vcsVerString = Linker::makeExternalLink(
754  $vcsLink,
755  $this->msg( 'version-version', $vcsVersion ),
756  true,
757  '',
758  [ 'class' => 'mw-version-ext-vcs-version' ]
759  );
760  } else {
761  $vcsVerString = Html::element( 'span',
762  [ 'class' => 'mw-version-ext-vcs-version' ],
763  "({$vcsVersion})"
764  );
765  }
766  $versionString .= " {$vcsVerString}";
767 
768  if ( $vcsDate ) {
769  $vcsTimeString = Html::element( 'span',
770  [ 'class' => 'mw-version-ext-vcs-timestamp' ],
771  $this->getLanguage()->timeanddate( $vcsDate, true )
772  );
773  $versionString .= " {$vcsTimeString}";
774  }
775  $versionString = Html::rawElement( 'span',
776  [ 'class' => 'mw-version-ext-meta-version' ],
777  $versionString
778  );
779  }
780 
781  // ... and license information; if a license file exists we
782  // will link to it
783  $licenseLink = '';
784  if ( isset( $extension['name'] ) ) {
785  $licenseName = null;
786  if ( isset( $extension['license-name'] ) ) {
787  $licenseName = $out->parseInline( $extension['license-name'] );
788  } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
789  $licenseName = $this->msg( 'version-ext-license' )->escaped();
790  }
791  if ( $licenseName !== null ) {
792  $licenseLink = Linker::link(
793  $this->getPageTitle( 'License/' . $extension['name'] ),
794  $licenseName,
795  [
796  'class' => 'mw-version-ext-license',
797  'dir' => 'auto',
798  ]
799  );
800  }
801  }
802 
803  // ... and generate the description; which can be a parameterized l10n message
804  // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
805  // up string
806  if ( isset( $extension['descriptionmsg'] ) ) {
807  // Localized description of extension
808  $descriptionMsg = $extension['descriptionmsg'];
809 
810  if ( is_array( $descriptionMsg ) ) {
811  $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
812  array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
813  array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
814  $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
815  } else {
816  $description = $this->msg( $descriptionMsg )->text();
817  }
818  } elseif ( isset( $extension['description'] ) ) {
819  // Non localized version
820  $description = $extension['description'];
821  } else {
822  $description = '';
823  }
824  $description = $out->parseInline( $description );
825 
826  // ... now get the authors for this extension
827  $authors = isset( $extension['author'] ) ? $extension['author'] : [];
828  $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
829 
830  // Finally! Create the table
831  $html = Html::openElement( 'tr', [
832  'class' => 'mw-version-ext',
833  'id' => Sanitizer::escapeId( 'mw-version-ext-' . $extension['name'] )
834  ]
835  );
836 
837  $html .= Html::rawElement( 'td', [], $extensionNameLink );
838  $html .= Html::rawElement( 'td', [], $versionString );
839  $html .= Html::rawElement( 'td', [], $licenseLink );
840  $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-description' ], $description );
841  $html .= Html::rawElement( 'td', [ 'class' => 'mw-version-ext-authors' ], $authors );
842 
843  $html .= Html::closeElement( 'tr' );
844 
845  return $html;
846  }
847 
853  private function getWgHooks() {
854  global $wgSpecialVersionShowHooks, $wgHooks;
855 
856  if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
857  $myWgHooks = $wgHooks;
858  ksort( $myWgHooks );
859 
860  $ret = [];
861  $ret[] = '== {{int:version-hooks}} ==';
862  $ret[] = Html::openElement( 'table', [ 'class' => 'wikitable', 'id' => 'sv-hooks' ] );
863  $ret[] = Html::openElement( 'tr' );
864  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-name' )->text() );
865  $ret[] = Html::element( 'th', [], $this->msg( 'version-hook-subscribedby' )->text() );
866  $ret[] = Html::closeElement( 'tr' );
867 
868  foreach ( $myWgHooks as $hook => $hooks ) {
869  $ret[] = Html::openElement( 'tr' );
870  $ret[] = Html::element( 'td', [], $hook );
871  $ret[] = Html::element( 'td', [], $this->listToText( $hooks ) );
872  $ret[] = Html::closeElement( 'tr' );
873  }
874 
875  $ret[] = Html::closeElement( 'table' );
876 
877  return implode( "\n", $ret );
878  } else {
879  return '';
880  }
881  }
882 
883  private function openExtType( $text = null, $name = null ) {
884  $out = '';
885 
886  $opt = [ 'colspan' => 5 ];
887  if ( $this->firstExtOpened ) {
888  // Insert a spacing line
889  $out .= Html::rawElement( 'tr', [ 'class' => 'sv-space' ],
890  Html::element( 'td', $opt )
891  );
892  }
893  $this->firstExtOpened = true;
894 
895  if ( $name ) {
896  $opt['id'] = "sv-$name";
897  }
898 
899  if ( $text !== null ) {
900  $out .= Html::rawElement( 'tr', [],
901  Html::element( 'th', $opt, $text )
902  );
903  }
904 
905  $firstHeadingMsg = ( $name === 'credits-skin' )
906  ? 'version-skin-colheader-name'
907  : 'version-ext-colheader-name';
908  $out .= Html::openElement( 'tr' );
909  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
910  $this->msg( $firstHeadingMsg )->text() );
911  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
912  $this->msg( 'version-ext-colheader-version' )->text() );
913  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
914  $this->msg( 'version-ext-colheader-license' )->text() );
915  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
916  $this->msg( 'version-ext-colheader-description' )->text() );
917  $out .= Html::element( 'th', [ 'class' => 'mw-version-ext-col-label' ],
918  $this->msg( 'version-ext-colheader-credits' )->text() );
919  $out .= Html::closeElement( 'tr' );
920 
921  return $out;
922  }
923 
929  private function IPInfo() {
930  $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
931 
932  return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
933  }
934 
956  public function listAuthors( $authors, $extName, $extDir ) {
957  $hasOthers = false;
958 
959  $list = [];
960  foreach ( (array)$authors as $item ) {
961  if ( $item == '...' ) {
962  $hasOthers = true;
963 
964  if ( $extName && $this->getExtAuthorsFileName( $extDir ) ) {
965  $text = Linker::link(
966  $this->getPageTitle( "Credits/$extName" ),
967  $this->msg( 'version-poweredby-others' )->escaped()
968  );
969  } else {
970  $text = $this->msg( 'version-poweredby-others' )->escaped();
971  }
972  $list[] = $text;
973  } elseif ( substr( $item, -5 ) == ' ...]' ) {
974  $hasOthers = true;
975  $list[] = $this->getOutput()->parseInline(
976  substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
977  );
978  } else {
979  $list[] = $this->getOutput()->parseInline( $item );
980  }
981  }
982 
983  if ( $extName && !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
984  $list[] = $text = Linker::link(
985  $this->getPageTitle( "Credits/$extName" ),
986  $this->msg( 'version-poweredby-others' )->escaped()
987  );
988  }
989 
990  return $this->listToText( $list, false );
991  }
992 
1004  public static function getExtAuthorsFileName( $extDir ) {
1005  if ( !$extDir ) {
1006  return false;
1007  }
1008 
1009  foreach ( scandir( $extDir ) as $file ) {
1010  $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
1011  if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt|\.wiki|\.mediawiki)?$/', $file ) &&
1012  is_readable( $fullPath ) &&
1013  is_file( $fullPath )
1014  ) {
1015  return $fullPath;
1016  }
1017  }
1018 
1019  return false;
1020  }
1021 
1033  public static function getExtLicenseFileName( $extDir ) {
1034  if ( !$extDir ) {
1035  return false;
1036  }
1037 
1038  foreach ( scandir( $extDir ) as $file ) {
1039  $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
1040  if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
1041  is_readable( $fullPath ) &&
1042  is_file( $fullPath )
1043  ) {
1044  return $fullPath;
1045  }
1046  }
1047 
1048  return false;
1049  }
1050 
1059  public function listToText( $list, $sort = true ) {
1060  if ( !count( $list ) ) {
1061  return '';
1062  }
1063  if ( $sort ) {
1064  sort( $list );
1065  }
1066 
1067  return $this->getLanguage()
1068  ->listToText( array_map( [ __CLASS__, 'arrayToString' ], $list ) );
1069  }
1070 
1079  public static function arrayToString( $list ) {
1080  if ( is_array( $list ) && count( $list ) == 1 ) {
1081  $list = $list[0];
1082  }
1083  if ( $list instanceof Closure ) {
1084  // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
1085  return 'Closure';
1086  } elseif ( is_object( $list ) ) {
1087  $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
1088 
1089  return $class;
1090  } elseif ( !is_array( $list ) ) {
1091  return $list;
1092  } else {
1093  if ( is_object( $list[0] ) ) {
1094  $class = get_class( $list[0] );
1095  } else {
1096  $class = $list[0];
1097  }
1098 
1099  return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
1100  }
1101  }
1102 
1107  public static function getGitHeadSha1( $dir ) {
1108  $repo = new GitInfo( $dir );
1109 
1110  return $repo->getHeadSHA1();
1111  }
1112 
1117  public static function getGitCurrentBranch( $dir ) {
1118  $repo = new GitInfo( $dir );
1119  return $repo->getCurrentBranch();
1120  }
1121 
1126  public function getEntryPointInfo() {
1128  $scriptPath = $wgScriptPath ? $wgScriptPath : "/";
1129  $entryPoints = [
1130  'version-entrypoints-articlepath' => $wgArticlePath,
1131  'version-entrypoints-scriptpath' => $scriptPath,
1132  'version-entrypoints-index-php' => wfScript( 'index' ),
1133  'version-entrypoints-api-php' => wfScript( 'api' ),
1134  'version-entrypoints-load-php' => wfScript( 'load' ),
1135  ];
1136 
1137  $language = $this->getLanguage();
1138  $thAttribures = [
1139  'dir' => $language->getDir(),
1140  'lang' => $language->getHtmlCode()
1141  ];
1142  $out = Html::element(
1143  'h2',
1144  [ 'id' => 'mw-version-entrypoints' ],
1145  $this->msg( 'version-entrypoints' )->text()
1146  ) .
1147  Html::openElement( 'table',
1148  [
1149  'class' => 'wikitable plainlinks',
1150  'id' => 'mw-version-entrypoints-table',
1151  'dir' => 'ltr',
1152  'lang' => 'en'
1153  ]
1154  ) .
1155  Html::openElement( 'tr' ) .
1156  Html::element(
1157  'th',
1158  $thAttribures,
1159  $this->msg( 'version-entrypoints-header-entrypoint' )->text()
1160  ) .
1161  Html::element(
1162  'th',
1163  $thAttribures,
1164  $this->msg( 'version-entrypoints-header-url' )->text()
1165  ) .
1166  Html::closeElement( 'tr' );
1167 
1168  foreach ( $entryPoints as $message => $value ) {
1169  $url = wfExpandUrl( $value, PROTO_RELATIVE );
1170  $out .= Html::openElement( 'tr' ) .
1171  // ->text() looks like it should be ->parse(), but this function
1172  // returns wikitext, not HTML, boo
1173  Html::rawElement( 'td', [], $this->msg( $message )->text() ) .
1174  Html::rawElement( 'td', [], Html::rawElement( 'code', [], "[$url $value]" ) ) .
1175  Html::closeElement( 'tr' );
1176  }
1177 
1178  $out .= Html::closeElement( 'table' );
1179 
1180  return $out;
1181  }
1182 
1183  protected function getGroupName() {
1184  return 'wiki';
1185  }
1186 }
static getExtensionTypes()
Returns an array with the base extension types.
static getVersionLinkedGit()
static closeElement($element)
Returns "".
Definition: Html.php:306
static getExtAuthorsFileName($extDir)
Obtains the full path of an extensions authors or credits file if one exists.
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:1798
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
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
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:762
the array() calling protocol came about after MediaWiki 1.4rc1.
$wgVersion
MediaWiki version number.
wfScript($script= 'index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
if(count($args)==0) $dir
wfIsHHVM()
Check if we are running under HHVM.
static element($element, $attribs=null, $contents= '', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
$IP
Definition: WebStart.php:58
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:1798
$wgParser
Definition: Setup.php:809
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:210
if(!isset($args[0])) $lang
$sort
execute($par)
main()
$value
getExtensionCategory($type, $message)
Creates and returns the HTML for a single extension category.
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2548
$wgHooks['ArticleShow'][]
Definition: hooks.txt:110
compare($a, $b)
Callback to sort extensions by type.
msg()
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
wfExpandUrl($url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
getParserTags()
Obtains a list of installed parser tags and the associated H2 header.
$wgArticlePath
Definition: img_auth.php:45
getParserFunctionHooks()
Obtains a list of installed parser function hooks and the associated H2 header.
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
openExtType($text=null, $name=null)
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
outputHeader($summaryMessageKey= '')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
IPInfo()
Get information about client's IP address.
listToText($list, $sort=true)
Convert an array of items into a list for display.
static closeElement($element)
Shortcut to close an XML element.
Definition: Xml.php:118
Parent class for all special pages.
Definition: SpecialPage.php:36
static openElement($element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:248
static getICUVersion()
Return the version of ICU library used by PHP's intl extension, or false when the extension is not in...
wfGetCache($cacheType)
Get a specific cache object.
static getExtensionTypeName($type)
Returns the internationalized name for an extension type.
static getGitCurrentBranch($dir)
getExternalLibraries()
Generate an HTML table for external libraries that are installed.
static $extensionTypes
static openElement($element, $attribs=null)
This opens an XML element.
Definition: Xml.php:109
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 after processing 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
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.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes! ...
static getExtLicenseFileName($extDir)
Obtains the full path of an extensions copying or license file if one exists.
const DB_SLAVE
Definition: Defines.php:46
static getwgVersionLinked()
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
const PROTO_RELATIVE
Definition: Defines.php:263
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
static getVersionLinked()
Return a wikitext-formatted string of the MediaWiki version with a link to the Git SHA1 of head if av...
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 link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:195
static escapeId($id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1132
static getCopyrightAndAuthorList()
Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text.
static makeExternalLink($url, $text, $escape=true, $linktype= '', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:1052
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
static arrayToString($list)
Convert an array or object to a string for display.
$wgScriptPath
The path we should point to.
getExtensionCredits()
Generate wikitext showing the name, URL, author and description of each extension.
static getMediaWikiCredits()
Returns wiki text showing the license information.
const CACHE_ANYTHING
Definition: Defines.php:101
getLanguage()
Shortcut to get user's language.
Give information about the version of MediaWiki, PHP, the DB and extensions.
listAuthors($authors, $extName, $extDir)
Return a formatted unsorted list of authors.
wfMemcKey()
Make a cache key for the local wiki.
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.
getCreditsForExtension(array $extension)
Creates and formats a version line for a single extension.
$version
Definition: parserTests.php:85
$extensions
getRequest()
Get the WebRequest being used for this instance.
static getVersion($flags= '', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:230
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2338
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:1473
$coreId
Stores the current rev id/SHA hash of MediaWiki core.
getPageTitle($subpage=false)
Get a self-referential title object.
static getGitHeadSha1($dir)
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310