Skip to content
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
35 changes: 0 additions & 35 deletions config-templates/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -733,41 +733,6 @@
| LANGUAGE AND INTERNATIONALIZATION |
*************************************/

/*
* Language-related options.
*/
'language' => [
/*
* An array in the form 'language' => <list of alternative languages>.
*
* Each key in the array is the ISO 639 two-letter code for a language,
* and its value is an array with a list of alternative languages that
* can be used if the given language is not available at some point.
* Each alternative language is also specified by its ISO 639 code.
*
* For example, for the "no" language code (Norwegian), we would have:
*
* 'priorities' => [
* 'no' => ['nb', 'nn', 'en', 'se'],
* ...
* ],
*
* establishing that if a translation for the "no" language code is
* not available, we look for translations in "nb",
* and so on, in that order.
*/
'priorities' => [
'no' => ['nb', 'nn', 'en', 'se'],
'nb' => ['no', 'nn', 'en', 'se'],
'nn' => ['no', 'nb', 'en', 'se'],
'se' => ['nb', 'no', 'nn', 'en'],
'nr' => ['zu', 'en'],
'nd' => ['zu', 'en'],
'tw' => ['st', 'en'],
'nso' => ['st', 'en'],
],
],

/*
* Languages available, RTL languages, and what language is the default.
*/
Expand Down
3 changes: 3 additions & 0 deletions docs/simplesamlphp-upgrade-notes-2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ Upgrade notes for SimpleSAMLphp 2.0
- lib/SimpleSAML/Store/SQL.php has been renamed to lib/SimpleSAML/Store/SQLStore.php
- lib/SimpleSAML/Store/Memcache.php has been renamed to lib/SimpleSAML/Store/MemcacheStore.php
- lib/SimpleSAML/Store/Redis.php has been renamed to lib/SimpleSAML/Store/RedisStore.php

- Configuration options removed:
- languages[priorities]
2 changes: 2 additions & 0 deletions lib/SimpleSAML/IdP.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ public function getConfig(): Configuration

/**
* Get SP name.
* Only used in IFrameLogout it seems.
* TODO: probably replace with template Template::getEntityDisplayName()
*
* @param string $assocId The association identifier.
*
Expand Down
26 changes: 21 additions & 5 deletions lib/SimpleSAML/Locale/Language.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ class Language
*/
private string $defaultLanguage;

/**
* The final fallback language to use when no current or default available
*
* @var string
*/
public const FALLBACKLANGUAGE = 'en';

/**
* An array holding a list of languages that are written from right to left.
*
Expand Down Expand Up @@ -146,7 +153,7 @@ public function __construct(Configuration $configuration)
{
$this->configuration = $configuration;
$this->availableLanguages = $this->getInstalledLanguages();
$this->defaultLanguage = $this->configuration->getString('language.default', 'en');
$this->defaultLanguage = $this->configuration->getString('language.default', self::FALLBACKLANGUAGE);
$this->languageParameterName = $this->configuration->getString('language.parameter.name', 'language');
$this->customFunction = $this->configuration->getArray('language.get_language_function', null);
$this->rtlLanguages = $this->configuration->getArray('language.rtl', []);
Expand All @@ -166,7 +173,7 @@ public function __construct(Configuration $configuration)
*/
private function getInstalledLanguages(): array
{
$configuredAvailableLanguages = $this->configuration->getArray('language.available', ['en']);
$configuredAvailableLanguages = $this->configuration->getArray('language.available', [self::FALLBACKLANGUAGE]);
$availableLanguages = [];
foreach ($configuredAvailableLanguages as $code) {
if (array_key_exists($code, self::$language_names) && isset(self::$language_names[$code])) {
Expand Down Expand Up @@ -377,6 +384,16 @@ public function isLanguageRTL(): bool
return in_array($this->getLanguage(), $this->rtlLanguages, true);
}

/**
* Returns the list of languages in order of preference. This is useful
* to search e.g. an array of entity names for first the current language,
* if not present the default language, if not present the fallback language.
*/
public function getPreferredLanguages(): array
{
$curLanguage = $this->getLanguage();
return array_unique([0 => $curLanguage, 1 => $this->defaultLanguage, 2 => self::FALLBACKLANGUAGE]);
}

/**
* Retrieve the user-selected language from a cookie.
Expand All @@ -386,7 +403,7 @@ public function isLanguageRTL(): bool
public static function getLanguageCookie(): ?string
{
$config = Configuration::getInstance();
$availableLanguages = $config->getArray('language.available', ['en']);
$availableLanguages = $config->getArray('language.available', [self::FALLBACKLANGUAGE]);
$name = $config->getString('language.cookie.name', 'language');

if (isset($_COOKIE[$name])) {
Expand All @@ -399,7 +416,6 @@ public static function getLanguageCookie(): ?string
return null;
}


/**
* This method will attempt to set the user-selected language in a cookie. It will do nothing if the language
* specified is not in the list of available languages, or the headers have already been sent to the browser.
Expand All @@ -410,7 +426,7 @@ public static function setLanguageCookie(string $language): void
{
$language = strtolower($language);
$config = Configuration::getInstance();
$availableLanguages = $config->getArray('language.available', ['en']);
$availableLanguages = $config->getArray('language.available', [self::FALLBACKLANGUAGE]);

if (!in_array($language, $availableLanguages, true) || headers_sent()) {
return;
Expand Down
14 changes: 0 additions & 14 deletions lib/SimpleSAML/Locale/Translate.php
Original file line number Diff line number Diff line change
Expand Up @@ -393,20 +393,6 @@ public static function translateFromArray(?array $context, ?array $translations)
return $translations[$context['currentLanguage']];
}

// we don't have a translation for the current language, load alternative priorities
$sspcfg = Configuration::getInstance();
/** @psalm-var \SimpleSAML\Configuration $langcfg */
$langcfg = $sspcfg->getConfigItem('language');
$priorities = $langcfg->getArray('priorities', []);

if (!empty($priorities[$context['currentLanguage']])) {
foreach ($priorities[$context['currentLanguage']] as $lang) {
if (isset($translations[$lang])) {
return $translations[$lang];
}
}
}

// nothing we can use, return null so that we can set a default
return null;
}
Expand Down
42 changes: 3 additions & 39 deletions lib/SimpleSAML/XHTML/IdPDisco.php
Original file line number Diff line number Diff line change
Expand Up @@ -589,30 +589,12 @@ public function handleRequest(): void

$t = new Template($this->config, $templateFile, 'disco');

$fallbackLanguage = 'en';
$defaultLanguage = $this->config->getString('language.default', $fallbackLanguage);
$translator = $t->getTranslator();
$language = $translator->getLanguage()->getLanguage();
$tryLanguages = [0 => $language, 1 => $defaultLanguage, 2 => $fallbackLanguage];

$newlist = [];
foreach ($idpList as $entityid => $data) {
$newlist[$entityid]['entityid'] = $entityid;
foreach ($tryLanguages as $lang) {
if ($name = $this->getEntityDisplayName($data, $lang)) {
$newlist[$entityid]['name'] = $name;
continue;
}
}
if (empty($newlist[$entityid]['name'])) {
$newlist[$entityid]['name'] = $entityid;
}
foreach ($tryLanguages as $lang) {
if (!empty($data['description'][$lang])) {
$newlist[$entityid]['description'] = $data['description'][$lang];
continue;
}
}
$newlist[$entityid]['name'] = $t->getEntityDisplayName($data);

$newlist[$entityid]['description'] = $t->getEntityPropertyTranslation('description', $data);
if (!empty($data['icon'])) {
$newlist[$entityid]['icon'] = $data['icon'];
$newlist[$entityid]['iconurl'] = $httpUtils->resolveURL($data['icon']);
Expand Down Expand Up @@ -640,22 +622,4 @@ function (array $idpentry1, array $idpentry2) {
$t->data['rememberchecked'] = $this->config->getBoolean('idpdisco.rememberchecked', false);
$t->send();
}


/**
* @param array $idpData
* @param string $language
* @return string|null
*/
private function getEntityDisplayName(array $idpData, string $language): ?string
{
if (isset($idpData['UIInfo']['DisplayName'][$language])) {
return $idpData['UIInfo']['DisplayName'][$language];
} elseif (isset($idpData['name'][$language])) {
return $idpData['name'][$language];
} elseif (isset($idpData['OrganizationDisplayName'][$language])) {
return $idpData['OrganizationDisplayName'][$language];
}
return null;
}
}
50 changes: 50 additions & 0 deletions lib/SimpleSAML/XHTML/Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,13 @@ private function setupTwig(): \Twig\Environment
['needs_context' => true]
)
);
// add a filter for preferred entity name
$twig->addFilter(
new TwigFilter(
'entityDisplayName',
[$this, 'getEntityDisplayName'],
)
);

// add an asset() function
$twig->addFunction(new TwigFunction('asset', [$this, 'asset']));
Expand Down Expand Up @@ -583,4 +590,47 @@ private function isLanguageRTL(): bool
{
return $this->translator->getLanguage()->isLanguageRTL();
}

/**
* Search through entity metadata to find the best display name for this
* entity. It will search in order for the current language, default
* language and fallback language for the DisplayName, name, OrganizationDisplayName
* and OrganizationName; the first one found is considered the best match.
* If nothing found, will return the entityId.
*/
public function getEntityDisplayName(array $data): string
{
$tryLanguages = $this->translator->getLanguage()->getPreferredLanguages();

foreach($tryLanguages as $language) {
if (isset($data['UIInfo']['DisplayName'][$language])) {
return $data['UIInfo']['DisplayName'][$language];
} elseif (isset($data['name'][$language])) {
return $data['name'][$language];
} elseif (isset($data['OrganizationDisplayName'][$language])) {
return $data['OrganizationDisplayName'][$language];
} elseif (isset($data['OrganizationName'][$language])) {
return $data['OrganizationName'][$language];
}
}
return $data['entityid'];
}

/**
* Search through entity metadata to find the best value for a
* specific property. It will search in order for the current language, default
* language and fallback language; if not found it returns null.
*/
public function getEntityPropertyTranslation(string $property, array $data): ?string
{
$tryLanguages = $this->translator->getLanguage()->getPreferredLanguages();

foreach($tryLanguages as $language) {
if (isset($data[$property][$language])) {
return $data[$property][$language];
}
}

return null;
}
}
23 changes: 23 additions & 0 deletions tests/lib/SimpleSAML/Locale/LanguageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,27 @@ public function testSetLanguage(): void
$l = new Language($c);
$this->assertEquals('en', $l->getLanguage());
}

public function testGetPreferredLanguages(): void
{
// test defaults
$c = Configuration::loadFromArray([], '', 'simplesaml');
$l = new Language($c);
$l->setLanguage('en');
$this->assertEquals(['en'], $l->getPreferredLanguages());

// test order current, default, fallback
$c = Configuration::loadFromArray([
'language.available' => ['fr', 'nn', 'es'],
'language.default' => 'nn',
], '', 'simplesaml');
$l = new Language($c);
$l->setLanguage('es');
$this->assertEquals(['es', 'nn', 'en'], $l->getPreferredLanguages());

// test duplicate values (curlang is default lang) removed
$l->setLanguage('nn');
$this->assertEquals([0 => 'nn', 2 => 'en'], $l->getPreferredLanguages());
}

}
Loading