Skip to content
Snippets Groups Projects
Commit 213add97 authored by Thijs Kinkhorst's avatar Thijs Kinkhorst
Browse files

getEntityDisplayName to get entityID's name for display

Reimplement getPreferredTranslation outside of IdPDisco so it's reusable
and much more complete. The new method will search the current, default
and fallback language for a displayable string in various metadata
fields. This should be much more complete and robust than previous
approches. Make it a Twig filter so it can also be used there.
parent d5c4ff1e
No related branches found
No related tags found
No related merge requests found
......@@ -169,6 +169,8 @@ class IdP
/**
* Get SP name.
* Only usefd in IFrameLogout it seems.
* TODO: probably replace with template Template::getEntityDisplayName()
*
* @param string $assocId The association identifier.
*
......
......@@ -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.
*
......@@ -146,7 +153,7 @@ class Language
{
$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', []);
......@@ -166,7 +173,7 @@ class Language
*/
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])) {
......@@ -377,6 +384,16 @@ class Language
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.
......@@ -386,7 +403,7 @@ class Language
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])) {
......@@ -399,7 +416,6 @@ class Language
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.
......@@ -410,7 +426,7 @@ class Language
{
$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;
......
......@@ -589,30 +589,12 @@ class IdPDisco
$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']);
......@@ -640,22 +622,4 @@ class IdPDisco
$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;
}
}
......@@ -583,4 +583,47 @@ class Template extends Response
{
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;
}
}
......@@ -173,4 +173,27 @@ class LanguageTest extends TestCase
$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());
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Test\XHTML;
use Exception;
use PHPUnit\Framework\TestCase;
use SimpleSAML\Configuration;
use SimpleSAML\XHTML\Template;
/**
* @covers \SimpleSAML\XHTML\Template
*/
class TemplateTest extends TestCase
{
private const TEMPLATE = 'sandbox.twig';
function testSetup(): void
{
$c = Configuration::loadFromArray([], '', 'simplesaml');
$t = new Template($c, self::TEMPLATE);
$this->assertEquals(self::TEMPLATE, $t->getTemplateName());
}
function testNormalizeName(): void
{
$c = Configuration::loadFromArray([], '', 'simplesaml');
$t = new Template($c, 'sandbox');
$this->assertEquals(self::TEMPLATE, $t->getTemplateName());
}
function testTemplateModuleNamespace(): void
{
$c = Configuration::loadFromArray([], '', 'simplesaml');
$t = new Template($c, 'core:login');
$this->assertEquals('core:login.twig', $t->getTemplateName());
}
function testGetEntityDisplayNameBasic(): void
{
$c = Configuration::loadFromArray([], '', 'simplesaml');
$t = new Template($c, self::TEMPLATE);
$data = [
'entityid' => 'urn:example.org',
'name' => ['nl' => 'Something', 'en' => 'Other lang'],
];
$name = $t->getEntityDisplayName($data);
$this->assertEquals('Other lang', $name);
$c = Configuration::loadFromArray(['language.default' => 'nl'], '', 'simplesaml');
$t = new Template($c, self::TEMPLATE);
$name = $t->getEntityDisplayName($data);
$this->assertEquals('Something', $name);
}
function testGetEntityDisplayNamePriorities(): void
{
$c = Configuration::loadFromArray([], '', 'simplesaml');
$t = new Template($c, self::TEMPLATE);
$data = [
'entityid' => 'urn:example.org',
];
$name = $t->getEntityDisplayName($data);
$this->assertEquals('urn:example.org', $name);
$data['OrganizationName'] = ['fr' => 'Example Org', 'nl' => 'Anything Org'];
$data['OrganizationDisplayName'] = ['fr' => 'DisplayExample', 'nl' => 'DisplayAnything'];
$name = $t->getEntityDisplayName($data);
$this->assertEquals('urn:example.org', $name);
$data['OrganizationName']['en'] = 'Example Org EN';
$name = $t->getEntityDisplayName($data);
$this->assertEquals('Example Org EN', $name);
$c = Configuration::loadFromArray(['language.default' => 'nl'], '', 'simplesaml');
$t = new Template($c, self::TEMPLATE);
$data['UIInfo']['DisplayName'] = ['de' => 'UIname', 'nl' => 'UIname NL'];
$name = $t->getEntityDisplayName($data);
$this->assertEquals('UIname NL', $name);
}
function testGetEntityPropertyTranslation(): void
{
$c = Configuration::loadFromArray([], '', 'simplesaml');
$t = new Template($c, self::TEMPLATE);
$prop = 'description';
$data = [
'entityid' => 'urn:example.org',
$prop => ['nl' => 'Something', 'en' => 'Other lang', 'fr' => 'Another desc'],
];
$name = $t->getEntityPropertyTranslation($prop, $data);
$this->assertEquals('Other lang', $name);
$c = Configuration::loadFromArray(['language.default' => 'nl'], '', 'simplesaml');
$t = new Template($c, self::TEMPLATE);
$name = $t->getEntityPropertyTranslation($prop, $data);
$this->assertEquals('Something', $name);
unset($data[$prop]['nl']);
$name = $t->getEntityPropertyTranslation($prop, $data);
$this->assertEquals('Other lang', $name);
unset($data[$prop]['en']);
$name = $t->getEntityPropertyTranslation($prop, $data);
$this->assertNull($name);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment