Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes and unit tests for UI language detection #1457

Merged
merged 6 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"twig/extensions": "1.5.*",
"twbs/bootstrap": "5.1.*",
"twitter/typeahead.js": "v0.11.*",
"willdurand/negotiation": "3.0.*",
"willdurand/negotiation": "3.1.*",
"vakata/jstree": "3.3.*",
"punic/punic": "3.5.1",
"ml/json-ld": "1.*",
Expand Down
13 changes: 8 additions & 5 deletions controller/WebController.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,16 @@ public function __construct($model)
/**
* Guess the language of the user. Return a language string that is one
* of the supported languages defined in the $LANGUAGES setting, e.g. "fi".
* @param Request $request HTTP request
* @param string $vocid identifier for the vocabulary eg. 'yso'.
* @return string returns the language choice as a numeric string value
*/
public function guessLanguage($vocid = null)
public function guessLanguage($request, $vocid = null)
{
// 1. select language based on SKOSMOS_LANGUAGE cookie
if (filter_input(INPUT_COOKIE, 'SKOSMOS_LANGUAGE', FILTER_SANITIZE_FULL_SPECIAL_CHARS)) {
return filter_input(INPUT_COOKIE, 'SKOSMOS_LANGUAGE', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$languageCookie = $request->getCookie('SKOSMOS_LANGUAGE');
if ($languageCookie) {
return $languageCookie;
}

// 2. if vocabulary given, select based on the default language of the vocabulary
Expand All @@ -101,9 +103,10 @@ public function guessLanguage($vocid = null)
$this->negotiator = new \Negotiation\LanguageNegotiator();
$langcodes = array_keys($this->languages);
// using a random language from the configured UI languages when there is no accept language header set
$acceptLanguage = filter_input(INPUT_SERVER, 'HTTP_ACCEPT_LANGUAGE', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ? filter_input(INPUT_SERVER, 'HTTP_ACCEPT_LANGUAGE', FILTER_SANITIZE_FULL_SPECIAL_CHARS) : $langcodes[0];
$acceptLanguage = $request->getServerConstant('HTTP_ACCEPT_LANGUAGE') ? $request->getServerConstant('HTTP_ACCEPT_LANGUAGE') : $langcodes[0];

$bestLang = $this->negotiator->getBest($acceptLanguage, $langcodes);
if (isset($bestLang) && in_array($bestLang, $langcodes)) {
if (isset($bestLang) && in_array($bestLang->getValue(), $langcodes)) {
return $bestLang->getValue();
}

Expand Down
4 changes: 2 additions & 2 deletions dockerfiles/config/config-docker-compose.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
[ rdfs:label "da" ; rdf:value "da_DK.utf8" ]
[ rdfs:label "de" ; rdf:value "de_DE.utf8" ]
[ rdfs:label "en" ; rdf:value "en_GB.utf8" ]
[ rdfs:label "en_US" ; rdf:value "en_US.utf8" ]
[ rdfs:label "en-US" ; rdf:value "en_US.utf8" ]
[ rdfs:label "es" ; rdf:value "es_ES.utf8" ]
[ rdfs:label "fa" ; rdf:value "fa_IR.utf8" ]
[ rdfs:label "fi" ; rdf:value "fi_FI.utf8" ]
Expand All @@ -48,7 +48,7 @@
[ rdfs:label "nn" ; rdf:value "nn_NO.utf8" ]
[ rdfs:label "pl" ; rdf:value "pl_PL.utf8" ]
[ rdfs:label "pt" ; rdf:value "pt_PT.utf8" ]
[ rdfs:label "pt_BR" ; rdf:value "pt_BR.utf8" ]
[ rdfs:label "pt-BR" ; rdf:value "pt_BR.utf8" ]
[ rdfs:label "ru" ; rdf:value "ru_RU.utf8" ]
[ rdfs:label "sv" ; rdf:value "sv_SE.utf8" ]
[ rdfs:label "zh" ; rdf:value "zh_CN.utf8" ]
Expand Down
4 changes: 2 additions & 2 deletions dockerfiles/config/config-docker.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
[ rdfs:label "da" ; rdf:value "da_DK.utf8" ]
[ rdfs:label "de" ; rdf:value "de_DE.utf8" ]
[ rdfs:label "en" ; rdf:value "en_GB.utf8" ]
[ rdfs:label "en_US" ; rdf:value "en_US.utf8" ]
[ rdfs:label "en-US" ; rdf:value "en_US.utf8" ]
[ rdfs:label "es" ; rdf:value "es_ES.utf8" ]
[ rdfs:label "fa" ; rdf:value "fa_IR.utf8" ]
[ rdfs:label "fi" ; rdf:value "fi_FI.utf8" ]
Expand All @@ -48,7 +48,7 @@
[ rdfs:label "nn" ; rdf:value "nn_NO.utf8" ]
[ rdfs:label "pl" ; rdf:value "pl_PL.utf8" ]
[ rdfs:label "pt" ; rdf:value "pt_PT.utf8" ]
[ rdfs:label "pt_BR" ; rdf:value "pt_BR.utf8" ]
[ rdfs:label "pt-BR" ; rdf:value "pt_BR.utf8" ]
[ rdfs:label "ru" ; rdf:value "ru_RU.utf8" ]
[ rdfs:label "sv" ; rdf:value "sv_SE.utf8" ]
[ rdfs:label "zh" ; rdf:value "zh_CN.utf8" ]
Expand Down
8 changes: 4 additions & 4 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
if (sizeof($parts) <= 2) {
// if language code missing, redirect to guessed language
// in any case, redirect to <lang>/
$lang = sizeof($parts) == 2 && $parts[1] !== '' ? $parts[1] : $controller->guessLanguage();
$lang = sizeof($parts) == 2 && $parts[1] !== '' ? $parts[1] : $controller->guessLanguage($request);
header("Location: " . $lang . "/");
} else {
if (array_key_exists($parts[1], $config->getLanguages())) { // global pages
Expand All @@ -50,12 +50,12 @@
try {
$request->setVocab($parts[1]);
} catch (Exception | ValueError $e) {
$request->setLang($controller->guessLanguage());
$request->setLang($controller->guessLanguage($request));
$controller->invokeGenericErrorPage($request);
return;
}
if (sizeof($parts) == 3) { // language code missing
$lang = $controller->guessLanguage();
$lang = $controller->guessLanguage($request);
$newurl = $controller->getBaseHref() . $vocab . "/" . $lang . "/";
header("Location: " . $newurl);
} else {
Expand Down Expand Up @@ -97,7 +97,7 @@
$controller->invokeGenericErrorPage($request);
}
} else { // language code missing, redirect to some language version
$lang = $controller->guessLanguage($vocab);
$lang = $controller->guessLanguage($request, $vocab);
$newurl = $controller->getBaseHref() . $vocab . "/" . $lang . "/" . implode('/', array_slice($parts, 2));
$qs = $request->getServerConstant('QUERY_STRING');
if ($qs) {
Expand Down
24 changes: 24 additions & 0 deletions model/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Request
private $queryParams;
private $queryParamsPOST;
private $serverConstants;
private $cookies;

/**
* Initializes the Request Object
Expand Down Expand Up @@ -45,6 +46,13 @@ public function __construct($model)
foreach (filter_input_array(INPUT_SERVER) ?: [] as $key => $val) {
$this->serverConstants[$key] = $val;
}

// Store cookies in a local array, so we can mock them in tests.
// We do not apply any filters at this point.
$this->cookies = [];
foreach (filter_input_array(INPUT_COOKIE) ?: [] as $key => $val) {
$this->cookies[$key] = $val;
}
}

/**
Expand All @@ -67,6 +75,16 @@ public function setServerConstant($paramName, $value)
$this->serverConstants[$paramName] = $value;
}

/**
* Set a cookie to mock it in tests.
* @param string $paramName parameter name
* @param string $value parameter value
*/
public function setCookie($paramName, $value)
{
$this->cookies[$paramName] = $value;
}

/**
* Return the requested GET query parameter as a string. Backslashes are stripped for security reasons.
* @param string $paramName parameter name
Expand Down Expand Up @@ -110,6 +128,12 @@ public function getServerConstant($paramName)
return filter_var($this->serverConstants[$paramName], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
}

public function getCookie($paramName)
{
if (!isset($this->cookies[$paramName])) return null;
return filter_var($this->cookies[$paramName], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
}

public function getLang()
{
return $this->lang;
Expand Down
2 changes: 1 addition & 1 deletion tests/GlobalConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function testGetBaseHref()

public function testGetLanguages()
{
$this->assertEquals(array('en' => 'en_GB.utf8'), $this->config->getLanguages());
$this->assertEquals(array('en' => 'en_GB.utf8', 'fi' => 'fi_FI.utf8', 'fr' => 'fr_FR.utf8'), $this->config->getLanguages());
}

public function testGetSearchResultsSize()
Expand Down
36 changes: 36 additions & 0 deletions tests/WebControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,40 @@ public function testFormatChangeList() {
$expected = array ('hurr durr' => array ('uri' => 'http://www.skosmos.skos/changes/d3', 'prefLabel' => 'Hurr Durr', 'date' => DateTime::__set_state(array('date' => '2010-02-12 10:26:39.000000', 'timezone_type' => 3, 'timezone' => 'UTC')), 'datestring' => 'Feb 12, 2010'), 'second date' => array ('uri' => 'http://www.skosmos.skos/changes/d2', 'prefLabel' => 'Second date', 'date' => DateTime::__set_state(array('date' => '2010-02-12 15:26:39.000000', 'timezone_type' => 3, 'timezone' => 'UTC')), 'datestring' => 'Feb 12, 2010'));
$this->assertEquals($expected, $months['February 2010']);
}

public function testGuessLanguageFirstInConfig() {
$request = new Request($this->model);
$guessedLanguage = $this->webController->guessLanguage($request);
$this->assertEquals($guessedLanguage, 'en');
}

public function testGuessLanguageCookie() {
$request = new Request($this->model);
$request->setCookie('SKOSMOS_LANGUAGE', 'fr');
$guessedLanguage = $this->webController->guessLanguage($request);
$this->assertEquals($guessedLanguage, 'fr');
}

public function testGuessLanguageVocabDefault() {
$request = new Request($this->model);
$guessedLanguage = $this->webController->guessLanguage($request, 'groups');
$this->assertEquals($guessedLanguage, 'fi');
}

public function testGuessLanguageAcceptLanguageSimple() {
$request = new Request($this->model);
$request->setServerConstant('HTTP_ACCEPT_LANGUAGE', 'fr');
$guessedLanguage = $this->webController->guessLanguage($request);
$this->assertEquals($guessedLanguage, 'fr');
}

public function testGuessLanguageAcceptLanguageBestMatch() {
$request = new Request($this->model);
$request->setServerConstant('HTTP_ACCEPT_LANGUAGE', 'sv, de;q=0.9, fi;q=0.8, fr;q=0.5');
$guessedLanguage = $this->webController->guessLanguage($request);
// configured/available languages are en, fi, fr
// the best matching language for the given Accept-Language is fi
$this->assertEquals($guessedLanguage, 'fi');
}

}
4 changes: 3 additions & 1 deletion tests/testconfig.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
# customize the base element. Set this if the automatic base url detection doesn't work. For example setups behind a proxy.
skosmos:baseHref "http://tests.localhost/Skosmos/" ;
# interface languages available, and the corresponding system locales
skosmos:languages ( [ rdfs:label "en" ; rdf:value "en_GB.utf8" ] ) ;
skosmos:languages ( [ rdfs:label "en" ; rdf:value "en_GB.utf8" ]
[ rdfs:label "fi" ; rdf:value "fi_FI.utf8" ]
[ rdfs:label "fr" ; rdf:value "fr_FR.utf8" ] ) ;
# how many results (maximum) to load at a time on the search results page
skosmos:searchResultsSize 5 ;
# how many items (maximum) to retrieve in transitive property queries
Expand Down