Merge "Treat langtags in SVG switch case-insensitively"
This commit is contained in:
commit
716814a5d5
5 changed files with 186 additions and 70 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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' ];
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue