Merge "Treat langtags in SVG switch case-insensitively"

This commit is contained in:
jenkins-bot 2017-11-15 10:17:32 +00:00 committed by Gerrit Code Review
commit 716814a5d5
5 changed files with 186 additions and 70 deletions

View file

@ -581,6 +581,25 @@ abstract class File implements IDBAccessObject {
}
}
/**
* Get the language code from the available languages for this file that matches the language
* requested by the user
*
* @param string $userPreferredLanguage
* @return string|null
*/
public function getMatchedLanguage( $userPreferredLanguage ) {
$handler = $this->getHandler();
if ( $handler && method_exists( $handler, 'getMatchedLanguage' ) ) {
return $handler->getMatchedLanguage(
$userPreferredLanguage,
$handler->getAvailableLanguages( $this )
);
} else {
return null;
}
}
/**
* In files that support multiple language, what is the default language
* to use if none specified.

View file

@ -97,19 +97,50 @@ class SvgHandler extends ImageHandler {
if ( isset( $metadata['translations'] ) ) {
foreach ( $metadata['translations'] as $lang => $langType ) {
if ( $langType === SVGReader::LANG_FULL_MATCH ) {
$langList[] = $lang;
$langList[] = strtolower( $lang );
}
}
}
}
return $langList;
return array_unique( $langList );
}
/**
* What language to render file in if none selected.
* SVG's systemLanguage matching rules state:
* 'The `systemLanguage` attribute ... [e]valuates to "true" if one of the languages indicated
* by user preferences exactly equals one of the languages given in the value of this parameter,
* or if one of the languages indicated by user preferences exactly equals a prefix of one of
* the languages given in the value of this parameter such that the first tag character
* following the prefix is "-".'
*
* @param File $file
* @return string Language code.
* Return the first element of $svgLanguages that matches $userPreferredLanguage
*
* @see https://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
* @param string $userPreferredLanguage
* @param array $svgLanguages
* @return string|null
*/
public function getMatchedLanguage( $userPreferredLanguage, array $svgLanguages ) {
foreach ( $svgLanguages as $svgLang ) {
if ( strcasecmp( $svgLang, $userPreferredLanguage ) === 0 ) {
return $svgLang;
}
$trimmedSvgLang = $svgLang;
while ( strpos( $trimmedSvgLang, '-' ) !== false ) {
$trimmedSvgLang = substr( $trimmedSvgLang, 0, strrpos( $trimmedSvgLang, '-' ) );
if ( strcasecmp( $trimmedSvgLang, $userPreferredLanguage ) === 0 ) {
return $svgLang;
}
}
}
return null;
}
/**
* What language to render file in if none selected
*
* @param File $file Language code
* @return string
*/
public function getDefaultRenderLanguage( File $file ) {
return 'en';
@ -479,7 +510,7 @@ class SvgHandler extends ImageHandler {
return ( $value > 0 );
} elseif ( $name == 'lang' ) {
// Validate $code
if ( $value === '' || !Language::isValidBuiltInCode( $value ) ) {
if ( $value === '' || !Language::isValidCode( $value ) ) {
wfDebug( "Invalid user language code\n" );
return false;
@ -499,8 +530,7 @@ class SvgHandler extends ImageHandler {
public function makeParamString( $params ) {
$lang = '';
if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) {
$params['lang'] = strtolower( $params['lang'] );
$lang = "lang{$params['lang']}-";
$lang = 'lang' . strtolower( $params['lang'] ) . '-';
}
if ( !isset( $params['width'] ) ) {
return false;
@ -511,7 +541,7 @@ class SvgHandler extends ImageHandler {
public function parseParamString( $str ) {
$m = false;
if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/', $str, $m ) ) {
if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/i', $str, $m ) ) {
return [ 'width' => array_pop( $m ), 'lang' => $m[1] ];
} elseif ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
return [ 'width' => $m[1], 'lang' => 'en' ];

View file

@ -285,6 +285,19 @@ class ImagePage extends Article {
return parent::getContentObject();
}
private function getLanguageForRendering( WebRequest $request, File $file ) {
$handler = $this->displayImg->getHandler();
$requestLanguage = $request->getVal( 'lang' );
if ( !is_null( $requestLanguage ) ) {
if ( $handler && $handler->validateParam( 'lang', $requestLanguage ) ) {
return $requestLanguage;
}
}
return $handler->getDefaultRenderLanguage( $this->displayImg );
}
protected function openShowImage() {
global $wgEnableUploads, $wgSend404Code, $wgSVGMaxSize;
@ -309,14 +322,9 @@ class ImagePage extends Article {
$params = [ 'page' => $page ];
}
$renderLang = $request->getVal( 'lang' );
$renderLang = $this->getLanguageForRendering( $request, $this->displayImg );
if ( !is_null( $renderLang ) ) {
$handler = $this->displayImg->getHandler();
if ( $handler && $handler->validateParam( 'lang', $renderLang ) ) {
$params['lang'] = $renderLang;
} else {
$renderLang = null;
}
$params['lang'] = $renderLang;
}
$width_orig = $this->displayImg->getWidth( $page );
@ -544,12 +552,7 @@ EOT
$renderLangOptions = $this->displayImg->getAvailableLanguages();
if ( count( $renderLangOptions ) >= 1 ) {
$currentLanguage = $renderLang;
$defaultLang = $this->displayImg->getDefaultRenderLanguage();
if ( is_null( $currentLanguage ) ) {
$currentLanguage = $defaultLang;
}
$out->addHTML( $this->doRenderLangOpt( $renderLangOptions, $currentLanguage, $defaultLang ) );
$out->addHTML( $this->doRenderLangOpt( $renderLangOptions, $renderLang ) );
}
// Add cannot animate thumbnail warning
@ -1047,60 +1050,31 @@ EOT
* Output a drop-down box for language options for the file
*
* @param array $langChoices Array of string language codes
* @param string $curLang Language code file is being viewed in.
* @param string $defaultLang Language code that image is rendered in by default
* @param string $renderLang Language code for the language we want the file to rendered in.
* @return string HTML to insert underneath image.
*/
protected function doRenderLangOpt( array $langChoices, $curLang, $defaultLang ) {
protected function doRenderLangOpt( array $langChoices, $renderLang ) {
global $wgScript;
sort( $langChoices );
$curLang = LanguageCode::bcp47( $curLang );
$defaultLang = LanguageCode::bcp47( $defaultLang );
$opts = '';
$haveCurrentLang = false;
$haveDefaultLang = false;
// We make a list of all the language choices in the file.
// Additionally if the default language to render this file
// is not included as being in this file (for example, in svgs
// usually the fallback content is the english content) also
// include a choice for that. Last of all, if we're viewing
// the file in a language not on the list, add it as a choice.
$matchedRenderLang = $this->displayImg->getMatchedLanguage( $renderLang );
foreach ( $langChoices as $lang ) {
$code = LanguageCode::bcp47( $lang );
$name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
if ( $name !== '' ) {
$display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text();
} else {
$display = $code;
}
$opts .= "\n" . Xml::option( $display, $code, $curLang === $code );
if ( $curLang === $code ) {
$haveCurrentLang = true;
}
if ( $defaultLang === $code ) {
$haveDefaultLang = true;
}
}
if ( !$haveDefaultLang ) {
// Its hard to know if the content is really in the default language, or
// if its just unmarked content that could be in any language.
$opts = Xml::option(
$this->getContext()->msg( 'img-lang-default' )->text(),
$defaultLang,
$defaultLang === $curLang
) . $opts;
}
if ( !$haveCurrentLang && $defaultLang !== $curLang ) {
$name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() );
if ( $name !== '' ) {
$display = $this->getContext()->msg( 'img-lang-opt', $curLang, $name )->text();
} else {
$display = $curLang;
}
$opts = Xml::option( $display, $curLang, true ) . $opts;
$opts .= $this->createXmlOptionStringForLanguage(
$lang,
$matchedRenderLang === $lang
);
}
// Allow for the default case in an svg <switch> that is displayed if no
// systemLanguage attribute matches
$opts .= "\n" .
Xml::option(
$this->getContext()->msg( 'img-lang-default' )->text(),
'und',
is_null( $matchedRenderLang )
);
$select = Html::rawElement(
'select',
[ 'id' => 'mw-imglangselector', 'name' => 'lang' ],
@ -1119,6 +1093,27 @@ EOT
return $langSelectLine;
}
/**
* @param $lang string
* @param $selected bool
* @return string
*/
private function createXmlOptionStringForLanguage( $lang, $selected ) {
$code = LanguageCode::bcp47( $lang );
$name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
if ( $name !== '' ) {
$display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text();
} else {
$display = $code;
}
return "\n" .
Xml::option(
$display,
$lang,
$selected
);
}
/**
* Get the width and height to display image at.
*

View file

@ -15140,9 +15140,9 @@ SVG thumbnails with invalid language code
!! options
parsoid=wt2html,wt2wt,html2html
!! wikitext
[[File:Foobar.svg|thumb|caption|lang=invalid.language.code]]
[[File:Foobar.svg|thumb|caption|lang=invalid:language:code]]
!! html/php
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png" width="180" height="135" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"></a></div>lang=invalid.language.code</div></div></div>
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png" width="180" height="135" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"></a></div>lang=invalid:language:code</div></div></div>
!! html/parsoid
<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="./File:Foobar.svg"><img resource="./File:Foobar.svg" src="//example.com/images/f/ff/Foobar.svg" data-file-width="240" data-file-height="180" data-file-type="drawing" height="165" width="220"/></a><figcaption>lang=invalid.language.code</figcaption></figure>

View file

@ -5,6 +5,11 @@
*/
class SvgTest extends MediaWikiMediaTestCase {
/**
* @var SvgHandler
*/
private $handler;
protected function setUp() {
parent::setUp();
@ -38,4 +43,71 @@ class SvgTest extends MediaWikiMediaTestCase {
[ 'Wikimedia-logo.svg', [] ]
];
}
/**
* @param string $userPreferredLanguage
* @param array $svgLanguages
* @param string $expectedMatch
* @dataProvider providerGetMatchedLanguage
* @covers SvgHandler::getMatchedLanguage
*/
public function testGetMatchedLanguage( $userPreferredLanguage, $svgLanguages, $expectedMatch ) {
$match = $this->handler->getMatchedLanguage( $userPreferredLanguage, $svgLanguages );
$this->assertEquals( $expectedMatch, $match );
}
public function providerGetMatchedLanguage() {
return [
'no match' => [
'userPreferredLanguage' => 'en',
'svgLanguages' => [ 'de-DE', 'zh', 'ga', 'fr', 'sr-Latn-ME' ],
'expectedMatch' => null,
],
'no subtags' => [
'userPreferredLanguage' => 'en',
'svgLanguages' => [ 'de', 'zh', 'en', 'fr' ],
'expectedMatch' => 'en',
],
'user no subtags, svg 1 subtag' => [
'userPreferredLanguage' => 'en',
'svgLanguages' => [ 'de-DE', 'en-GB', 'en-US', 'fr' ],
'expectedMatch' => 'en-GB',
],
'user no subtags, svg >1 subtag' => [
'userPreferredLanguage' => 'sr',
'svgLanguages' => [ 'de-DE', 'sr-Cyrl-BA', 'sr-Latn-ME', 'en-US', 'fr' ],
'expectedMatch' => 'sr-Cyrl-BA',
],
'user 1 subtag, svg no subtags' => [
'userPreferredLanguage' => 'en-US',
'svgLanguages' => [ 'de', 'en', 'en', 'fr' ],
'expectedMatch' => null,
],
'user 1 subtag, svg 1 subtag' => [
'userPreferredLanguage' => 'en-US',
'svgLanguages' => [ 'de-DE', 'en-GB', 'en-US', 'fr' ],
'expectedMatch' => 'en-US',
],
'user 1 subtag, svg >1 subtag' => [
'userPreferredLanguage' => 'sr-Latn',
'svgLanguages' => [ 'de-DE', 'sr-Cyrl-BA', 'sr-Latn-ME', 'fr' ],
'expectedMatch' => 'sr-Latn-ME',
],
'user >1 subtag, svg >1 subtag' => [
'userPreferredLanguage' => 'sr-Latn-ME',
'svgLanguages' => [ 'de-DE', 'sr-Cyrl-BA', 'sr-Latn-ME', 'en-US', 'fr' ],
'expectedMatch' => 'sr-Latn-ME',
],
'user >1 subtag, svg <=1 subtag' => [
'userPreferredLanguage' => 'sr-Latn-ME',
'svgLanguages' => [ 'de-DE', 'sr-Cyrl', 'sr-Latn', 'en-US', 'fr' ],
'expectedMatch' => null,
],
'ensure case-insensitive' => [
'userPreferredLanguage' => 'sr-latn',
'svgLanguages' => [ 'de-DE', 'sr-Cyrl', 'sr-Latn-ME', 'en-US', 'fr' ],
'expectedMatch' => 'sr-Latn-ME',
],
];
}
}