1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
|
<?php
/**
* Choosing the language to localize to for our minimalistic XHTML PHP based template system.
*
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
* @author Hanne Moa, UNINETT AS. <hanne.moa@uninett.no>
* @package SimpleSAMLphp
*/
declare(strict_types=1);
namespace SimpleSAML\Locale;
use SimpleSAML\Configuration;
use SimpleSAML\Logger;
use SimpleSAML\Utils;
class Language
{
/**
* This is the default language map. It is used to map languages codes from the user agent to other language codes.
*/
private static $defaultLanguageMap = ['nb' => 'no'];
/**
* The configuration to use.
*
* @var \SimpleSAML\Configuration
*/
private $configuration;
/**
* An array holding a list of languages available.
*
* @var array
*/
private $availableLanguages;
/**
* The language currently in use.
*
* @var null|string
*/
private $language = null;
/**
* The language to use by default.
*
* @var string
*/
private $defaultLanguage;
/**
* An array holding a list of languages that are written from right to left.
*
* @var array
*/
private $rtlLanguages;
/**
* HTTP GET language parameter name.
*
* @var string
*/
private $languageParameterName;
/**
* A custom function to use in order to determine the language in use.
*
* @var callable|null
*/
private $customFunction;
/**
* A list of languages supported with their names localized.
* Indexed by something that mostly resembles ISO 639-1 code,
* with some charming SimpleSAML-specific variants...
* that must remain before 2.0 due to backwards compatibility
*
* @var array
*/
public static $language_names = [
'no' => 'Bokmål', // Norwegian Bokmål
'nn' => 'Nynorsk', // Norwegian Nynorsk
'se' => 'Sámegiella', // Northern Sami
'sma' => 'Åarjelh-saemien giele', // Southern Sami
'da' => 'Dansk', // Danish
'en' => 'English',
'de' => 'Deutsch', // German
'sv' => 'Svenska', // Swedish
'fi' => 'Suomeksi', // Finnish
'es' => 'Español', // Spanish
'ca' => 'Català', // Catalan
'fr' => 'Français', // French
'it' => 'Italiano', // Italian
'nl' => 'Nederlands', // Dutch
'lb' => 'Lëtzebuergesch', // Luxembourgish
'cs' => 'Čeština', // Czech
'sl' => 'Slovenščina', // Slovensk
'lt' => 'Lietuvių kalba', // Lithuanian
'hr' => 'Hrvatski', // Croatian
'hu' => 'Magyar', // Hungarian
'pl' => 'Język polski', // Polish
'pt' => 'Português', // Portuguese
'pt-br' => 'Português brasileiro', // Portuguese
'ru' => 'русский язык', // Russian
'et' => 'eesti keel', // Estonian
'tr' => 'Türkçe', // Turkish
'el' => 'ελληνικά', // Greek
'ja' => '日本語', // Japanese
'zh' => '简体中文', // Chinese (simplified)
'zh-tw' => '繁體中文', // Chinese (traditional)
'ar' => 'العربية', // Arabic
'fa' => 'پارسی', // Persian
'ur' => 'اردو', // Urdu
'he' => 'עִבְרִית', // Hebrew
'id' => 'Bahasa Indonesia', // Indonesian
'sr' => 'Srpski', // Serbian
'lv' => 'Latviešu', // Latvian
'ro' => 'Românește', // Romanian
'eu' => 'Euskara', // Basque
'af' => 'Afrikaans', // Afrikaans
'zu' => 'IsiZulu', // Zulu
'xh' => 'isiXhosa', // Xhosa
'st' => 'Sesotho', // Sesotho
];
/**
* A mapping of SSP languages to locales
*
* @var array
*/
private $languagePosixMapping = [
'no' => 'nb_NO',
'nn' => 'nn_NO',
];
/**
* Constructor
*
* @param \SimpleSAML\Configuration $configuration Configuration object
*/
public function __construct(Configuration $configuration)
{
$this->configuration = $configuration;
$this->availableLanguages = $this->getInstalledLanguages();
$this->defaultLanguage = $this->configuration->getString('language.default', 'en');
$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', []);
if (isset($_GET[$this->languageParameterName])) {
$this->setLanguage(
$_GET[$this->languageParameterName],
$this->configuration->getBoolean('language.parameter.setcookie', true)
);
}
}
/**
* Filter configured (available) languages against installed languages.
*
* @return array The set of languages both in 'language.available' and self::$language_names.
*/
private function getInstalledLanguages(): array
{
$configuredAvailableLanguages = $this->configuration->getArray('language.available', ['en']);
$availableLanguages = [];
foreach ($configuredAvailableLanguages as $code) {
if (array_key_exists($code, self::$language_names) && isset(self::$language_names[$code])) {
$availableLanguages[] = $code;
} else {
Logger::error("Language \"$code\" not installed. Check config.");
}
}
return $availableLanguages;
}
/**
* Rename to non-idiosyncratic language code.
*
* @param string $language Language code for the language to rename, if necessary.
*
* @return string The language code.
*/
public function getPosixLanguage($language)
{
if (isset($this->languagePosixMapping[$language])) {
return $this->languagePosixMapping[$language];
}
return $language;
}
/**
* This method will set a cookie for the user's browser to remember what language was selected.
*
* @param string $language Language code for the language to set.
* @param boolean $setLanguageCookie Whether to set the language cookie or not. Defaults to true.
* @return void
*/
public function setLanguage($language, $setLanguageCookie = true)
{
$language = strtolower($language);
if (in_array($language, $this->availableLanguages, true)) {
$this->language = $language;
if ($setLanguageCookie === true) {
self::setLanguageCookie($language);
}
}
}
/**
* This method will return the language selected by the user, or the default language. It looks first for a cached
* language code, then checks for a language cookie, then it tries to calculate the preferred language from HTTP
* headers.
*
* @return string The language selected by the user according to the processing rules specified, or the default
* language in any other case.
*/
public function getLanguage()
{
// language is set in object
if (isset($this->language)) {
return $this->language;
}
// run custom getLanguage function if defined
if (isset($this->customFunction) && is_callable($this->customFunction)) {
$customLanguage = call_user_func($this->customFunction, $this);
if ($customLanguage !== null && $customLanguage !== false) {
return $customLanguage;
}
}
// language is provided in a stored cookie
$languageCookie = self::getLanguageCookie();
if ($languageCookie !== null) {
$this->language = $languageCookie;
return $languageCookie;
}
// check if we can find a good language from the Accept-Language HTTP header
$httpLanguage = $this->getHTTPLanguage();
if ($httpLanguage !== null) {
return $httpLanguage;
}
// language is not set, and we get the default language from the configuration
return $this->getDefaultLanguage();
}
/**
* Get the localized name of a language, by ISO 639-2 code.
*
* @param string $code The ISO 639-2 code of the language.
*
* @return string|null The localized name of the language.
*/
public function getLanguageLocalizedName($code)
{
if (array_key_exists($code, self::$language_names) && isset(self::$language_names[$code])) {
return self::$language_names[$code];
}
Logger::error("Name for language \"$code\" not found. Check config.");
return null;
}
/**
* Get the language parameter name.
*
* @return string The language parameter name.
*/
public function getLanguageParameterName()
{
return $this->languageParameterName;
}
/**
* This method returns the preferred language for the user based on the Accept-Language HTTP header.
*
* @return string|null The preferred language based on the Accept-Language HTTP header,
* or null if none of the languages in the header is available.
*/
private function getHTTPLanguage(): ?string
{
$languageScore = Utils\HTTP::getAcceptLanguage();
// for now we only use the default language map. We may use a configurable language map in the future
$languageMap = self::$defaultLanguageMap;
// find the available language with the best score
$bestLanguage = null;
$bestScore = -1.0;
foreach ($languageScore as $language => $score) {
// apply the language map to the language code
if (array_key_exists($language, $languageMap)) {
$language = $languageMap[$language];
}
if (!in_array($language, $this->availableLanguages, true)) {
// skip this language - we don't have it
continue;
}
/* Some user agents use very limited precision of the quality value, but order the elements in descending
* order. Therefore we rely on the order of the output from getAcceptLanguage() matching the order of the
* languages in the header when two languages have the same quality.
*/
if ($score > $bestScore) {
$bestLanguage = $language;
$bestScore = $score;
}
}
return $bestLanguage;
}
/**
* Return the default language according to configuration.
*
* @return string The default language that has been configured. Defaults to english if not configured.
*/
public function getDefaultLanguage()
{
return $this->defaultLanguage;
}
/**
* Return an alias for a language code, if any.
*
* @param string $langcode
* @return string|null The alias, or null if the alias was not found.
*/
public function getLanguageCodeAlias($langcode)
{
if (isset(self::$defaultLanguageMap[$langcode])) {
return self::$defaultLanguageMap[$langcode];
}
// No alias found, which is fine
return null;
}
/**
* Return an indexed list of all languages available.
*
* @return array An array holding all the languages available as the keys of the array. The value for each key is
* true in case that the language specified by that key is currently active, or false otherwise.
*/
public function getLanguageList()
{
$current = $this->getLanguage();
$list = array_fill_keys($this->availableLanguages, false);
$list[$current] = true;
return $list;
}
/**
* Check whether a language is written from the right to the left or not.
*
* @return boolean True if the language is right-to-left, false otherwise.
*/
public function isLanguageRTL()
{
return in_array($this->getLanguage(), $this->rtlLanguages, true);
}
/**
* Retrieve the user-selected language from a cookie.
*
* @return string|null The selected language or null if unset.
*/
public static function getLanguageCookie()
{
$config = Configuration::getInstance();
$availableLanguages = $config->getArray('language.available', ['en']);
$name = $config->getString('language.cookie.name', 'language');
if (isset($_COOKIE[$name])) {
$language = strtolower((string) $_COOKIE[$name]);
if (in_array($language, $availableLanguages, true)) {
return $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.
*
* @param string $language The language set by the user.
* @return void
*/
public static function setLanguageCookie($language)
{
assert(is_string($language));
$language = strtolower($language);
$config = Configuration::getInstance();
$availableLanguages = $config->getArray('language.available', ['en']);
if (!in_array($language, $availableLanguages, true) || headers_sent()) {
return;
}
$name = $config->getString('language.cookie.name', 'language');
$params = [
'lifetime' => ($config->getInteger('language.cookie.lifetime', 60 * 60 * 24 * 900)),
'domain' => strval($config->getString('language.cookie.domain', null)),
'path' => ($config->getString('language.cookie.path', '/')),
'secure' => ($config->getBoolean('language.cookie.secure', false)),
'httponly' => ($config->getBoolean('language.cookie.httponly', false)),
'samesite' => ($config->getString('language.cookie.samesite', null)),
];
Utils\HTTP::setCookie($name, $language, $params, false);
}
}
|