diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php index 68275de8b5d52d5446d58f9d3741eadf24463ff1..f0ecda83b7078da94bc9ad36aa8145ba4752603e 100644 --- a/lib/SimpleSAML/XHTML/Template.php +++ b/lib/SimpleSAML/XHTML/Template.php @@ -8,710 +8,704 @@ */ class SimpleSAML_XHTML_Template { - /** - * This is the default language map. It is used to map languages codes from the user agent to - * other language codes. - */ - private static $defaultLanguageMap = array('nb' => 'no'); - - - private $configuration = null; - private $template = 'default.php'; - private $availableLanguages = array('en'); - private $language = null; - - private $langtext = array(); - - public $data = null; - - - /** - * Associative array of dictionaries. - */ - private $dictionaries = array(); - - - /** - * The default dictionary. - */ - private $defaultDictionary = NULL; - - - /** - * HTTP GET language parameter name. - */ - private $languageParameterName = 'language'; - - - /** - * Constructor - * - * @param $configuration Configuration object - * @param $template Which template file to load - * @param $defaultDictionary The default dictionary where tags will come from. - */ - function __construct(SimpleSAML_Configuration $configuration, $template, $defaultDictionary = NULL) { - $this->configuration = $configuration; - $this->template = $template; - - $this->data['baseurlpath'] = $this->configuration->getBaseURL(); - - $this->availableLanguages = $this->configuration->getArray('language.available', array('en')); - - $this->languageParameterName = $this->configuration->getString('language.parameter.name', 'language'); - if (isset($_GET[$this->languageParameterName])) { - $this->setLanguage($_GET[$this->languageParameterName], $this->configuration->getBoolean('language.parameter.setcookie', TRUE)); - } - - if($defaultDictionary !== NULL && substr($defaultDictionary, -4) === '.php') { - /* For backwards compatibility - print warning. */ - $backtrace = debug_backtrace(); - $where = $backtrace[0]['file'] . ':' . $backtrace[0]['line']; - SimpleSAML_Logger::warning('Deprecated use of new SimpleSAML_Template(...) at ' . $where . - '. The last parameter is now a dictionary name, which should not end in ".php".'); - - $this->defaultDictionary = substr($defaultDictionary, 0, -4); - } else { - $this->defaultDictionary = $defaultDictionary; - } - } - - /** - * setLanguage() will set a cookie for the user's browser to remember what language - * was selected - * - * @param $language Language code for the language to set. - */ - public function setLanguage($language, $setLanguageCookie = TRUE) { - $language = strtolower($language); - if (in_array($language, $this->availableLanguages, TRUE)) { - $this->language = $language; - if ($setLanguageCookie === TRUE) { - SimpleSAML_XHTML_Template::setLanguageCookie($language); - } - } - } - - /** - * getLanguage() will return the language selected by the user, or the default language - * This function first looks for a cached language code, - * then checks for a language cookie, - * then it tries to calculate the preferred language from HTTP headers. - * Last it returns the default language. - */ - public function getLanguage() { - - // Language is set in object - if (isset($this->language)) { - return $this->language; - } - - // Run custom getLanguage function if defined - $customFunction = $this->configuration->getArray('language.get_language_function', NULL); - if (isset($customFunction)) { - assert('is_callable($customFunction)'); - $customLanguage = call_user_func($customFunction, $this); - if ($customLanguage !== NULL && $customLanguage !== FALSE) { - return $customLanguage; - } - } - - // Language is provided in a stored COOKIE - $languageCookie = SimpleSAML_XHTML_Template::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(); - } - - - /** - * This function gets the prefered language for the user based on the Accept-Language http header. - * - * @return The prefered language based on the Accept-Language http header, or NULL if none of the - * languages in the header were available. - */ - private function getHTTPLanguage() { - $languageScore = \SimpleSAML\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 precicion 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; - } - - - /** - * Returns the language default (from configuration) - */ - private function getDefaultLanguage() { - return $this->configuration->getString('language.default', 'en'); - } - - /** - * Returns a list of all available languages. - */ - private function getLanguageList() { - $thisLang = $this->getLanguage(); - $lang = array(); - foreach ($this->availableLanguages AS $nl) { - $lang[$nl] = ($nl == $thisLang); - } - return $lang; - } - - /** - * Return TRUE if language is Right-to-Left. - */ - private function isLanguageRTL() { - $rtlLanguages = $this->configuration->getArray('language.rtl', array()); - $thisLang = $this->getLanguage(); - if (in_array($thisLang, $rtlLanguages)) { - return TRUE; - } - return FALSE; - } - - /** - * Includs a file relative to the template base directory. - * This function can be used to include headers and footers etc. - * - */ - private function includeAtTemplateBase($file) { - $data = $this->data; - - $filename = $this->findTemplatePath($file); - - include($filename); - } - - - /** - * Retrieve a dictionary. - * - * This function retrieves a dictionary with the given name. - * - * @param $name The name of the dictionary, as the filename in the dictionary directory, - * without the '.php'-ending. - * @return An associative array with the dictionary. - */ - private function getDictionary($name) { - assert('is_string($name)'); - - if(!array_key_exists($name, $this->dictionaries)) { - $sepPos = strpos($name, ':'); - if($sepPos !== FALSE) { - $module = substr($name, 0, $sepPos); - $fileName = substr($name, $sepPos + 1); - $dictDir = SimpleSAML_Module::getModuleDir($module) . '/dictionaries/'; - } else { - $dictDir = $this->configuration->getPathValue('dictionarydir', 'dictionaries/'); - $fileName = $name; - } - - $this->dictionaries[$name] = $this->readDictionaryFile($dictDir . $fileName); - } - - return $this->dictionaries[$name]; - } - - - /** - * Retrieve a tag. - * - * This function retrieves a tag as an array with language => string mappings. - * - * @param $tag The tag name. The tag name can also be on the form '{<dictionary>:<tag>}', to retrieve - * a tag from the specific dictionary. - * @return As associative array with language => string mappings, or NULL if the tag wasn't found. - */ - public function getTag($tag) { - assert('is_string($tag)'); - - /* First check translations loaded by the includeInlineTranslation and includeLanguageFile methods. */ - if(array_key_exists($tag, $this->langtext)) { - return $this->langtext[$tag]; - } - - /* Check whether we should use the default dictionary or a dictionary specified in the tag. */ - if(substr($tag, 0, 1) === '{' && preg_match('/^{((?:\w+:)?\w+?):(.*)}$/D', $tag, $matches)) { - $dictionary = $matches[1]; - $tag = $matches[2]; - } else { - $dictionary = $this->defaultDictionary; - if($dictionary === NULL) { - /* We don't have any dictionary to load the tag from. */ - return NULL; - } - } - - $dictionary = $this->getDictionary($dictionary); - if(!array_key_exists($tag, $dictionary)) { - return NULL; - } - - return $dictionary[$tag]; - } - - - /** - * Retrieve the preferred translation of a given text. - * - * @param $translations The translations, as an associative array with language => text mappings. - * @return The preferred translation. - */ - public function getTranslation($translations) { - assert('is_array($translations)'); - - /* Look up translation of tag in the selected language. */ - $selected_language = $this->getLanguage(); - if (array_key_exists($selected_language, $translations)) { - return $translations[$selected_language]; - } - - /* Look up translation of tag in the default language. */ - $default_language = $this->getDefaultLanguage(); - if(array_key_exists($default_language, $translations)) { - return $translations[$default_language]; - } - - /* Check for english translation. */ - if(array_key_exists('en', $translations)) { - return $translations['en']; - } - - /* Pick the first translation available. */ - if(count($translations) > 0) { - $languages = array_keys($translations); - return $translations[$languages[0]]; - } - - /* We don't have anything to return. */ - throw new Exception('Nothing to return from translation.'); - } - - - /** - * Translate a attribute name. - * - * @param string $name The attribute name. - * @return string The translated attribute name, or the original attribute name if no translation was found. - */ - public function getAttributeTranslation($name) { - - /* Normalize attribute name. */ - $normName = strtolower($name); - $normName = str_replace(":", "_", $normName); - - /* Check for an extra dictionary. */ - $extraDict = $this->configuration->getString('attributes.extradictionary', NULL); - if ($extraDict !== NULL) { - $dict = $this->getDictionary($extraDict); - if (array_key_exists($normName, $dict)) { - return $this->getTranslation($dict[$normName]); - } - } - - /* Search the default attribute dictionary. */ - $dict = $this->getDictionary('attributes'); - if (array_key_exists('attribute_' . $normName, $dict)) { - return $this->getTranslation($dict['attribute_' . $normName]); - } - - /* No translations found. */ - return $name; - } - - - /** - * Translate a tag into the current language, with a fallback to english. - * - * This function is used to look up a translation tag in dictionaries, and return the - * translation into the current language. If no translation into the current language can be - * found, english will be tried, and if that fails, placeholder text will be returned. - * - * An array can be passed as the tag. In that case, the array will be assumed to be on the - * form (language => text), and will be used as the source of translations. - * - * This function can also do replacements into the translated tag. It will search the - * translated tag for the keys provided in $replacements, and replace any found occurances - * with the value of the key. - * - * @param string|array $tag A tag name for the translation which should be looked up, or an - * array with (language => text) mappings. - * @param array $replacements An associative array of keys that should be replaced with - * values in the translated string. - * @return string The translated tag, or a placeholder value if the tag wasn't found. - */ - public function t($tag, $replacements = array(), $fallbackdefault = true, $oldreplacements = array(), $striptags = FALSE) { - if(!is_array($replacements)) { - - /* Old style call to t(...). Print warning to log. */ - $backtrace = debug_backtrace(); - $where = $backtrace[0]['file'] . ':' . $backtrace[0]['line']; - SimpleSAML_Logger::warning('Deprecated use of SimpleSAML_Template::t(...) at ' . $where . - '. Please update the code to use the new style of parameters.'); - - /* For backwards compatibility. */ - if(!$replacements && $this->getTag($tag) === NULL) { - SimpleSAML_Logger::warning('Code which uses $fallbackdefault === FALSE shouls be' . - ' updated to use the getTag-method instead.'); - return NULL; - } - - $replacements = $oldreplacements; - } - - if(is_array($tag)) { - $tagData = $tag; - } else { - $tagData = $this->getTag($tag); - if($tagData === NULL) { - /* Tag not found. */ - SimpleSAML_Logger::info('Template: Looking up [' . $tag . ']: not translated at all.'); - return $this->t_not_translated($tag, $fallbackdefault); - } - } - - $translated = $this->getTranslation($tagData); - - foreach ($replacements as $k => $v) { - /* try to translate if no replacement is given */ - if ($v == NULL) $v = $this->t($k); - $translated = str_replace($k, $v, $translated); - } - return $translated; - } - - /** - * Return the string that should be used when no translation was found. - * - * @param $tag A name tag of the string that should be returned. - * @param $fallbacktag If set to TRUE and string was not found in any languages, return - * the tag it self. If FALSE return NULL. - */ - private function t_not_translated($tag, $fallbacktag) { - if ($fallbacktag) { - return 'not translated (' . $tag . ')'; - } else { - return $tag; - } - } - - - /** - * You can include translation inline instead of putting translation - * in dictionaries. This function is reccomended to only be used from dynamic - * data, or when the translation is already provided from an external source, as - * a database or in metadata. - * - * @param $tag The tag that has a translation - * @param $translation The translation array - */ - public function includeInlineTranslation($tag, $translation) { - - if (is_string($translation)) { - $translation = array('en' => $translation); - } elseif (!is_array($translation)) { - throw new Exception("Inline translation should be string or array. Is " . gettype($translation) . " now!"); - } - - SimpleSAML_Logger::debug('Template: Adding inline language translation for tag [' . $tag . ']'); - $this->langtext[$tag] = $translation; - } - - /** - * Include language file from the dictionaries directory. - * - * @param $file File name of dictionary to include - * @param $otherConfig Optionally provide a different configuration object than - * the one provided in the constructor to be used to find the dictionary directory. - * This enables the possiblity of combining dictionaries inside simpleSAMLphp - * distribution with external dictionaries. - */ - public function includeLanguageFile($file, $otherConfig = null) { - - $filebase = null; - if (!empty($otherConfig)) { - $filebase = $otherConfig->getPathValue('dictionarydir', 'dictionaries/'); - } else { - $filebase = $this->configuration->getPathValue('dictionarydir', 'dictionaries/'); - } - - - $lang = $this->readDictionaryFile($filebase . $file); - SimpleSAML_Logger::debug('Template: Merging language array. Loading [' . $file . ']'); - $this->langtext = array_merge($this->langtext, $lang); - } - - - /** - * Read a dictionary file in json format. - * - * @param string $filename The absolute path to the dictionary file, minus the .definition.json ending. - * @return array The translation array from the file. - */ - private function readDictionaryJSON($filename) { - $definitionFile = $filename . '.definition.json'; - assert('file_exists($definitionFile)'); - - $fileContent = file_get_contents($definitionFile); - $lang = json_decode($fileContent, TRUE); - - if (empty($lang)) { - SimpleSAML_Logger::error('Invalid dictionary definition file [' . $definitionFile . ']'); - return array(); - } - - $translationFile = $filename . '.translation.json'; - if (file_exists($translationFile)) { - $fileContent = file_get_contents($translationFile); - $moreTrans = json_decode($fileContent, TRUE); - if (!empty($moreTrans)) { - $lang = self::lang_merge($lang, $moreTrans); - } - } - - return $lang; - } - - - /** - * Read a dictionary file in PHP format. - * - * @param string $filename The absolute path to the dictionary file. - * @return array The translation array from the file. - */ - private function readDictionaryPHP($filename) { - $phpFile = $filename . '.php'; - assert('file_exists($phpFile)'); - - $lang = NULL; - include($phpFile); - if (isset($lang)) { - return $lang; - } - - return array(); - } - - - /** - * Read a dictionary file. - * - * @param $filename The absolute path to the dictionary file. - * @return The translation array which was found in the dictionary file. - */ - private function readDictionaryFile($filename) { - assert('is_string($filename)'); - - SimpleSAML_Logger::debug('Template: Reading [' . $filename . ']'); - - $jsonFile = $filename . '.definition.json'; - if (file_exists($jsonFile)) { - return $this->readDictionaryJSON($filename); - } - - - $phpFile = $filename . '.php'; - if (file_exists($phpFile)) { - return $this->readDictionaryPHP($filename); - } - - SimpleSAML_Logger::error($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . $this->template . '] at [' . $filename . ']'); - return array(); - } - - - // Merge two translation arrays. - public static function lang_merge($def, $lang) { - foreach($def AS $key => $value) { - if (array_key_exists($key, $lang)) - $def[$key] = array_merge($value, $lang[$key]); - } - return $def; - } - - - /** - * Show the template to the user. - */ - public function show() { - - $filename = $this->findTemplatePath($this->template); - require($filename); - } - - - /** - * Find template path. - * - * This function locates the given template based on the template name. - * It will first search for the template in the current theme directory, and - * then the default theme. - * - * The template name may be on the form <module name>:<template path>, in which case - * it will search for the template file in the given module. - * - * An error will be thrown if the template file couldn't be found. - * - * @param string $template The relative path from the theme directory to the template file. - * @return string The absolute path to the template file. - */ - private function findTemplatePath($template) { - assert('is_string($template)'); - - $tmp = explode(':', $template, 2); - if (count($tmp) === 2) { - $templateModule = $tmp[0]; - $templateName = $tmp[1]; - } else { - $templateModule = 'default'; - $templateName = $tmp[0]; - } - - $tmp = explode(':', $this->configuration->getString('theme.use', 'default'), 2); - if (count($tmp) === 2) { - $themeModule = $tmp[0]; - $themeName = $tmp[1]; - } else { - $themeModule = NULL; - $themeName = $tmp[0]; - } - - - /* First check the current theme. */ - if ($themeModule !== NULL) { - /* .../module/<themeModule>/themes/<themeName>/<templateModule>/<templateName> */ - - $filename = SimpleSAML_Module::getModuleDir($themeModule) . '/themes/' . $themeName . '/' . $templateModule . '/' . $templateName; - - } elseif ($templateModule !== 'default') { - /* .../module/<templateModule>/templates/<themeName>/<templateName> */ - $filename = SimpleSAML_Module::getModuleDir($templateModule) . '/templates/' . $templateName; - - } else { - /* .../templates/<theme>/<templateName> */ - $filename = $this->configuration->getPathValue('templatedir', 'templates/') . $templateName; - } - - if (file_exists($filename)) { - return $filename; - } - - - /* Not found in current theme. */ - SimpleSAML_Logger::debug($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . - $template . '] at [' . $filename . '] - now trying the base template'); - - - /* Try default theme. */ - if ($templateModule !== 'default') { - /* .../module/<templateModule>/templates/<templateName> */ - $filename = SimpleSAML_Module::getModuleDir($templateModule) . '/templates/' . $templateName; - - } else { - /* .../templates/<templateName> */ - $filename = $this->configuration->getPathValue('templatedir', 'templates/') . '/' . $templateName; - } - - if (file_exists($filename)) { - return $filename; - } - - - /* Not found in default template - log error and throw exception. */ - $error = 'Template: Could not find template file [' . $template . '] at [' . $filename . ']'; - SimpleSAML_Logger::critical($_SERVER['PHP_SELF'] . ' - ' . $error); - - throw new Exception($error); - } - - - /** - * Retrieve the user-selected language from a cookie. - * - * @return string|NULL The language, or NULL if unset. - */ - public static function getLanguageCookie() { - $config = SimpleSAML_Configuration::getInstance(); - $availableLanguages = $config->getArray('language.available', array('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; - } - - - /** - * Set the user-selected language in a cookie. - * - * @param string $language The language. - */ - public static function setLanguageCookie($language) { - assert('is_string($language)'); - - $language = strtolower($language); - $config = SimpleSAML_Configuration::getInstance(); - $availableLanguages = $config->getArray('language.available', array('en')); - - if (!in_array($language, $availableLanguages, TRUE) || headers_sent()) { - return; - } - - $name = $config->getString('language.cookie.name', 'language'); - $params = array( - 'lifetime' => ($config->getInteger('language.cookie.lifetime', 60*60*24*900)), - 'domain' => ($config->getString('language.cookie.domain', NULL)), - 'path' => ($config->getString('language.cookie.path', '/')), - 'httponly' => FALSE, - ); + /** + * This is the default language map. It is used to map languages codes from the user agent to + * other language codes. + */ + private static $defaultLanguageMap = array('nb' => 'no'); + + + private $configuration = null; + private $template = 'default.php'; + private $availableLanguages = array('en'); + private $language = null; + + private $langtext = array(); + + public $data = null; + + + /** + * Associative array of dictionaries. + */ + private $dictionaries = array(); + + + /** + * The default dictionary. + */ + private $defaultDictionary = NULL; + + + /** + * HTTP GET language parameter name. + */ + private $languageParameterName = 'language'; + + + /** + * Constructor + * + * @param $configuration Configuration object + * @param $template Which template file to load + * @param $defaultDictionary The default dictionary where tags will come from. + */ + function __construct(SimpleSAML_Configuration $configuration, $template, $defaultDictionary = NULL) { + $this->configuration = $configuration; + $this->template = $template; + + $this->data['baseurlpath'] = $this->configuration->getBaseURL(); + + $this->availableLanguages = $this->configuration->getArray('language.available', array('en')); + + $this->languageParameterName = $this->configuration->getString('language.parameter.name', 'language'); + if (isset($_GET[$this->languageParameterName])) { + $this->setLanguage($_GET[$this->languageParameterName], $this->configuration->getBoolean('language.parameter.setcookie', TRUE)); + } + + if($defaultDictionary !== NULL && substr($defaultDictionary, -4) === '.php') { + /* For backwards compatibility - print warning. */ + $backtrace = debug_backtrace(); + $where = $backtrace[0]['file'] . ':' . $backtrace[0]['line']; + SimpleSAML_Logger::warning('Deprecated use of new SimpleSAML_Template(...) at ' . $where . + '. The last parameter is now a dictionary name, which should not end in ".php".'); + + $this->defaultDictionary = substr($defaultDictionary, 0, -4); + } else { + $this->defaultDictionary = $defaultDictionary; + } + } + + + /** + * setLanguage() will set a cookie for the user's browser to remember what language + * was selected + * + * @param $language Language code for the language to set. + */ + public function setLanguage($language, $setLanguageCookie = TRUE) { + $language = strtolower($language); + if (in_array($language, $this->availableLanguages, TRUE)) { + $this->language = $language; + if ($setLanguageCookie === TRUE) { + SimpleSAML_XHTML_Template::setLanguageCookie($language); + } + } + } + + /** + * getLanguage() will return the language selected by the user, or the default language + * This function first looks for a cached language code, + * then checks for a language cookie, + * then it tries to calculate the preferred language from HTTP headers. + * Last it returns the default language. + */ + public function getLanguage() { + + // Language is set in object + if (isset($this->language)) { + return $this->language; + } + + // Run custom getLanguage function if defined + $customFunction = $this->configuration->getArray('language.get_language_function', NULL); + if (isset($customFunction)) { + assert('is_callable($customFunction)'); + $customLanguage = call_user_func($customFunction, $this); + if ($customLanguage !== NULL && $customLanguage !== FALSE) { + return $customLanguage; + } + } + + // Language is provided in a stored COOKIE + $languageCookie = SimpleSAML_XHTML_Template::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(); + } + + + /** + * This function gets the prefered language for the user based on the Accept-Language http header. + * + * @return The prefered language based on the Accept-Language http header, or NULL if none of the + * languages in the header were available. + */ + private function getHTTPLanguage() { + $languageScore = \SimpleSAML\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 precicion 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; + } + + + /** + * Returns the language default (from configuration) + */ + private function getDefaultLanguage() { + return $this->configuration->getString('language.default', 'en'); + } + + /** + * Returns a list of all available languages. + */ + private function getLanguageList() { + $thisLang = $this->getLanguage(); + $lang = array(); + foreach ($this->availableLanguages AS $nl) { + $lang[$nl] = ($nl == $thisLang); + } + return $lang; + } + + /** + * Return TRUE if language is Right-to-Left. + */ + private function isLanguageRTL() { + $rtlLanguages = $this->configuration->getArray('language.rtl', array()); + $thisLang = $this->getLanguage(); + if (in_array($thisLang, $rtlLanguages)) { + return TRUE; + } + return FALSE; + } + + /** + * Includs a file relative to the template base directory. + * This function can be used to include headers and footers etc. + * + */ + private function includeAtTemplateBase($file) { + $data = $this->data; + + $filename = $this->findTemplatePath($file); + + include($filename); + } + + + /** + * Retrieve a dictionary. + * + * This function retrieves a dictionary with the given name. + * + * @param $name The name of the dictionary, as the filename in the dictionary directory, + * without the '.php'-ending. + * @return An associative array with the dictionary. + */ + private function getDictionary($name) { + assert('is_string($name)'); + + if(!array_key_exists($name, $this->dictionaries)) { + $sepPos = strpos($name, ':'); + if($sepPos !== FALSE) { + $module = substr($name, 0, $sepPos); + $fileName = substr($name, $sepPos + 1); + $dictDir = SimpleSAML_Module::getModuleDir($module) . '/dictionaries/'; + } else { + $dictDir = $this->configuration->getPathValue('dictionarydir', 'dictionaries/'); + $fileName = $name; + } + $this->dictionaries[$name] = $this->readDictionaryFile($dictDir . $fileName); + } + + return $this->dictionaries[$name]; + } + + + /** + * Retrieve a tag. + * + * This function retrieves a tag as an array with language => string mappings. + * + * @param $tag The tag name. The tag name can also be on the form '{<dictionary>:<tag>}', to retrieve + * a tag from the specific dictionary. + * @return As associative array with language => string mappings, or NULL if the tag wasn't found. + */ + public function getTag($tag) { + assert('is_string($tag)'); + + /* First check translations loaded by the includeInlineTranslation and includeLanguageFile methods. */ + if(array_key_exists($tag, $this->langtext)) { + return $this->langtext[$tag]; + } + + /* Check whether we should use the default dictionary or a dictionary specified in the tag. */ + if(substr($tag, 0, 1) === '{' && preg_match('/^{((?:\w+:)?\w+?):(.*)}$/D', $tag, $matches)) { + $dictionary = $matches[1]; + $tag = $matches[2]; + } else { + $dictionary = $this->defaultDictionary; + if($dictionary === NULL) { + /* We don't have any dictionary to load the tag from. */ + return NULL; + } + } + + $dictionary = $this->getDictionary($dictionary); + if(!array_key_exists($tag, $dictionary)) { + return NULL; + } + + return $dictionary[$tag]; + } + + + /** + * Retrieve the preferred translation of a given text. + * + * @param $translations The translations, as an associative array with language => text mappings. + * @return The preferred translation. + */ + public function getTranslation($translations) { + assert('is_array($translations)'); + + /* Look up translation of tag in the selected language. */ + $selected_language = $this->getLanguage(); + if (array_key_exists($selected_language, $translations)) { + return $translations[$selected_language]; + } + + /* Look up translation of tag in the default language. */ + $default_language = $this->getDefaultLanguage(); + if(array_key_exists($default_language, $translations)) { + return $translations[$default_language]; + } + + /* Check for english translation. */ + if(array_key_exists('en', $translations)) { + return $translations['en']; + } + + /* Pick the first translation available. */ + if(count($translations) > 0) { + $languages = array_keys($translations); + return $translations[$languages[0]]; + } + + /* We don't have anything to return. */ + throw new Exception('Nothing to return from translation.'); + } + + + /** + * Translate a attribute name. + * + * @param string $name The attribute name. + * @return string The translated attribute name, or the original attribute name if no translation was found. + */ + public function getAttributeTranslation($name) { + + /* Normalize attribute name. */ + $normName = strtolower($name); + $normName = str_replace(":", "_", $normName); + + /* Check for an extra dictionary. */ + $extraDict = $this->configuration->getString('attributes.extradictionary', NULL); + if ($extraDict !== NULL) { + $dict = $this->getDictionary($extraDict); + if (array_key_exists($normName, $dict)) { + return $this->getTranslation($dict[$normName]); + } + } + + /* Search the default attribute dictionary. */ + $dict = $this->getDictionary('attributes'); + if (array_key_exists('attribute_' . $normName, $dict)) { + return $this->getTranslation($dict['attribute_' . $normName]); + } + + /* No translations found. */ + return $name; + } + + + /** + * Translate a tag into the current language, with a fallback to english. + * + * This function is used to look up a translation tag in dictionaries, and return the + * translation into the current language. If no translation into the current language can be + * found, english will be tried, and if that fails, placeholder text will be returned. + * + * An array can be passed as the tag. In that case, the array will be assumed to be on the + * form (language => text), and will be used as the source of translations. + * + * This function can also do replacements into the translated tag. It will search the + * translated tag for the keys provided in $replacements, and replace any found occurances + * with the value of the key. + * + * @param string|array $tag A tag name for the translation which should be looked up, or an + * array with (language => text) mappings. + * @param array $replacements An associative array of keys that should be replaced with + * values in the translated string. + * @return string The translated tag, or a placeholder value if the tag wasn't found. + */ + public function t($tag, $replacements = array(), $fallbackdefault = true, $oldreplacements = array(), $striptags = FALSE) { + if(!is_array($replacements)) { + + /* Old style call to t(...). Print warning to log. */ + $backtrace = debug_backtrace(); + $where = $backtrace[0]['file'] . ':' . $backtrace[0]['line']; + SimpleSAML_Logger::warning('Deprecated use of SimpleSAML_Template::t(...) at ' . $where . + '. Please update the code to use the new style of parameters.'); + + /* For backwards compatibility. */ + if(!$replacements && $this->getTag($tag) === NULL) { + SimpleSAML_Logger::warning('Code which uses $fallbackdefault === FALSE shouls be' . + ' updated to use the getTag-method instead.'); + return NULL; + } + + $replacements = $oldreplacements; + } + + if(is_array($tag)) { + $tagData = $tag; + } else { + $tagData = $this->getTag($tag); + if($tagData === NULL) { + /* Tag not found. */ + SimpleSAML_Logger::info('Template: Looking up [' . $tag . ']: not translated at all.'); + return $this->t_not_translated($tag, $fallbackdefault); + } + } + + $translated = $this->getTranslation($tagData); + + foreach ($replacements as $k => $v) { + /* try to translate if no replacement is given */ + if ($v == NULL) $v = $this->t($k); + $translated = str_replace($k, $v, $translated); + } + return $translated; + } + + + /** + * Return the string that should be used when no translation was found. + * + * @param $tag A name tag of the string that should be returned. + * @param $fallbacktag If set to TRUE and string was not found in any languages, return + * the tag it self. If FALSE return NULL. + */ + private function t_not_translated($tag, $fallbacktag) { + if ($fallbacktag) { + return 'not translated (' . $tag . ')'; + } else { + return $tag; + } + } + + + /** + * You can include translation inline instead of putting translation + * in dictionaries. This function is reccomended to only be used from dynamic + * data, or when the translation is already provided from an external source, as + * a database or in metadata. + * + * @param $tag The tag that has a translation + * @param $translation The translation array + */ + public function includeInlineTranslation($tag, $translation) { + if (is_string($translation)) { + $translation = array('en' => $translation); + } elseif (!is_array($translation)) { + throw new Exception("Inline translation should be string or array. Is " . gettype($translation) . " now!"); + } + SimpleSAML_Logger::debug('Template: Adding inline language translation for tag [' . $tag . ']'); + $this->langtext[$tag] = $translation; + } + + /** + * Include language file from the dictionaries directory. + * + * @param $file File name of dictionary to include + * @param $otherConfig Optionally provide a different configuration object than + * the one provided in the constructor to be used to find the dictionary directory. + * This enables the possiblity of combining dictionaries inside simpleSAMLphp + * distribution with external dictionaries. + */ + public function includeLanguageFile($file, $otherConfig = null) { + $filebase = null; + if (!empty($otherConfig)) { + $filebase = $otherConfig->getPathValue('dictionarydir', 'dictionaries/'); + } else { + $filebase = $this->configuration->getPathValue('dictionarydir', 'dictionaries/'); + } + + $lang = $this->readDictionaryFile($filebase . $file); + SimpleSAML_Logger::debug('Template: Merging language array. Loading [' . $file . ']'); + $this->langtext = array_merge($this->langtext, $lang); + } + + + /** + * Read a dictionary file in json format. + * + * @param string $filename The absolute path to the dictionary file, minus the .definition.json ending. + * @return array The translation array from the file. + */ + private function readDictionaryJSON($filename) { + $definitionFile = $filename . '.definition.json'; + assert('file_exists($definitionFile)'); + + $fileContent = file_get_contents($definitionFile); + $lang = json_decode($fileContent, TRUE); + + if (empty($lang)) { + SimpleSAML_Logger::error('Invalid dictionary definition file [' . $definitionFile . ']'); + return array(); + } + + $translationFile = $filename . '.translation.json'; + if (file_exists($translationFile)) { + $fileContent = file_get_contents($translationFile); + $moreTrans = json_decode($fileContent, TRUE); + if (!empty($moreTrans)) { + $lang = self::lang_merge($lang, $moreTrans); + } + } + + return $lang; + } + + + /** + * Read a dictionary file in PHP format. + * + * @param string $filename The absolute path to the dictionary file. + * @return array The translation array from the file. + */ + private function readDictionaryPHP($filename) { + $phpFile = $filename . '.php'; + assert('file_exists($phpFile)'); + + $lang = NULL; + include($phpFile); + if (isset($lang)) { + return $lang; + } + + return array(); + } + + + /** + * Read a dictionary file. + * + * @param $filename The absolute path to the dictionary file. + * @return The translation array which was found in the dictionary file. + */ + private function readDictionaryFile($filename) { + assert('is_string($filename)'); + + SimpleSAML_Logger::debug('Template: Reading [' . $filename . ']'); + + $jsonFile = $filename . '.definition.json'; + if (file_exists($jsonFile)) { + return $this->readDictionaryJSON($filename); + } + + + $phpFile = $filename . '.php'; + if (file_exists($phpFile)) { + return $this->readDictionaryPHP($filename); + } + + SimpleSAML_Logger::error($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . $this->template . '] at [' . $filename . ']'); + return array(); + } + + + // Merge two translation arrays. + public static function lang_merge($def, $lang) { + foreach($def AS $key => $value) { + if (array_key_exists($key, $lang)) + $def[$key] = array_merge($value, $lang[$key]); + } + return $def; + } + + + /** + * Show the template to the user. + */ + public function show() { + + $filename = $this->findTemplatePath($this->template); + require($filename); + } + + + /** + * Find template path. + * + * This function locates the given template based on the template name. + * It will first search for the template in the current theme directory, and + * then the default theme. + * + * The template name may be on the form <module name>:<template path>, in which case + * it will search for the template file in the given module. + * + * An error will be thrown if the template file couldn't be found. + * + * @param string $template The relative path from the theme directory to the template file. + * @return string The absolute path to the template file. + */ + private function findTemplatePath($template) { + assert('is_string($template)'); + + $tmp = explode(':', $template, 2); + if (count($tmp) === 2) { + $templateModule = $tmp[0]; + $templateName = $tmp[1]; + } else { + $templateModule = 'default'; + $templateName = $tmp[0]; + } + + $tmp = explode(':', $this->configuration->getString('theme.use', 'default'), 2); + if (count($tmp) === 2) { + $themeModule = $tmp[0]; + $themeName = $tmp[1]; + } else { + $themeModule = NULL; + $themeName = $tmp[0]; + } + + + /* First check the current theme. */ + if ($themeModule !== NULL) { + /* .../module/<themeModule>/themes/<themeName>/<templateModule>/<templateName> */ + + $filename = SimpleSAML_Module::getModuleDir($themeModule) . '/themes/' . $themeName . '/' . $templateModule . '/' . $templateName; + } elseif ($templateModule !== 'default') { + /* .../module/<templateModule>/templates/<themeName>/<templateName> */ + $filename = SimpleSAML_Module::getModuleDir($templateModule) . '/templates/' . $templateName; + } else { + /* .../templates/<theme>/<templateName> */ + $filename = $this->configuration->getPathValue('templatedir', 'templates/') . $templateName; + } + + if (file_exists($filename)) { + return $filename; + } + + + /* Not found in current theme. */ + SimpleSAML_Logger::debug($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . + $template . '] at [' . $filename . '] - now trying the base template'); + + + /* Try default theme. */ + if ($templateModule !== 'default') { + /* .../module/<templateModule>/templates/<templateName> */ + $filename = SimpleSAML_Module::getModuleDir($templateModule) . '/templates/' . $templateName; + } else { + /* .../templates/<templateName> */ + $filename = $this->configuration->getPathValue('templatedir', 'templates/') . '/' . $templateName; + } + + if (file_exists($filename)) { + return $filename; + } + + + /* Not found in default template - log error and throw exception. */ + $error = 'Template: Could not find template file [' . $template . '] at [' . $filename . ']'; + SimpleSAML_Logger::critical($_SERVER['PHP_SELF'] . ' - ' . $error); + + throw new Exception($error); + } + + + /** + * Retrieve the user-selected language from a cookie. + * + * @return string|NULL The language, or NULL if unset. + */ + public static function getLanguageCookie() { + $config = SimpleSAML_Configuration::getInstance(); + $availableLanguages = $config->getArray('language.available', array('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; + } + + + /** + * Set the user-selected language in a cookie. + * + * @param string $language The language. + */ + public static function setLanguageCookie($language) { + assert('is_string($language)'); + + $language = strtolower($language); + $config = SimpleSAML_Configuration::getInstance(); + $availableLanguages = $config->getArray('language.available', array('en')); + + if (!in_array($language, $availableLanguages, TRUE) || headers_sent()) { + return; + } + + $name = $config->getString('language.cookie.name', 'language'); + $params = array( + 'lifetime' => ($config->getInteger('language.cookie.lifetime', 60*60*24*900)), + 'domain' => ($config->getString('language.cookie.domain', NULL)), + 'path' => ($config->getString('language.cookie.path', '/')), + 'httponly' => FALSE, + ); \SimpleSAML\Utils\HTTP::setCookie($name, $language, $params, FALSE); - } + } }