* main()
*/
public function execute( $par ) {
- global $wgSpecialVersionShowHooks, $IP;
+ global $IP, $wgExtensionCredits;
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$out->allowClickjacking();
- if ( $par !== 'Credits' ) {
- $text =
- $this->getMediaWikiCredits() .
- $this->softwareInformation() .
- $this->getEntryPointInfo() .
- $this->getExtensionCredits();
- if ( $wgSpecialVersionShowHooks ) {
- $text .= $this->getWgHooks();
+ // Explode the sub page information into useful bits
+ $parts = explode( '/', (string)$par );
+ $extNode = null;
+ if ( isset( $parts[1] ) ) {
+ $extName = str_replace( '_', ' ', $parts[1] );
+ // Find it!
+ foreach ( $wgExtensionCredits as $group => $extensions ) {
+ foreach ( $extensions as $ext ) {
+ if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
+ $extNode = &$ext;
+ break 2;
+ }
+ }
}
-
- $out->addWikiText( $text );
- $out->addHTML( $this->IPInfo() );
-
- if ( $this->getRequest()->getVal( 'easteregg' ) ) {
- // TODO: put something interesting here
+ if ( !$extNode ) {
+ $out->setStatusCode( 404 );
}
} else {
- // Credits sub page
-
- // Header
- $out->addHTML( wfMessage( 'version-credits-summary' )->parseAsBlock() );
+ $extName = 'MediaWiki';
+ }
- $wikiText = file_get_contents( $IP . '/CREDITS' );
+ // Now figure out what to do
+ switch ( strtolower( $parts[0] ) ) {
+ case 'credits':
+ $wikiText = '{{int:version-credits-not-found}}';
+ if ( $extName === 'MediaWiki' ) {
+ $wikiText = file_get_contents( $IP . '/CREDITS' );
+ } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
+ $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
+ if ( $file ) {
+ $wikiText = file_get_contents( $file );
+ }
+ }
- // Take everything from the first section onwards, to remove the (not localized) header
- $wikiText = substr( $wikiText, strpos( $wikiText, '==' ) );
+ $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
+ $out->addWikiText( $wikiText );
+ break;
+
+ case 'license':
+ $wikiText = '{{int:version-license-not-found}}';
+ if ( $extName === 'MediaWiki' ) {
+ $wikiText = file_get_contents( $IP . '/COPYING' );
+ } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
+ $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
+ if ( $file ) {
+ $wikiText = file_get_contents( $file );
+ if ( !isset( $extNode['license-name'] ) ) {
+ // If the developer did not explicitly set license-name they probably
+ // are unaware that we're now sucking this file in and thus it's probably
+ // not wikitext friendly.
+ $wikiText = "<pre>$wikiText</pre>";
+ }
+ }
+ }
- $out->addWikiText( $wikiText );
+ $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
+ $out->addWikiText( $wikiText );
+ break;
+
+ default:
+ $out->addModules( 'mediawiki.special.version' );
+ $out->addWikiText(
+ $this->getMediaWikiCredits() .
+ $this->softwareInformation() .
+ $this->getEntryPointInfo()
+ );
+ $out->addHtml(
+ $this->getExtensionCredits() .
+ $this->getParserTags() .
+ $this->getParserFunctionHooks()
+ );
+ $out->addWikiText( $this->getWgHooks() );
+ $out->addHTML( $this->IPInfo() );
+
+ break;
}
}
$gitHeadCommitDate = $gitInfo->getHeadCommitDate();
if ( $gitHeadCommitDate ) {
- $shortSHA1 .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true );
+ $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
}
return self::getwgVersionLinked() . " $shortSHA1";
* @return String: Wikitext
*/
function getExtensionCredits() {
- global $wgExtensionCredits, $wgExtensionFunctions, $wgParser;
+ global $wgExtensionCredits;
- if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) ) {
+ if ( !count( $wgExtensionCredits ) ) {
return '';
}
// We want the 'other' type to be last in the list.
$out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
+ $out .= Xml::closeElement( 'table' );
+
+ return $out;
+ }
+
+ /**
+ * Obtains a list of installed parser tags and the associated H2 header
+ *
+ * @return string HTML output
+ */
+ protected function getParserTags() {
+ global $wgParser;
+
$tags = $wgParser->getTags();
- $cnt = count( $tags );
- if ( $cnt ) {
- for ( $i = 0; $i < $cnt; ++$i ) {
- $tags[$i] = "<{$tags[$i]}>";
- }
- $out .= $this->openExtType( $this->msg( 'version-parser-extensiontags' )->text(), 'parser-tags' );
- $out .= '<tr><td colspan="4">' . $this->listToText( $tags ) . "</td></tr>\n";
+ if ( count( $tags ) ) {
+ $out = Html::rawElement(
+ 'h2',
+ array( 'class' => 'mw-headline' ),
+ Linker::makeExternalLink(
+ '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
+ $this->msg( 'version-parser-extensiontags' )->parse(),
+ false /* msg()->parse() already escapes */
+ )
+ );
+
+ array_walk( $tags, function( &$value ) {
+ $value = '<' . htmlentities( $value ) . '>';
+ } );
+ $out .= $this->listToText( $tags );
+ } else {
+ $out = '';
}
+ return $out;
+ }
+
+ /**
+ * Obtains a list of installed parser function hooks and the associated H2 header
+ *
+ * @return string HTML output
+ */
+ protected function getParserFunctionHooks() {
+ global $wgParser;
+
$fhooks = $wgParser->getFunctionHooks();
if ( count( $fhooks ) ) {
- $out .= $this->openExtType( $this->msg( 'version-parser-function-hooks' )->text(), 'parser-function-hooks' );
- $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
- }
+ $out = Html::rawElement( 'h2', array( 'class' => 'mw-headline' ) , Linker::makeExternalLink(
+ '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
+ $this->msg( 'version-parser-function-hooks' )->parse(),
+ false /* msg()->parse() already escapes */
+ ) );
- $out .= Xml::closeElement( 'table' );
+ $out .= $this->listToText( $fhooks );
+ } else {
+ $out = '';
+ }
return $out;
}
}
/**
- * Creates and formats the credits for a single extension and returns this.
+ * Creates and formats a version line for a single extension.
+ *
+ * Information for five columns will be created. Parameters required in the
+ * $extension array for part rendering are indicated in ()
+ * - The name of (name), and URL link to (url), the extension
+ * - Official version number (version) and if available version control system
+ * revision (path), link, and date
+ * - If available the short name of the license (license-name) and a linke
+ * to ((LICENSE)|(COPYING))(\.txt)? if it exists.
+ * - Description of extension (descriptionmsg or description)
+ * - List of authors (author) and link to a ((AUTHORS)|(CREDITS))(\.txt)? file if it exists
*
* @param $extension Array
*
- * @return string
+ * @return string raw HTML
*/
function getCreditsForExtension( array $extension ) {
- global $wgLang;
+ $out = $this->getOutput();
+
+ // We must obtain the information for all the bits and pieces!
+ // ... such as extension names and links
+ $extensionName = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
+ if ( isset( $extension['url'] ) ) {
+ $extensionNameLink = Linker::makeExternalLink(
+ $extension['url'],
+ $extensionName,
+ true,
+ '',
+ array( 'class' => 'mw-version-ext-name' )
+ );
+ } else {
+ $extensionNameLink = $extensionName;
+ }
- $name = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
+ // ... and the version information
+ // If the extension path is set we will check that directory for GIT and SVN
+ // metadata in an attempt to extract date and vcs commit metadata.
+ $canonicalVersion = '–';
+ $extensionPath = null;
+ $vcsVersion = null;
+ $vcsLink = null;
+ $vcsDate = null;
- $vcsText = false;
+ if ( isset( $extension['version'] ) ) {
+ $canonicalVersion = $out->parseInline( $extension['version'] );
+ }
if ( isset( $extension['path'] ) ) {
- $gitInfo = new GitInfo( dirname( $extension['path'] ) );
- $gitHeadSHA1 = $gitInfo->getHeadSHA1();
- if ( $gitHeadSHA1 !== false ) {
- $vcsText = '(' . substr( $gitHeadSHA1, 0, 7 ) . ')';
- $gitViewerUrl = $gitInfo->getHeadViewUrl();
- if ( $gitViewerUrl !== false ) {
- $vcsText = "[$gitViewerUrl $vcsText]";
- }
- $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
- if ( $gitHeadCommitDate ) {
- $vcsText .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true );
- }
+ $extensionPath = dirname( $extension['path'] );
+ $gitInfo = new GitInfo( $extensionPath );
+ $vcsVersion = $gitInfo->getHeadSHA1();
+ if ( $vcsVersion !== false ) {
+ $vcsVersion = substr( $vcsVersion, 0, 7 );
+ $vcsLink = $gitInfo->getHeadViewUrl();
+ $vcsDate = $gitInfo->getHeadCommitDate();
} else {
- $svnInfo = self::getSvnInfo( dirname( $extension['path'] ) );
- # Make subversion text/link.
+ $svnInfo = self::getSvnInfo( $extensionPath );
if ( $svnInfo !== false ) {
- $directoryRev = isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : null;
- $vcsText = $this->msg( 'version-svn-revision', $directoryRev, $svnInfo['checkout-rev'] )->text();
- $vcsText = isset( $svnInfo['viewvc-url'] ) ? '[' . $svnInfo['viewvc-url'] . " $vcsText]" : $vcsText;
+ $vcsVersion = $this->msg( 'version-svn-revision', $svnInfo['checkout-rev'] )->text();
+ $vcsLink = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : '';
}
}
}
- # Make main link (or just the name if there is no URL).
- if ( isset( $extension['url'] ) ) {
- $mainLink = "[{$extension['url']} $name]";
- } else {
- $mainLink = $name;
+ $versionString = Html::rawElement( 'span', array( 'class' => 'mw-version-ext-version' ), $canonicalVersion );
+ if ( $vcsVersion ) {
+ if ( $vcsLink ) {
+ $vcsVerString = Linker::makeExternalLink(
+ $vcsLink,
+ $this->msg( 'version-version', $vcsVersion ),
+ true,
+ '',
+ array( 'class' => 'mw-version-ext-vcs-version' )
+ );
+ } else {
+ $vcsVerString = Html::element( 'span',
+ array( 'class' => 'mw-version-ext-vcs-version'),
+ "({$vcsVersion})"
+ );
+ }
+ $versionString .= " {$vcsVerString}";
+
+ if ( $vcsDate ) {
+ $vcsTimeString = Html::element( 'span',
+ array( 'class' => 'mw-version-ext-vcs-timestamp'),
+ $this->getLanguage()->timeanddate( $vcsDate )
+ );
+ $versionString .= " {$vcsTimeString}";
+ }
+ $versionString = Html::rawElement( 'span',
+ array( 'class' => 'mw-version-ext-meta-version' ),
+ $versionString
+ );
}
- if ( isset( $extension['version'] ) ) {
- $versionText = '<span class="mw-version-ext-version">' .
- $this->msg( 'version-version', $extension['version'] )->text() .
- '</span>';
- } else {
- $versionText = '';
+ // ... and license information; if a license file exists we
+ // will link to it
+ $licenseLink = '';
+ if ( isset( $extension['license-name'] ) ) {
+ $licenseLink = Linker::link(
+ $this->getPageTitle( 'License/' . $extensionName ),
+ $out->parseInline( $extension['license-name'] ),
+ array( 'class' => 'mw-version-ext-license' )
+ );
+ } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
+ $licenseLink = Linker::link(
+ $this->getPageTitle( 'License/' . $extensionName ),
+ $this->msg( 'version-ext-license' ),
+ array( 'class' => 'mw-version-ext-license' )
+ );
}
- # Make description text.
- $description = isset( $extension['description'] ) ? $extension['description'] : '';
-
+ // ... and generate the description; which can be a parameterized l10n message
+ // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
+ // up string
if ( isset( $extension['descriptionmsg'] ) ) {
- # Look for a localized description.
+ // Localized description of extension
$descriptionMsg = $extension['descriptionmsg'];
if ( is_array( $descriptionMsg ) ) {
} else {
$description = $this->msg( $descriptionMsg )->text();
}
- }
-
- if ( $vcsText !== false ) {
- $extNameVer = "<tr>
- <td><em>$mainLink $versionText</em></td>
- <td><em>$vcsText</em></td>";
+ } elseif ( isset( $extension['description'] ) ) {
+ // Non localized version
+ $description = $extension['description'];
} else {
- $extNameVer = "<tr>
- <td colspan=\"2\"><em>$mainLink $versionText</em></td>";
+ $description = '';
}
+ $description = $out->parseInline( $description );
- $author = isset( $extension['author'] ) ? $extension['author'] : array();
- $extDescAuthor = "<td>$description</td>
- <td>" . $this->listAuthors( $author, false ) . "</td>
- </tr>\n";
+ // ... now get the authors for this extension
+ $authors = isset( $extension['author'] ) ? $extension['author'] : array();
+ $authors = $this->listAuthors( $authors, $extensionName, $extensionPath );
- return $extNameVer . $extDescAuthor;
+ // Finally! Create the table
+ $html = Html::openElement( 'tr', array(
+ 'class' => 'mw-version-ext',
+ 'id' => "mw-version-ext-{$extensionName}"
+ )
+ );
+
+ $html .= Html::rawElement( 'td', array(), $extensionNameLink );
+ $html .= Html::rawElement( 'td', array(), $versionString );
+ $html .= Html::rawElement( 'td', array(), $licenseLink );
+ $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-description' ), $description );
+ $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-authors' ), $authors );
+
+ $html .= Html::closeElement( 'td' );
+
+ return $html;
}
/**
* @return String: wikitext
*/
private function getWgHooks() {
- global $wgHooks;
+ global $wgSpecialVersionShowHooks, $wgHooks;
- if ( count( $wgHooks ) ) {
+ if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
$myWgHooks = $wgHooks;
ksort( $myWgHooks );
- $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), $this->msg( 'version-hooks' )->text() ) .
- Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
- "<tr>
- <th>" . $this->msg( 'version-hook-name' )->text() . "</th>
- <th>" . $this->msg( 'version-hook-subscribedby' )->text() . "</th>
- </tr>\n";
+ $ret = array();
+ $ret[] = '== {{int:version-hooks}} ==';
+ $ret[] = Html::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) );
+ $ret[] = Html::openElement( 'tr' );
+ $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-name' )->text() );
+ $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-subscribedby' )->text() );
+ $ret[] = Html::closeElement( 'tr' );
foreach ( $myWgHooks as $hook => $hooks ) {
- $ret .= "<tr>
- <td>$hook</td>
- <td>" . $this->listToText( $hooks ) . "</td>
- </tr>\n";
+ $ret[] = Html::openElement( 'tr' );
+ $ret[] = Html::element( 'td', array(), $hook );
+ $ret[] = Html::element( 'td', array(), $this->listToText( $hooks ) );
+ $ret[] = Html::closeElement( 'tr' );
}
- $ret .= Xml::closeElement( 'table' );
- return $ret;
+ $ret[] = Html::closeElement( 'table' );
+
+ return implode( "\n", $ret );
} else {
return '';
}
}
private function openExtType( $text, $name = null ) {
- $opt = array( 'colspan' => 4 );
$out = '';
+ $opt = array( 'colspan' => 5 );
if ( $this->firstExtOpened ) {
// Insert a spacing line
- $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n";
+ $out .= Html::rawElement( 'tr', array( 'class' => 'sv-space' ),
+ Html::element( 'td', $opt )
+ );
}
$this->firstExtOpened = true;
$opt['id'] = "sv-$name";
}
- $out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n";
+ $out .= Html::rawElement( 'tr', array(),
+ Html::element( 'th', $opt, $text )
+ );
+
+ $out .= Html::openElement( 'tr' );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-name' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-version' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-license' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-description' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-credits' )->text() );
+ $out .= Html::closeElement( 'tr' );
return $out;
}
/**
* Return a formatted unsorted list of authors
*
+ * 'And Others'
+ * If an item in the $authors array is '...' it is assumed to indicate an
+ * 'and others' string which will then be linked to an ((AUTHORS)|(CREDITS))(\.txt)?
+ * file if it exists in $dir.
+ *
+ * Similarly an entry ending with ' ...]' is assumed to be a link to an
+ * 'and others' page.
+ *
+ * If no '...' string variant is found, but an authors file is found an
+ * 'and others' will be added to the end of the credits.
+ *
* @param $authors mixed: string or array of strings
+ * @param $extName string: name of the extension for link creation
+ * @param $extDir string: path to the extension root directory
+ *
* @return String: HTML fragment
*/
- function listAuthors( $authors ) {
+ function listAuthors( $authors, $extName, $extDir ) {
+ $hasOthers = false;
+
$list = array();
foreach ( (array)$authors as $item ) {
if ( $item == '...' ) {
- $list[] = $this->msg( 'version-poweredby-others' )->text();
+ $hasOthers = true;
+
+ if ( $this->getExtAuthorsFileName( $extDir ) ) {
+ $text = Linker::link(
+ $this->getPageTitle( "Credits/$extName" ),
+ $this->msg( 'version-poweredby-others' )->text()
+ );
+ } else {
+ $text = $this->msg( 'version-poweredby-others' )->text();
+ }
+ $list[] = $text;
+
} elseif ( substr( $item, -5 ) == ' ...]' ) {
- $list[] = substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]";
+ $hasOthers = true;
+ $list[] = $this->getOutput()->parseInline(
+ substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
+ );
+
} else {
- $list[] = $item;
+ $list[] = $this->getOutput()->parseInline( $item );
}
}
+
+ if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
+ $list[] = $text = Linker::link(
+ $this->getPageTitle( "Credits/$extName" ),
+ $this->msg( 'version-poweredby-others' )->text()
+ );
+ }
+
return $this->listToText( $list, false );
}
+ /**
+ * Obtains the full path of an extensions authors or credits file if
+ * one exists.
+ *
+ * @param string $extDir: Path to the extensions root directory
+ *
+ * @since 1.23
+ *
+ * @return bool|string False if no such file exists, otherwise returns
+ * a path to it.
+ */
+ public static function getExtAuthorsFileName( $extDir ) {
+ if ( !$extDir ) {
+ return false;
+ }
+
+ foreach ( scandir( $extDir ) as $file ) {
+ $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
+ if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt)?$/', $file ) &&
+ is_readable( $fullPath ) &&
+ is_file( $fullPath )
+ ) {
+ return $fullPath;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Obtains the full path of an extensions copying or license file if
+ * one exists.
+ *
+ * @param string $extDir: Path to the extensions root directory
+ *
+ * @since 1.23
+ *
+ * @return bool|string False if no such file exists, otherwise returns
+ * a path to it.
+ */
+ public static function getExtLicenseFileName( $extDir ) {
+ if ( !$extDir ) {
+ return false;
+ }
+
+ foreach ( scandir( $extDir ) as $file ) {
+ $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
+ if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
+ is_readable( $fullPath ) &&
+ is_file( $fullPath )
+ ) {
+ return $fullPath;
+ }
+ }
+
+ return false;
+ }
+
/**
* Convert an array of items into a list for display.
*