Newer
Older
Andreas Åkre Solberg
committed
* A minimalistic XHTML PHP based template system implemented for simpleSAMLphp.
*
Andreas Åkre Solberg
committed
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
Andreas Åkre Solberg
committed
* @package simpleSAMLphp
* @version $Id$
*/
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 $language = null;
Andreas Åkre Solberg
committed
private $langtext = null;
public $data = null;
/**
* Associative array of dictionaries.
*/
private $dictionaries = array();
Andreas Åkre Solberg
committed
/**
* Constructor
*
* @param $configuration Configuration object
* @param $template Which template file to load
* @param $languagefile Optionally load a language file
*/
Andreas Åkre Solberg
committed
function __construct(SimpleSAML_Configuration $configuration, $template, $languagefile = null) {
$this->configuration = $configuration;
$this->template = $template;
Andreas Åkre Solberg
committed
$this->data['baseurlpath'] = $this->configuration->getBaseURL();
Andreas Åkre Solberg
committed
if (isset($_GET['language'])) {
$this->setLanguage($_GET['language']);
}
Andreas Åkre Solberg
committed
if (!empty($languagefile)) $this->includeLanguageFile($languagefile);
/**
* 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) {
$this->language = $language;
Andreas Åkre Solberg
committed
// setcookie ( string $name [, string $value [, int $expire [, string $path [, string $domain [, bool $secure [, bool $httponly ]]]]]] )
// time()+60*60*24*900 expires 900 days from now.
setcookie('language', $language, time()+60*60*24*900, '/');
/**
* 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() {
Andreas Åkre Solberg
committed
// Language is set in object
if (isset($this->language)) {
return $this->language;
}
Andreas Åkre Solberg
committed
// Language is provided in a stored COOKIE
if (isset($_COOKIE['language'])) {
$this->language = $_COOKIE['language'];
return $this->language;
}
/* Check if we can find a good language from the Accept-Language http header. */
$httpLanguage = $this->getHTTPLanguage();
if ($httpLanguage !== NULL) {
return $httpLanguage;
}
Andreas Åkre Solberg
committed
// 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() {
$availableLanguages = $this->configuration->getValue('language.available');
$languageScore = SimpleSAML_Utilities::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;
Olav Morken
committed
foreach($languageScore as $language => $score) {
/* Apply the language map to the language code. */
if(array_key_exists($language, $languageMap)) {
$language = $languageMap[$language];
}
Olav Morken
committed
if(!in_array($language, $availableLanguages, TRUE)) {
/* Skip this language - we don't have it. */
continue;
}
Olav Morken
committed
/* 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 base (from configuration)
*/
private function getBaseLanguage() {
return $this->configuration->getValue('language.base', 'en');
}
Andreas Åkre Solberg
committed
/**
* Returns the language default (from configuration)
*/
Andreas Åkre Solberg
committed
private function getDefaultLanguage() {
return $this->configuration->getValue('language.default', 'en');
}
/**
* Returns a list of all available languages.
*/
private function getLanguageList() {
$availableLanguages = $this->configuration->getValue('language.available');
$thisLang = $this->getLanguage();
$lang = array();
foreach ($availableLanguages AS $nl) {
$lang[$nl] = ($nl == $thisLang);
}
return $lang;
}
/**
* Includs a file relative to the template base directory.
* This function can be used to include headers and footers etc.
Andreas Åkre Solberg
committed
*
private function includeAtTemplateBase($file) {
$data = $this->data;
$filename = $this->configuration->getPathValue('templatedir') . $this->configuration->getValue('theme.use') . '/' . $file;
if (!file_exists($filename)) {
SimpleSAML_Logger::error($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . $file .
'] at [' . $filename . '] - Now trying at base');
$filename = $this->configuration->getPathValue('templatedir') . $this->configuration->getValue('theme.base') . '/' . $file;
if (!file_exists($filename)) {
SimpleSAML_Logger::error($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . $file .
'] at [' . $filename . ']');
throw new Exception('Could not load template file [' . $file . ']');
}
/**
* 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)) {
$dictDir = $this->configuration->getPathValue('dictionarydir');
$this->dictionaries[$name] = $this->readDictionaryFile($dictDir . $name . '.php');
}
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)');
if(substr($tag, 0, 1) === '{' && preg_match('/^{(\w+?):(.*)}$/', $tag, $matches)) {
$dictionary = $matches[1];
$tag = $matches[2];
$dictionary = $this->getDictionary($dictionary);
} else {
$dictionary = $this->langtext;
}
if(!array_key_exists($tag, $dictionary)) {
return NULL;
}
return $dictionary[$tag];
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
/**
* 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];
}
/* Look up translation of tag in the base language. */
$base_language = $this->getBaseLanguage();
if(array_key_exists($base_language, $translations)) {
return $translations[$base_language];
}
/* 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.');
}
Andreas Åkre Solberg
committed
/**
* Include text in the current language.
*
Olav Morken
committed
* @param $tag A name tag of the string that should be returned.
* @param $replacements An associative array of keys that should be replaced with values in the translated string.
Andreas Åkre Solberg
committed
*/
Olav Morken
committed
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;
}
Andreas Åkre Solberg
committed
if (empty($this->langtext) || !is_array($this->langtext)) {
SimpleSAML_Logger::error('Template: No language text loaded. Looking up [' . $tag . ']');
Olav Morken
committed
return $this->t_not_translated($tag, TRUE);
Andreas Åkre Solberg
committed
}
$tagData = $this->getTag($tag);
if($tagData === NULL) {
/* Tag not found. */
SimpleSAML_Logger::info('Template: Looking up [' . $tag . ']: not translated at all.');
Olav Morken
committed
return $this->t_not_translated($tag, TRUE);
$translated = $this->getTranslation($tagData);
foreach ($replacements as $k => $v) {
/* try to translate if no replacement is given */
if (!$v) $v = $this->t($k);
$translated = str_replace($k, $v, $translated);
}
return $translated;
Andreas Åkre Solberg
committed
}
/**
* 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.
Andreas Åkre Solberg
committed
*/
private function t_not_translated($tag, $fallbacktag) {
if ($fallbacktag) {
return 'not translated (' . $tag . ')';
} else {
return NULL;
Andreas Åkre Solberg
committed
}
}
Andreas Åkre Solberg
committed
/**
* 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
*/
Mads Freek Petersen
committed
public function includeInlineTranslation($tag, $translation) {
Andreas Åkre Solberg
committed
$translation = array($this->getBaseLanguage() => $translation);
Mads Freek Petersen
committed
throw new Exception("Inline translation should be string or array. Is " . gettype($translation) . " now!");
}
Andreas Åkre Solberg
committed
if (!is_array($this->langtext))
$this->langtext = array();
SimpleSAML_Logger::info('Template: Adding inline language translation for tag [' . $tag . ']');
$this->langtext[$tag] = $translation;
}
Andreas Åkre Solberg
committed
/**
* Include language file from the dictionaries directory.
Andreas Åkre Solberg
committed
*
* @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.
Andreas Åkre Solberg
committed
*/
Andreas Åkre Solberg
committed
public function includeLanguageFile($file, $otherConfig = null) {
$filebase = null;
if (!empty($otherConfig)) {
$filebase = $otherConfig->getPathValue('dictionarydir');
} else {
$filebase = $this->configuration->getPathValue('dictionarydir');
}
Olav Morken
committed
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
$lang = $this->readDictionaryFile($filebase . $file);
if (is_array($this->langtext)) {
SimpleSAML_Logger::info('Template: Merging language array. Loading [' . $file . ']');
$this->langtext = array_merge($this->langtext, $lang);
} else {
SimpleSAML_Logger::info('Template: Setting new language array. Loading [' . $file . ']');
$this->langtext = $lang;
}
}
/**
* 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::info('Template: Reading [' . $filename . ']');
if (!file_exists($filename)) {
SimpleSAML_Logger::error($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . $this->template . '] at [' . $filename . ']');
return array();
Andreas Åkre Solberg
committed
}
Olav Morken
committed
$lang = NULL;
include($filename);
Andreas Åkre Solberg
committed
if (isset($lang)) {
Olav Morken
committed
return $lang;
Andreas Åkre Solberg
committed
}
Andreas Åkre Solberg
committed
Olav Morken
committed
return array();
Andreas Åkre Solberg
committed
}
Olav Morken
committed
Andreas Åkre Solberg
committed
/**
* Show the template to the user.
*/
public function show() {
Andreas Åkre Solberg
committed
$filename = $this->configuration->getPathValue('templatedir') .
$this->configuration->getValue('theme.use') . '/' . $this->template;
Andreas Åkre Solberg
committed
if (!file_exists($filename)) {
SimpleSAML_Logger::warning($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . $this->template . '] at [' . $filename . '] - now trying the base template');
Andreas Åkre Solberg
committed
$filename = $this->configuration->getPathValue('templatedir') .
$this->configuration->getValue('theme.base') . '/' . $this->template;
Andreas Åkre Solberg
committed
if (!file_exists($filename)) {
Andreas Åkre Solberg
committed
SimpleSAML_Logger::critical($_SERVER['PHP_SELF'].' - Template: Could not find template file [' . $this->template . '] at [' . $filename . ']');
Andreas Åkre Solberg
committed
echo 'Fatal error: Could not find template file [' . $this->template . '] at [' . $filename . ']';
exit(0);
}
require_once($filename);
}
}
?>