Paladox has uploaded a new change for review. https://gerrit.wikimedia.org/r/184689
Change subject: Add SkinCredits ...................................................................... Add SkinCredits * This adds SkinCredits for skins. It replaces ExtensionCredits for skins. * There is backword compatibility so that authors have time to change there extension to use the new SkinCredits. With SkinCredits it deprecates skin in ExtensionTypes and Deprecates the use of ExtensionCredits for skins. SkinCredits also gets more information in skin simler to what the extension shows just that it is for skins. Note this has been tested. Change-Id: Ica0a9eccad7644460a3c63b184c09126303f25d2 --- M includes/DefaultSettings.php M includes/api/ApiQuerySiteinfo.php M includes/specials/SpecialVersion.php 3 files changed, 447 insertions(+), 12 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/89/184689/1 diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index ee462d8..de92165 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -6355,6 +6355,60 @@ $wgExtensionCredits = array(); /** + * An array of information about installed skins keyed by their type. + * + * All but 'name', 'path' and 'author' can be omitted. + * + * @code + * $wgSkinCredits[$type][] = array( + * 'path' => __FILE__, + * 'name' => 'Example skin', + * 'namemsg' => 'exampleskin-name', + * 'author' => array( + * 'Foo Barstein', + * ), + * 'version' => '1.9.0', + * 'url' => 'http://example.org/example-skin/', + * 'descriptionmsg' => 'exampleskin-desc', + * 'license-name' => 'GPL-2.0', + * ); + * @endcode + * + * The extensions are listed on Special:Version. This page also looks for a file + * named COPYING or LICENSE (optional .txt extension) and provides a link to + * view said file. When the 'license-name' key is specified, this file is + * interpreted as wikitext. + * + * - $type: One of 'skin', + * or any additional types as specified through the + * SkinTypes hook as used in SpecialVersion::getSkinTypes(). + * + * - name: Name of skin as an inline string instead of localizable message. + * Do not omit this even if 'namemsg' is provided, as it is used to override + * the path Special:Version uses to find skin's license info, and is + * required for backwards-compatibility with MediaWiki 1.23 and older. + * + * - namemsg (since MW 1.24): A message key for a message containing the + * skin's name, if the name is localizable. (For example, skin names + * usually are.) + * + * - author: A string or an array of strings. Authors can be linked using + * the regular wikitext link syntax. To have an internationalized version of + * "and others" show, add an element "...". This element can also be linked, + * for instance "[http://example ...]". + * + * - descriptionmsg: A message key or an an array with message key and parameters: + * `'descriptionmsg' => 'exampleskin-desc',` + * + * - description: Description of skin as an inline string instead of + * localizable message (omit in favour of 'descriptionmsg'). + * + * - license-name: Short name of the license (used as label for the link), such + * as "GPL-2.0" or "MIT" (https://spdx.org/licenses/ for a list of identifiers). + */ +$wgSkinCredits = array(); + +/** * Authentication plugin. * @var $wgAuth AuthPlugin */ diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index f373021..138dd68 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -690,11 +690,107 @@ return $this->getResult()->addValue( 'query', $property, $data ); } + + public function appendSkins( $property ) { $data = array(); $allowed = Skin::getAllowedSkins(); $default = Skin::normalizeKey( 'default' ); + foreach ( $this->getConfig()->get( 'SkinCredits' ) as $type => $skins ) { + foreach ( $skins as $skin ) { + $ret = array(); + $ret['type'] = $type; + if ( isset( $skin['name'] ) ) { + $ret['name'] = $skin['name']; + } + if ( isset( $skin['namemsg'] ) ) { + $ret['namemsg'] = $skin['namemsg']; + } + if ( isset( $skin['description'] ) ) { + $ret['description'] = $skin['description']; + } + if ( isset( $skin['descriptionmsg'] ) ) { + // Can be a string or array( key, param1, param2, ... ) + if ( is_array( $skin['descriptionmsg'] ) ) { + $ret['descriptionmsg'] = $skin['descriptionmsg'][0]; + $ret['descriptionmsgparams'] = array_slice( $skin['descriptionmsg'], 1 ); + $this->getResult()->setIndexedTagName( $ret['descriptionmsgparams'], 'param' ); + } else { + $ret['descriptionmsg'] = $skin['descriptionmsg']; + } + } + if ( isset( $skin['author'] ) ) { + $ret['author'] = is_array( $skin['author'] ) ? + implode( ', ', $skin['author'] ) : $skin['author']; + } + if ( isset( $skin['url'] ) ) { + $ret['url'] = $skin['url']; + } + if ( isset( $skin['version'] ) ) { + $ret['version'] = $skin['version']; + } elseif ( isset( $skin['svn-revision'] ) && + preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/', + $skin['svn-revision'], $m ) + ) { + $ret['version'] = 'r' . $m[1]; + } + if ( isset( $skin['path'] ) ) { + $skinPath = dirname( $skin['path'] ); + $gitInfo = new GitInfo( $skinPath ); + $vcsVersion = $gitInfo->getHeadSHA1(); + if ( $vcsVersion !== false ) { + $ret['vcs-system'] = 'git'; + $ret['vcs-version'] = $vcsVersion; + $ret['vcs-url'] = $gitInfo->getHeadViewUrl(); + $vcsDate = $gitInfo->getHeadCommitDate(); + if ( $vcsDate !== false ) { + $ret['vcs-date'] = wfTimestamp( TS_ISO_8601, $vcsDate ); + } + } else { + $svnInfo = SpecialVersion::getSvnInfo( $skinPath ); + if ( $svnInfo !== false ) { + $ret['vcs-system'] = 'svn'; + $ret['vcs-version'] = $svnInfo['checkout-rev']; + $ret['vcs-url'] = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : ''; + } + } + + if ( SpecialVersion::getExtLicenseFileName( $skinPath ) ) { + $ret['license-name'] = isset( $skin['license-name'] ) ? $skin['license-name'] : ''; + $ret['license'] = SpecialPage::getTitleFor( + 'Version', + "License/{$ext['name']}" + )->getLinkURL(); + } + + if ( SpecialVersion::getExtAuthorsFileName( $skinPath ) ) { + $ret['credits'] = SpecialPage::getTitleFor( + 'Version', + "Credits/{$ext['name']}" + )->getLinkURL(); + } + } + if ( !isset( $allowed[$name] ) ) { + $skin['unusable'] = ''; + } + if ( $type === $default ) { + $ret['default'] = ''; + } + $data[] = $ret; + } + } + + /* + * This is here for backward compatibility. + * This is deprecated since MediaWiki 1.25 + * @deprecated since 1.25 Use SkinCredits + */ + wfDeprecated( __METHOD__, '1.25' ); + if ( is_callable( $GLOBALS['wgExtensionCredits']['skin'] ) ); { + echo $GLOBALS['wgExtensionCredits']['skin']; + foreach ( Skin::getSkinNames() as $name => $displayName ) { + $msg = $this->msg( "skinname-{$name}" ); $code = $this->getParameter( 'inlanguagecode' ); if ( $code && Language::isValidCode( $code ) ) { @@ -706,8 +802,8 @@ $displayName = $msg->text(); } $skin = array( 'code' => $name ); - ApiResult::setContent( $skin, $displayName ); - if ( !isset( $allowed[$name] ) ) { + ApiResult::setContent( $skin, $displayName ); + if ( !isset( $allowed[$name] ) ) { $skin['unusable'] = ''; } if ( $name === $default ) { @@ -715,6 +811,8 @@ } $data[] = $skin; } + } + $this->getResult()->setIndexedTagName( $data, 'skin' ); return $this->getResult()->addValue( 'query', $property, $data ); diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index 2aa629e..67e62b0 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -38,6 +38,8 @@ protected static $extensionTypes = false; + protected static $skinTypes = false; + protected static $viewvcUrls = array( 'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', 'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', @@ -53,7 +55,7 @@ * @param string|null $par */ public function execute( $par ) { - global $IP, $wgExtensionCredits; + global $IP, $wgExtensionCredits, $wgSkinCredits; $this->setHeaders(); $this->outputHeader(); @@ -74,6 +76,15 @@ } } } + foreach ( $wgSkinCredits as $group => $skins ) { + foreach ( $skins as $skin ) { + if ( isset( $skin['name'] ) && ( $skin['name'] === $extName ) ) { + $extNode = &$skin; + break 2; + } + } + } + if ( !$extNode ) { $out->setStatusCode( 404 ); } @@ -288,6 +299,7 @@ */ public static function getVersionLinked() { global $wgVersion; + wfProfileIn( __METHOD__ ); $gitVersion = self::getVersionLinkedGit(); if ( $gitVersion ) { @@ -392,6 +404,7 @@ 'variable' => wfMessage( 'version-variables' )->text(), 'media' => wfMessage( 'version-mediahandlers' )->text(), 'antispam' => wfMessage( 'version-antispam' )->text(), + /* @deprecated since 1.25 please use SkinCredits and then skin */ 'skin' => wfMessage( 'version-skins' )->text(), 'api' => wfMessage( 'version-api' )->text(), 'other' => wfMessage( 'version-other' )->text(), @@ -401,6 +414,28 @@ } return self::$extensionTypes; + } + + /** + * Returns an array with the base skin types. + * Type is stored as array key, the message as array value. + * + * TODO: ideally this would return all extension types. + * + * @since 1.25 + * + * @return array + */ + public static function getSkinTypes() { + if ( self::$skinTypes === false ) { + self::$skinTypes = array( + 'skin' => wfMessage( 'version-skins' )->text(), + ); + + Hooks::run( 'SkinTypes', array( &self::$skinTypes ) ); + } + + return self::$skinTypes; } /** @@ -419,6 +454,21 @@ } /** + * Returns the internationalized name for an extension type. + * + * @since 1.25 + * + * @param string $type + * + * @return string + */ + public static function getSkinTypeName( $type ) { + $types = self::getSkinTypes(); + + return isset( $types[$type] ) ? $types[$type] : $types['other']; + } + + /** * Generate wikitext showing the name, URL, author and description of each extension. * * @return string Wikitext @@ -426,10 +476,7 @@ public function getExtensionCredits() { global $wgExtensionCredits; - if ( - count( $wgExtensionCredits ) === 0 || - // Skins are displayed separately, see getSkinCredits() - ( count( $wgExtensionCredits ) === 1 && isset( $wgExtensionCredits['skin'] ) ) + if ( !count( $wgExtensionCredits ) ) { return ''; } @@ -473,15 +520,20 @@ } /** - * Generate wikitext showing the name, URL, author and description of each skin. + * Generate wikitext showing the name, URL, author and description of each extension. * * @return string Wikitext */ public function getSkinCredits() { - global $wgExtensionCredits; - if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) { + global $wgSkinCredits; + + if ( !count( $wgSkinCredits ) + ) { return ''; } + + + $skinTypes = self::getSkinTypes(); $out = Xml::element( 'h2', @@ -491,7 +543,10 @@ Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ) ); $this->firstExtOpened = false; + + // We want the 'other' type to be last in the list. $out .= $this->getExtensionCategory( 'skin', null ); + $out .= $this->getSkinCategory( 'skin', null); $out .= Xml::closeElement( 'table' ); @@ -636,6 +691,34 @@ } /** + * Creates and returns the HTML for a single extension category. + * + * @since 1.17 + * + * @param string $type + * @param string $message + * + * @return string + */ + protected function getSkinCategory( $type, $message ) { + global $wgSkinCredits; + + $out = ''; + + if ( array_key_exists( $type, $wgSkinCredits ) && count( $wgSkinCredits[$type] ) > 0 ) { + $out .= $this->openExtType( $message, 'credits-' . $type ); + + usort( $wgSkinCredits[$type], array( $this, 'compare' ) ); + + foreach ( $wgSkinCredits[$type] as $skin ) { + $out .= $this->getCreditsForSkin( $skin ); + } + } + + return $out; + } + + /** * Callback to sort extensions by type. * @param array $a * @param array $b @@ -728,7 +811,7 @@ list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey ); if ( !$vcsVersion ) { - wfDebug( "Getting VCS info for extension {$extension['name']}" ); + wfDebug( "Getting VCS info for extension $extensionName" ); $gitInfo = new GitInfo( $extensionPath ); $vcsVersion = $gitInfo->getHeadSHA1(); if ( $vcsVersion !== false ) { @@ -744,7 +827,7 @@ } $cache->set( $memcKey, array( $vcsVersion, $vcsLink, $vcsDate ), 60 * 60 * 24 ); } else { - wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" ); + wfDebug( "Pulled VCS info for extension $extensionName from cache" ); } } @@ -852,6 +935,206 @@ } /** + * 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 array $extension + * + * @return string Raw HTML + */ + public function getCreditsForSkin( array $skin ) { + $out = $this->getOutput(); + + // We must obtain the information for all the bits and pieces! + // ... such as extension names and links + if ( isset( $skin['namemsg'] ) ) { + // Localized name of extension + $skinName = $this->msg( $skin['namemsg'] )->text(); + } elseif ( isset( $skin['name'] ) ) { + // Non localized version + $skinName = $skin['name']; + } else { + $skinName = $this->msg( 'version-no-skin-name' )->text(); + } + + if ( isset( $skin['url'] ) ) { + $skinNameLink = Linker::makeExternalLink( + $skin['url'], + $skinName, + true, + '', + array( 'class' => 'mw-version-mediawiki-name' ) + ); + } else { + $skinNameLink = $skinName; + } + + // ... 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 = '–'; + $skinPath = null; + $vcsVersion = null; + $vcsLink = null; + $vcsDate = null; + + if ( isset( $skin['version'] ) ) { + $canonicalVersion = $out->parseInline( $skin['version'] ); + } + + if ( isset( $skin['path'] ) ) { + global $IP; + $skinPath = dirname( $skin['path'] ); + if ( $this->coreId == '' ) { + wfDebug( 'Looking up core head id' ); + $coreHeadSHA1 = self::getGitHeadSha1( $IP ); + if ( $coreHeadSHA1 ) { + $this->coreId = $coreHeadSHA1; + } else { + $svnInfo = self::getSvnInfo( $IP ); + if ( $svnInfo !== false ) { + $this->coreId = $svnInfo['checkout-rev']; + } + } + } + $cache = wfGetCache( CACHE_ANYTHING ); + $memcKey = wfMemcKey( 'specialversion-skin-version-text', $skin['path'], $this->coreId ); + list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey ); + + if ( !$vcsVersion ) { + wfDebug( "Getting VCS info for skin $skinName" ); + $gitInfo = new GitInfo( $skinPath ); + $vcsVersion = $gitInfo->getHeadSHA1(); + if ( $vcsVersion !== false ) { + $vcsVersion = substr( $vcsVersion, 0, 7 ); + $vcsLink = $gitInfo->getHeadViewUrl(); + $vcsDate = $gitInfo->getHeadCommitDate(); + } else { + $svnInfo = self::getSvnInfo( $skinPath ); + if ( $svnInfo !== false ) { + $vcsVersion = $this->msg( 'version-svn-revision', $svnInfo['checkout-rev'] )->text(); + $vcsLink = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : ''; + } + } + $cache->set( $memcKey, array( $vcsVersion, $vcsLink, $vcsDate ), 60 * 60 * 24 ); + } else { + wfDebug( "Pulled VCS info for skin $skinName from cache" ); + } + } + + $versionString = Html::rawElement( + 'span', + array( 'class' => 'mw-version-skin-version' ), + $canonicalVersion + ); + + if ( $vcsVersion ) { + if ( $vcsLink ) { + $vcsVerString = Linker::makeExternalLink( + $vcsLink, + $this->msg( 'version-version', $vcsVersion ), + true, + '', + array( 'class' => 'mw-version-skin-vcs-version' ) + ); + } else { + $vcsVerString = Html::element( 'span', + array( 'class' => 'mw-version-skin-vcs-version' ), + "({$vcsVersion})" + ); + } + $versionString .= " {$vcsVerString}"; + + if ( $vcsDate ) { + $vcsTimeString = Html::element( 'span', + array( 'class' => 'mw-version-skin-vcs-timestamp' ), + $this->getLanguage()->timeanddate( $vcsDate, true ) + ); + $versionString .= " {$vcsTimeString}"; + } + $versionString = Html::rawElement( 'span', + array( 'class' => 'mw-version-skin-meta-version' ), + $versionString + ); + } + + // ... and license information; if a license file exists we + // will link to it + $licenseLink = ''; + if ( isset( $skin['name'] ) ) { + $licenseName = null; + if ( isset( $skin['license-name'] ) ) { + $licenseName = $out->parseInline( $skin['license-name'] ); + } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) { + $licenseName = $this->msg( 'version-ext-license' ); + } + if ( $licenseName !== null ) { + $licenseLink = Linker::link( + $this->getPageTitle( 'License/' . $skin['name'] ), + $licenseName, + array( + 'class' => 'mw-version-skin-license', + 'dir' => 'auto', + ) + ); + } + } + + // ... 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( $skin['descriptionmsg'] ) ) { + // Localized description of extension + $descriptionMsg = $skin['descriptionmsg']; + + if ( is_array( $descriptionMsg ) ) { + $descriptionMsgKey = $descriptionMsg[0]; // Get the message key + array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only + array_map( "htmlspecialchars", $descriptionMsg ); // For sanity + $description = $this->msg( $descriptionMsgKey, $descriptionMsg )->text(); + } else { + $description = $this->msg( $descriptionMsg )->text(); + } + } elseif ( isset( $skin['description'] ) ) { + // Non localized version + $description = $skin['description']; + } else { + $description = ''; + } + $description = $out->parseInline( $description ); + + // ... now get the authors for this extension + $authors = isset( $skin['author'] ) ? $skin['author'] : array(); + $authors = $this->listAuthors( $authors, $skin['name'], $skinPath ); + + // Finally! Create the table + $html = Html::openElement( 'tr', array( + 'class' => 'mw-version-skin', + 'id' => "mw-version-skin-{$skin['name']}" + ) + ); + + $html .= Html::rawElement( 'td', array(), $skinNameLink ); + $html .= Html::rawElement( 'td', array(), $versionString ); + $html .= Html::rawElement( 'td', array(), $licenseLink ); + $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-skin-description' ), $description ); + $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-skin-authors' ), $authors ); + + $html .= Html::closeElement( 'tr' ); + + return $html; + } + + /** * Generate wikitext showing hooks in $wgHooks. * * @return string Wikitext -- To view, visit https://gerrit.wikimedia.org/r/184689 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ica0a9eccad7644460a3c63b184c09126303f25d2 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: Paladox <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
