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
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
|
<?php
/**
* A minimalistic XHTML PHP based template system implemented for simpleSAMLphp.
*
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
* @package simpleSAMLphp
*/
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_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;
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);
# if (!empty($replacements)){ echo('<pre> [' . $tag . ']'); print_r($replacements); exit; }
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_Utilities::setCookie($name, $language, $params, FALSE);
}
}
|