. */ class it_text { var $actlanguage; # Selected language var $defaultlanguage; # Browser language var $languages = array(); # Active languages var $languages_available = array(); # Available languages var $statictext = array(); # Text array, read from php file on init var $label_to_service = array(); # which label belongs to which service - only used for debug parameter texts var $p; # Constructor Parameters /** * Constructor * Loads all texts.php in include path for translated labels. Singleton; if instanciated mutiple times, texts are merged * Example texts.php: array('en'=>"English", 'de'=>"Deutsch"), 'edit'=>array('en'=>"Edit", 'de'=>("Editieren"))); * @param $p['fallbacklanguage'] optional language to use for undefined texts (useful for partially translated projects) * @param $p['forcelanguage'] optional language to use instead of user's preferred language * @param $p['global'] store text object in global it_text for global functions (default: true) * @param $p['phpfile'] optional texts file(s), default: all texts.php in include path * @param $p['phpfiles'] text files to load in addition to $p['phpfile'] (Note: $p['phpfile'] defaults to all texts.php files in include path) * @param $p['transmogrifiers'] comma separated functions that may be called by using {foo:bar} (foo will be called with bar as argument) in T() */ function __construct($p = array()) { if (!$p['phpfile']) { # Find all texts.php in path (abs path in case we need to save) foreach (explode(PATH_SEPARATOR, ini_get('include_path')) as $dir) if (file_exists($phpfile = "$dir/texts.php")) $p['phpfiles'][] = $phpfile; } $this->p = ($p += (array)$GLOBALS['it_text_defaultconfig'] + array( 'global' => true, 'phpfiles' => array_unique(array_merge((array)$p['phpfiles'], (array)$p['phpfile'])), )); $this->allowedfuncs = array_flip(explode(",", $p['transmogrifiers'])); # Read and merge texts from php files if none defined yet foreach ($p['phpfiles'] as $phpfile) { $this->statictext += ($ret = include($phpfile)); if ($GLOBALS['debug_texts']) { $service = strpos($phpfile, $GLOBALS['ULTRAHOME']) !== false ? '' : it::match('/www/([^/.]+)', $phpfile); $this->label_to_service += array_combine(array_keys($ret), array_fill(0, count($ret), $service)); } } # Get array of supported languages and their names $this->languages_available = (array)$this->statictext['_']; foreach ($this->languages_available as $code => $languagename) { # Only use a language in browser detection below if it's not disabled by a leading '-' if (substr($languagename, 0, 1) != '-') { $this->languages[$code] = $languagename; if (!$this->actlanguage) $this->initlang($code); # failsafe lang } } # Set our default language according to browser preference if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { foreach (explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $code) if ($this->initlang($code) || $this->initlang(substr($code, 0, 2))) # lang from browser/lang group from browser break; } $this->defaultlanguage = $this->actlanguage; $this->initlang(it::match('\.([a-z]{2})\.[^./]+$', $_SERVER['PHP_SELF'])); # lang from url override $this->initlang($p['forcelanguage']); # programmer override # Create empty array to activate sampling; dont kill any existing one if (!it::is_live() || rand(1, $_POST ? 10 : 100) == 1) $GLOBALS['it_text_sampling'] = (array)$GLOBALS['it_text_sampling']; # Make this object available under $GLOBALS['it_text'], or add my texts to $GLOBALS['it_text'] if it exists if ($p['global']) { if (!$GLOBALS['it_text']) $GLOBALS['it_text'] =& $this; else $GLOBALS['it_text']->statictext += $this->statictext; } } # internal: overwrite language setting if code is valid, return success function initlang($code) { if ($this->languages[$code]) $this->actlanguage = $code; return $this->languages[$code]; } /** * Instanciate singleton if necessary */ static function init() { if (!$GLOBALS['it_text']) new it_text; } /** * INTERNAL function for T(): : Return translated text in the selected language */ function text($label, $language = null) { if (!$language) $language = $this->actlanguage; $text = $this->statictext[$label][$language]; if (!isset($text)) { $text = $this->statictext[$label][$this->p['fallbacklanguage']]; if (!isset($text)) { $text = "1, 'skipfiles'=>"text|auto_prepend")) . ")") . "'>" . Q($label) . "."; it::error(array('title'=>"unknown label $label language $language - see /tmp/alertdata/alertlog", 'backtraceskip'=>2, 'blockmail'=>21600)); } } if ($GLOBALS['debug_texts'] && !preg_match('/submit|button|servicedomain/i', $label) && (!$_GET['it_texts_mark'] || $label == $_GET['it_texts_mark'])) { $host = 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . preg_replace('/\.texts/', '', $_SERVER['HTTP_HOST']); if ($service = $this->label_to_service[$label]) $host = preg_replace('#//(admin\.)?[^.]*\.#', "//$service.", $host); $text = "1, 'skipfiles'=>"text|auto_prepend")) . ")'>" . ($text ? $text : $label) . "."; } if (isset($GLOBALS['it_text_sampling'])) $GLOBALS['it_text_sampling'][$label] = true; return $text; } /** * INTERNAL function for ET(): Return translated text with values replaced */ function etext($label, $values = null, $language = null) { return self::transmogrify($this->text($label, $language), $values, $label, $this->allowedfuncs); } /** * INTERNAL function for T_set_language() */ function set_language($language) { $this->actlanguage = is_scalar($language) && $this->languages_available[$language] ? $language : $this->defaultlanguage; } /** * INTERNAL function for T_lang(): Get active language */ function get_language() { return $this->actlanguage; } /** * INTERNAL function for T_lang(): Get active language */ function get_defaultlanguage() { return $this->defaultlanguage; } /** * INTERNAL function for T_exists(): Check if a text entry for a specific label exists */ function text_exists($label, $language = null) { return (isset($this->statictext[$label][isset($language) ? $language : $this->actlanguage]) || $this->p['fallbacklanguage'] && isset($this->statictext[$label][$this->p['fallbacklanguage']])) ? $label : false; } /** * Create / overwrite a text in the selected language. Call dump_php() to make the change permanent. * @param $label Label of text to change * @param $text New text to set * @param $language Optional language that is to be manipulated */ function set($label, $text = null, $language = null) { if (!isset($language)) $language = $this->actlanguage; $this->statictext[$label][$language] = $text; } /** * Replaces variables of the form {obj.var} with value from $values, e.g. {user.name}, or result of a func, e.g. {LU(//www/terms)} * NOTE: Invalid object names or non-existing variables are simply deleted. */ static function transmogrify($text, $values = null, $label = null, $allowedfuncs = null) { foreach (preg_split('#{([^}]*)}#', $text, -1, PREG_SPLIT_DELIM_CAPTURE) as $i => $part) { if ($i % 2) # odd offsets are delimiters, i.e. braces to be replaced { if (it::match('^[\w.]+$', $part)) { $value = $values ? $values : $GLOBALS; foreach (explode(".", $part) as $key) { $value = is_object($value) ? $value->$key : $value[$key]; if ($value === null && $values && $label) # do not test in $GLOBALS mode it::error(array('title' => "No value given for text variable {" . $key ."} in label $label", 'backtraceskip' => 3)); } } else $value = (list($func, $arg) = it::match('^([\w:]+)\((.*)\)$', $part)) && isset($allowedfuncs[$func]) ? $func($arg) : "{" . $part . "}"; $result .= $GLOBALS['debug_texts'] ? "$value" : $value; } else $result .= $part; } return $result; } /** * Re-create php text file from $this->statictext * @return true if successful, false if not (usually if file is not writeable by user www) */ function dump_php() { $result = false; # Special sorting: natural, but _ is the first entry uksort($this->statictext, "strnatcmp"); $this->statictext = array_merge(['_' => $this->statictext['_']], $this->statictext); $oldmask = umask(002); $filename = $this->p['phpfiles'][0]; if ((count($this->p['phpfiles']) == 1)) { foreach ($this->statictext as $label => $texts) { $dump .= var_export($label, true) . " => [\n"; foreach ($texts as $lang => $text) $dump .= " " . var_export($lang, true) . " => " . var_export($text, true) . ",\n"; $dump .= "],\n"; } $result = it::file_put_contents($filename, "