3 * Implements Special:Version
5 * Copyright © 2005 Ævar Arnfjörð Bjarmason
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
23 * @ingroup SpecialPage
27 * Give information about the version of MediaWiki, PHP, the DB and extensions
29 * @ingroup SpecialPage
31 class SpecialVersion
extends SpecialPage
{
33 protected $firstExtOpened = false;
35 protected static $extensionTypes = false;
37 protected static $viewvcUrls = array(
38 'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
39 'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
40 'https://svn.wikimedia.org/svnroot/mediawiki' => 'https://svn.wikimedia.org/viewvc/mediawiki',
43 public function __construct() {
44 parent
::__construct( 'Version' );
50 public function execute( $par ) {
51 global $IP, $wgExtensionCredits;
54 $this->outputHeader();
55 $out = $this->getOutput();
56 $out->allowClickjacking();
58 // Explode the sub page information into useful bits
59 $parts = explode( '/', (string)$par );
61 if ( isset( $parts[1] ) ) {
62 $extName = str_replace( '_', ' ', $parts[1] );
64 foreach ( $wgExtensionCredits as $group => $extensions ) {
65 foreach ( $extensions as $ext ) {
66 if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
73 $out->setStatusCode( 404 );
76 $extName = 'MediaWiki';
79 // Now figure out what to do
80 switch ( strtolower( $parts[0] ) ) {
82 $wikiText = '{{int:version-credits-not-found}}';
83 if ( $extName === 'MediaWiki' ) {
84 $wikiText = file_get_contents( $IP . '/CREDITS' );
85 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
86 $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
88 $wikiText = file_get_contents( $file );
92 $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
93 $out->addWikiText( $wikiText );
97 $wikiText = '{{int:version-license-not-found}}';
98 if ( $extName === 'MediaWiki' ) {
99 $wikiText = file_get_contents( $IP . '/COPYING' );
100 } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
101 $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
103 $wikiText = file_get_contents( $file );
104 if ( !isset( $extNode['license-name'] ) ) {
105 // If the developer did not explicitly set license-name they probably
106 // are unaware that we're now sucking this file in and thus it's probably
107 // not wikitext friendly.
108 $wikiText = "<pre>$wikiText</pre>";
113 $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
114 $out->addWikiText( $wikiText );
118 $out->addModules( 'mediawiki.special.version' );
120 $this->getMediaWikiCredits() .
121 $this->softwareInformation() .
122 $this->getEntryPointInfo()
125 $this->getExtensionCredits() .
126 $this->getParserTags() .
127 $this->getParserFunctionHooks()
129 $out->addWikiText( $this->getWgHooks() );
130 $out->addHTML( $this->IPInfo() );
137 * Returns wiki text showing the license information.
141 private static function getMediaWikiCredits() {
142 $ret = Xml
::element( 'h2', array( 'id' => 'mw-version-license' ), wfMessage( 'version-license' )->text() );
144 // This text is always left-to-right.
145 $ret .= '<div class="plainlinks">';
147 " . self
::getCopyrightAndAuthorList() . "\n
148 " . wfMessage( 'version-license-info' )->text();
151 return str_replace( "\t\t", '', $ret ) . "\n";
155 * Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text
159 public static function getCopyrightAndAuthorList() {
162 if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
163 $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' . wfMessage( 'version-poweredby-others' )->text() . ']';
165 $othersLink = '[[Special:Version/Credits|' . wfMessage( 'version-poweredby-others' )->text() . ']]';
168 $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' . wfMessage( 'version-poweredby-translators' )->text() . ']';
171 'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
172 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason',
173 'Niklas Laxström', 'Domas Mituzas', 'Rob Church', 'Yuri Astrakhan',
174 'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
175 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
176 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
177 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
178 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink,
182 return wfMessage( 'version-poweredby-credits', MWTimestamp
::getLocalInstance()->format( 'Y' ),
183 $wgLang->listToText( $authorList ) )->text();
187 * Returns wiki text showing the third party software versions (apache, php, mysql).
191 static function softwareInformation() {
192 $dbr = wfGetDB( DB_SLAVE
);
194 // Put the software in an array of form 'name' => 'version'. All messages should
195 // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
196 // wikimarkup can be used.
198 $software['[https://www.mediawiki.org/ MediaWiki]'] = self
::getVersionLinked();
199 $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . PHP_SAPI
. ")";
200 $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
202 // Allow a hook to add/remove items.
203 wfRunHooks( 'SoftwareInfo', array( &$software ) );
205 $out = Xml
::element( 'h2', array( 'id' => 'mw-version-software' ), wfMessage( 'version-software' )->text() ) .
206 Xml
::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) .
208 <th>" . wfMessage( 'version-software-product' )->text() . "</th>
209 <th>" . wfMessage( 'version-software-version' )->text() . "</th>
212 foreach ( $software as $name => $version ) {
214 <td>" . $name . "</td>
215 <td dir=\"ltr\">" . $version . "</td>
219 return $out . Xml
::closeElement( 'table' );
223 * Return a string of the MediaWiki version with SVN revision if available.
225 * @param $flags String
228 public static function getVersion( $flags = '' ) {
229 global $wgVersion, $IP;
230 wfProfileIn( __METHOD__
);
232 $gitInfo = self
::getGitHeadSha1( $IP );
233 $svnInfo = self
::getSvnInfo( $IP );
234 if ( !$svnInfo && !$gitInfo ) {
235 $version = $wgVersion;
236 } elseif ( $gitInfo && $flags === 'nodb' ) {
237 $shortSha1 = substr( $gitInfo, 0, 7 );
238 $version = "$wgVersion ($shortSha1)";
239 } elseif ( $gitInfo ) {
240 $shortSha1 = substr( $gitInfo, 0, 7 );
241 $shortSha1 = wfMessage( 'parentheses' )->params( $shortSha1 )->escaped();
242 $version = "$wgVersion $shortSha1";
243 } elseif ( $flags === 'nodb' ) {
244 $version = "$wgVersion (r{$svnInfo['checkout-rev']})";
246 $version = $wgVersion . ' ' .
248 'version-svn-revision',
249 isset( $info['directory-rev'] ) ?
$info['directory-rev'] : '',
250 $info['checkout-rev']
254 wfProfileOut( __METHOD__
);
259 * Return a wikitext-formatted string of the MediaWiki version with a link to
260 * the SVN revision or the git SHA1 of head if available.
261 * Git is prefered over Svn
262 * The fallback is just $wgVersion
266 public static function getVersionLinked() {
268 wfProfileIn( __METHOD__
);
270 $gitVersion = self
::getVersionLinkedGit();
274 $svnVersion = self
::getVersionLinkedSvn();
278 $v = $wgVersion; // fallback
282 wfProfileOut( __METHOD__
);
287 * @return string wgVersion + a link to subversion revision of svn BASE
289 private static function getVersionLinkedSvn() {
292 $info = self
::getSvnInfo( $IP );
293 if ( !isset( $info['checkout-rev'] ) ) {
297 $linkText = wfMessage(
298 'version-svn-revision',
299 isset( $info['directory-rev'] ) ?
$info['directory-rev'] : '',
300 $info['checkout-rev']
303 if ( isset( $info['viewvc-url'] ) ) {
304 $version = "[{$info['viewvc-url']} $linkText]";
306 $version = $linkText;
309 return self
::getwgVersionLinked() . " $version";
315 private static function getwgVersionLinked() {
318 if ( wfRunHooks( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) {
319 $versionParts = array();
320 preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
321 $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
323 return "[$versionUrl $wgVersion]";
327 * @since 1.22 Returns the HEAD date in addition to the sha1 and link
328 * @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars with link and date, or false on failure
330 private static function getVersionLinkedGit() {
333 $gitInfo = new GitInfo( $IP );
334 $headSHA1 = $gitInfo->getHeadSHA1();
339 $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')';
341 $gitHeadUrl = $gitInfo->getHeadViewUrl();
342 if ( $gitHeadUrl !== false ) {
343 $shortSHA1 = "[$gitHeadUrl $shortSHA1]";
346 $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
347 if ( $gitHeadCommitDate ) {
348 $shortSHA1 .= Html
::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
351 return self
::getwgVersionLinked() . " $shortSHA1";
355 * Returns an array with the base extension types.
356 * Type is stored as array key, the message as array value.
358 * TODO: ideally this would return all extension types, including
359 * those added by SpecialVersionExtensionTypes. This is not possible
360 * since this hook is passing along $this though.
366 public static function getExtensionTypes() {
367 if ( self
::$extensionTypes === false ) {
368 self
::$extensionTypes = array(
369 'specialpage' => wfMessage( 'version-specialpages' )->text(),
370 'parserhook' => wfMessage( 'version-parserhooks' )->text(),
371 'variable' => wfMessage( 'version-variables' )->text(),
372 'media' => wfMessage( 'version-mediahandlers' )->text(),
373 'antispam' => wfMessage( 'version-antispam' )->text(),
374 'skin' => wfMessage( 'version-skins' )->text(),
375 'api' => wfMessage( 'version-api' )->text(),
376 'other' => wfMessage( 'version-other' )->text(),
379 wfRunHooks( 'ExtensionTypes', array( &self
::$extensionTypes ) );
382 return self
::$extensionTypes;
386 * Returns the internationalized name for an extension type.
390 * @param $type String
394 public static function getExtensionTypeName( $type ) {
395 $types = self
::getExtensionTypes();
396 return isset( $types[$type] ) ?
$types[$type] : $types['other'];
400 * Generate wikitext showing extensions name, URL, author and description.
402 * @return String: Wikitext
404 function getExtensionCredits() {
405 global $wgExtensionCredits;
407 if ( !count( $wgExtensionCredits ) ) {
411 $extensionTypes = self
::getExtensionTypes();
414 * @deprecated as of 1.17, use hook ExtensionTypes instead.
416 wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
418 $out = Xml
::element( 'h2', array( 'id' => 'mw-version-ext' ), $this->msg( 'version-extensions' )->text() ) .
419 Xml
::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ) );
421 // Make sure the 'other' type is set to an array.
422 if ( !array_key_exists( 'other', $wgExtensionCredits ) ) {
423 $wgExtensionCredits['other'] = array();
426 // Find all extensions that do not have a valid type and give them the type 'other'.
427 foreach ( $wgExtensionCredits as $type => $extensions ) {
428 if ( !array_key_exists( $type, $extensionTypes ) ) {
429 $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions );
433 // Loop through the extension categories to display their extensions in the list.
434 foreach ( $extensionTypes as $type => $message ) {
435 if ( $type != 'other' ) {
436 $out .= $this->getExtensionCategory( $type, $message );
440 // We want the 'other' type to be last in the list.
441 $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
443 $out .= Xml
::closeElement( 'table' );
449 * Obtains a list of installed parser tags and the associated H2 header
451 * @return string HTML output
453 protected function getParserTags() {
456 $tags = $wgParser->getTags();
458 if ( count( $tags ) ) {
459 $out = Html
::rawElement(
461 array( 'class' => 'mw-headline' ),
462 Linker
::makeExternalLink(
463 '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
464 $this->msg( 'version-parser-extensiontags' )->parse(),
465 false /* msg()->parse() already escapes */
469 array_walk( $tags, function( &$value ) {
470 $value = '<' . htmlentities( $value ) . '>';
472 $out .= $this->listToText( $tags );
481 * Obtains a list of installed parser function hooks and the associated H2 header
483 * @return string HTML output
485 protected function getParserFunctionHooks() {
488 $fhooks = $wgParser->getFunctionHooks();
489 if ( count( $fhooks ) ) {
490 $out = Html
::rawElement( 'h2', array( 'class' => 'mw-headline' ) , Linker
::makeExternalLink(
491 '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
492 $this->msg( 'version-parser-function-hooks' )->parse(),
493 false /* msg()->parse() already escapes */
496 $out .= $this->listToText( $fhooks );
505 * Creates and returns the HTML for a single extension category.
509 * @param $type String
510 * @param $message String
514 protected function getExtensionCategory( $type, $message ) {
515 global $wgExtensionCredits;
519 if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
520 $out .= $this->openExtType( $message, 'credits-' . $type );
522 usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
524 foreach ( $wgExtensionCredits[$type] as $extension ) {
525 $out .= $this->getCreditsForExtension( $extension );
533 * Callback to sort extensions by type.
538 function compare( $a, $b ) {
539 if ( $a['name'] === $b['name'] ) {
542 return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
549 * Creates and formats a version line for a single extension.
551 * Information for five columns will be created. Parameters required in the
552 * $extension array for part rendering are indicated in ()
553 * - The name of (name), and URL link to (url), the extension
554 * - Official version number (version) and if available version control system
555 * revision (path), link, and date
556 * - If available the short name of the license (license-name) and a linke
557 * to ((LICENSE)|(COPYING))(\.txt)? if it exists.
558 * - Description of extension (descriptionmsg or description)
559 * - List of authors (author) and link to a ((AUTHORS)|(CREDITS))(\.txt)? file if it exists
561 * @param $extension Array
563 * @return string raw HTML
565 function getCreditsForExtension( array $extension ) {
566 $out = $this->getOutput();
568 // We must obtain the information for all the bits and pieces!
569 // ... such as extension names and links
570 $extensionName = isset( $extension['name'] ) ?
$extension['name'] : '[no name]';
571 if ( isset( $extension['url'] ) ) {
572 $extensionNameLink = Linker
::makeExternalLink(
577 array( 'class' => 'mw-version-ext-name' )
580 $extensionNameLink = $extensionName;
583 // ... and the version information
584 // If the extension path is set we will check that directory for GIT and SVN
585 // metadata in an attempt to extract date and vcs commit metadata.
586 $canonicalVersion = '–';
587 $extensionPath = null;
592 if ( isset( $extension['version'] ) ) {
593 $canonicalVersion = $out->parseInline( $extension['version'] );
596 if ( isset( $extension['path'] ) ) {
597 $extensionPath = dirname( $extension['path'] );
598 $gitInfo = new GitInfo( $extensionPath );
599 $vcsVersion = $gitInfo->getHeadSHA1();
600 if ( $vcsVersion !== false ) {
601 $vcsVersion = substr( $vcsVersion, 0, 7 );
602 $vcsLink = $gitInfo->getHeadViewUrl();
603 $vcsDate = $gitInfo->getHeadCommitDate();
605 $svnInfo = self
::getSvnInfo( $extensionPath );
606 if ( $svnInfo !== false ) {
607 $vcsVersion = $this->msg( 'version-svn-revision', $svnInfo['checkout-rev'] )->text();
608 $vcsLink = isset( $svnInfo['viewvc-url'] ) ?
$svnInfo['viewvc-url'] : '';
613 $versionString = Html
::rawElement( 'span', array( 'class' => 'mw-version-ext-version' ), $canonicalVersion );
616 $vcsVerString = Linker
::makeExternalLink(
618 $this->msg( 'version-version', $vcsVersion ),
621 array( 'class' => 'mw-version-ext-vcs-version' )
624 $vcsVerString = Html
::element( 'span',
625 array( 'class' => 'mw-version-ext-vcs-version'),
629 $versionString .= " {$vcsVerString}";
632 $vcsTimeString = Html
::element( 'span',
633 array( 'class' => 'mw-version-ext-vcs-timestamp'),
634 $this->getLanguage()->timeanddate( $vcsDate )
636 $versionString .= " {$vcsTimeString}";
638 $versionString = Html
::rawElement( 'span',
639 array( 'class' => 'mw-version-ext-meta-version' ),
644 // ... and license information; if a license file exists we
647 if ( isset( $extension['license-name'] ) ) {
648 $licenseLink = Linker
::link(
649 $this->getPageTitle( 'License/' . $extensionName ),
650 $out->parseInline( $extension['license-name'] ),
651 array( 'class' => 'mw-version-ext-license' )
653 } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
654 $licenseLink = Linker
::link(
655 $this->getPageTitle( 'License/' . $extensionName ),
656 $this->msg( 'version-ext-license' ),
657 array( 'class' => 'mw-version-ext-license' )
661 // ... and generate the description; which can be a parameterized l10n message
662 // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
664 if ( isset( $extension['descriptionmsg'] ) ) {
665 // Localized description of extension
666 $descriptionMsg = $extension['descriptionmsg'];
668 if ( is_array( $descriptionMsg ) ) {
669 $descriptionMsgKey = $descriptionMsg[0]; // Get the message key
670 array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only
671 array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
672 $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text();
674 $description = $this->msg( $descriptionMsg )->text();
676 } elseif ( isset( $extension['description'] ) ) {
677 // Non localized version
678 $description = $out->parseInline( $extension['description'] );
682 $description = $out->parseInline( $description );
684 // ... now get the authors for this extension
685 $authors = isset( $extension['author'] ) ?
$extension['author'] : array();
686 $authors = $this->listAuthors( $authors, $extensionName, $extensionPath );
688 // Finally! Create the table
689 $html = Html
::openElement( 'tr', array(
690 'class' => 'mw-version-ext',
691 'id' => "mw-version-ext-{$extensionName}"
695 $html .= Html
::rawElement( 'td', array(), $extensionNameLink );
696 $html .= Html
::rawElement( 'td', array(), $versionString );
697 $html .= Html
::rawElement( 'td', array(), $licenseLink );
698 $html .= Html
::rawElement( 'td', array( 'class' => 'mw-version-ext-description' ), $description );
699 $html .= Html
::rawElement( 'td', array( 'class' => 'mw-version-ext-authors' ), $authors );
701 $html .= Html
::closeElement( 'td' );
707 * Generate wikitext showing hooks in $wgHooks.
709 * @return String: wikitext
711 private function getWgHooks() {
712 global $wgSpecialVersionShowHooks, $wgHooks;
714 if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
715 $myWgHooks = $wgHooks;
719 $ret[] = '== {{int:version-hooks}} ==';
720 $ret[] = Html
::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) );
721 $ret[] = Html
::openElement( 'tr' );
722 $ret[] = Html
::element( 'th', array(), $this->msg( 'version-hook-name' )->text() );
723 $ret[] = Html
::element( 'th', array(), $this->msg( 'version-hook-subscribedby' )->text() );
724 $ret[] = Html
::closeElement( 'tr' );
726 foreach ( $myWgHooks as $hook => $hooks ) {
727 $ret[] = Html
::openElement( 'tr' );
728 $ret[] = Html
::element( 'td', array(), $hook );
729 $ret[] = Html
::element( 'td', array(), $this->listToText( $hooks ) );
730 $ret[] = Html
::closeElement( 'tr' );
733 $ret[] = Html
::closeElement( 'table' );
735 return implode( "\n", $ret );
741 private function openExtType( $text, $name = null ) {
744 $opt = array( 'colspan' => 5 );
745 if ( $this->firstExtOpened
) {
746 // Insert a spacing line
747 $out .= Html
::rawElement( 'tr', array( 'class' => 'sv-space' ),
748 Html
::element( 'td', $opt )
751 $this->firstExtOpened
= true;
754 $opt['id'] = "sv-$name";
757 $out .= Html
::rawElement( 'tr', array(),
758 Html
::element( 'th', $opt, $text )
761 $out .= Html
::openElement( 'tr' );
762 $out .= Html
::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
763 $this->msg( 'version-ext-colheader-name' )->text() );
764 $out .= Html
::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
765 $this->msg( 'version-ext-colheader-version' )->text() );
766 $out .= Html
::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
767 $this->msg( 'version-ext-colheader-license' )->text() );
768 $out .= Html
::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
769 $this->msg( 'version-ext-colheader-description' )->text() );
770 $out .= Html
::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
771 $this->msg( 'version-ext-colheader-credits' )->text() );
772 $out .= Html
::closeElement( 'tr' );
778 * Get information about client's IP address.
780 * @return String: HTML fragment
782 private function IPInfo() {
783 $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
784 return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
788 * Return a formatted unsorted list of authors
791 * If an item in the $authors array is '...' it is assumed to indicate an
792 * 'and others' string which will then be linked to an ((AUTHORS)|(CREDITS))(\.txt)?
793 * file if it exists in $dir.
795 * Similarly an entry ending with ' ...]' is assumed to be a link to an
798 * If no '...' string variant is found, but an authors file is found an
799 * 'and others' will be added to the end of the credits.
801 * @param $authors mixed: string or array of strings
802 * @param $extName string: name of the extension for link creation
803 * @param $extDir string: path to the extension root directory
805 * @return String: HTML fragment
807 function listAuthors( $authors, $extName, $extDir ) {
811 foreach ( (array)$authors as $item ) {
812 if ( $item == '...' ) {
815 if ( $this->getExtAuthorsFileName( $extDir ) ) {
816 $text = Linker
::link(
817 $this->getPageTitle( "Credits/$extName" ),
818 $this->msg( 'version-poweredby-others' )->text()
821 $text = $this->msg( 'version-poweredby-others' )->text();
825 } elseif ( substr( $item, -5 ) == ' ...]' ) {
827 $list[] = $this->getOutput()->parseInline(
828 substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
832 $list[] = $this->getOutput()->parseInline( $item );
836 if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
837 $list[] = $text = Linker
::link(
838 $this->getPageTitle( "Credits/$extName" ),
839 $this->msg( 'version-poweredby-others' )->text()
843 return $this->listToText( $list, false );
847 * Obtains the full path of an extensions authors or credits file if
850 * @param string $extDir: Path to the extensions root directory
854 * @return bool|string False if no such file exists, otherwise returns
857 public static function getExtAuthorsFileName( $extDir ) {
862 foreach ( scandir( $extDir ) as $file ) {
863 $fullPath = $extDir . DIRECTORY_SEPARATOR
. $file;
864 if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt)?$/', $file ) &&
865 is_readable( $fullPath ) &&
876 * Obtains the full path of an extensions copying or license file if
879 * @param string $extDir: Path to the extensions root directory
883 * @return bool|string False if no such file exists, otherwise returns
886 public static function getExtLicenseFileName( $extDir ) {
891 foreach ( scandir( $extDir ) as $file ) {
892 $fullPath = $extDir . DIRECTORY_SEPARATOR
. $file;
893 if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
894 is_readable( $fullPath ) &&
905 * Convert an array of items into a list for display.
907 * @param array $list of elements to display
908 * @param $sort Boolean: whether to sort the items in $list
912 function listToText( $list, $sort = true ) {
913 $cnt = count( $list );
916 // Enforce always returning a string
917 return (string)self
::arrayToString( $list[0] );
918 } elseif ( $cnt == 0 ) {
924 return $this->getLanguage()->listToText( array_map( array( __CLASS__
, 'arrayToString' ), $list ) );
929 * Convert an array or object to a string for display.
931 * @param $list Mixed: will convert an array to string if given and return
932 * the paramater unaltered otherwise
936 public static function arrayToString( $list ) {
937 if ( is_array( $list ) && count( $list ) == 1 ) {
940 if ( is_object( $list ) ) {
941 $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
943 } elseif ( !is_array( $list ) ) {
946 if ( is_object( $list[0] ) ) {
947 $class = get_class( $list[0] );
951 return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
956 * Get an associative array of information about a given path, from its .svn
957 * subdirectory. Returns false on error, such as if the directory was not
958 * checked out with subversion.
962 * checkout-rev The revision which was checked out
964 * directory-rev The revision when the directory was last modified
965 * url The subversion URL of the directory
966 * repo-url The base URL of the repository
967 * viewvc-url A ViewVC URL pointing to the checked-out revision
971 public static function getSvnInfo( $dir ) {
972 // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
973 $entries = $dir . '/.svn/entries';
975 if ( !file_exists( $entries ) ) {
979 $lines = file( $entries );
980 if ( !count( $lines ) ) {
984 // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
985 if ( preg_match( '/^<\?xml/', $lines[0] ) ) {
986 // subversion is release <= 1.3
987 if ( !function_exists( 'simplexml_load_file' ) ) {
988 // We could fall back to expat... YUCK
992 // SimpleXml whines about the xmlns...
993 wfSuppressWarnings();
994 $xml = simplexml_load_file( $entries );
998 foreach ( $xml->entry
as $entry ) {
999 if ( $xml->entry
[0]['name'] == '' ) {
1000 // The directory entry should always have a revision marker.
1001 if ( $entry['revision'] ) {
1002 return array( 'checkout-rev' => intval( $entry['revision'] ) );
1011 // Subversion is release 1.4 or above.
1012 if ( count( $lines ) < 11 ) {
1017 'checkout-rev' => intval( trim( $lines[3] ) ),
1018 'url' => trim( $lines[4] ),
1019 'repo-url' => trim( $lines[5] ),
1020 'directory-rev' => intval( trim( $lines[10] ) )
1023 if ( isset( self
::$viewvcUrls[$info['repo-url']] ) ) {
1024 $viewvc = str_replace(
1026 self
::$viewvcUrls[$info['repo-url']],
1030 $viewvc .= '/?pathrev=';
1031 $viewvc .= urlencode( $info['checkout-rev'] );
1032 $info['viewvc-url'] = $viewvc;
1039 * Retrieve the revision number of a Subversion working directory.
1041 * @param string $dir directory of the svn checkout
1043 * @return Integer: revision number as int
1045 public static function getSvnRevision( $dir ) {
1046 $info = self
::getSvnInfo( $dir );
1048 if ( $info === false ) {
1050 } elseif ( isset( $info['checkout-rev'] ) ) {
1051 return $info['checkout-rev'];
1058 * @param string $dir directory of the git checkout
1059 * @return bool|String sha1 of commit HEAD points to
1061 public static function getGitHeadSha1( $dir ) {
1062 $repo = new GitInfo( $dir );
1063 return $repo->getHeadSHA1();
1067 * Get the list of entry points and their URLs
1068 * @return string Wikitext
1070 public function getEntryPointInfo() {
1071 global $wgArticlePath, $wgScriptPath;
1072 $scriptPath = $wgScriptPath ?
$wgScriptPath : "/";
1073 $entryPoints = array(
1074 'version-entrypoints-articlepath' => $wgArticlePath,
1075 'version-entrypoints-scriptpath' => $scriptPath,
1076 'version-entrypoints-index-php' => wfScript( 'index' ),
1077 'version-entrypoints-api-php' => wfScript( 'api' ),
1078 'version-entrypoints-load-php' => wfScript( 'load' ),
1081 $language = $this->getLanguage();
1082 $thAttribures = array(
1083 'dir' => $language->getDir(),
1084 'lang' => $language->getCode()
1086 $out = Html
::element( 'h2', array( 'id' => 'mw-version-entrypoints' ), $this->msg( 'version-entrypoints' )->text() ) .
1087 Html
::openElement( 'table',
1089 'class' => 'wikitable plainlinks',
1090 'id' => 'mw-version-entrypoints-table',
1095 Html
::openElement( 'tr' ) .
1096 Html
::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-entrypoint' )->text() ) .
1097 Html
::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-url' )->text() ) .
1098 Html
::closeElement( 'tr' );
1100 foreach ( $entryPoints as $message => $value ) {
1101 $url = wfExpandUrl( $value, PROTO_RELATIVE
);
1102 $out .= Html
::openElement( 'tr' ) .
1103 // ->text() looks like it should be ->parse(), but this function
1104 // returns wikitext, not HTML, boo
1105 Html
::rawElement( 'td', array(), $this->msg( $message )->text() ) .
1106 Html
::rawElement( 'td', array(), Html
::rawElement( 'code', array(), "[$url $value]" ) ) .
1107 Html
::closeElement( 'tr' );
1110 $out .= Html
::closeElement( 'table' );
1114 protected function getGroupName() {