Newer
Older
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
<?php
/*********************************************************************
class.i18n.php
Internationalization and localization helpers for osTicket
Peter Rotich <peter@osticket.com>
Jared Hancock <jared@osticket.com>
Copyright (c) 2006-2013 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
require_once INCLUDE_DIR.'class.error.php';
require_once INCLUDE_DIR.'class.yaml.php';
class Internationalization {
// Languages in order of decreasing priority. Always use en_US as a
// fallback
var $langs = array('en_US');
function Internationalization($language=false) {
if ($language)
array_unshift($this->langs, $language);
}
function getTemplate($path) {
return new DataTemplate($path, $this->langs);
}
/**
* Loads data from the I18N_DIR for the target language into the
* database. This is intended to be done at the time of installation;
* however, care should be taken in this process to ensure that the
* process could be repeated if an administrator wanted to change the
* system language and reload the data.
*/
function loadDefaultData() {
# notrans -- do not translate the contents of this array
'department.yaml' => 'Dept',
'sla.yaml' => 'SLA',
'form.yaml' => 'DynamicForm',
// Note that department, sla, and forms are required for
// help_topic
'help_topic.yaml' => 'Topic',
'filter.yaml' => 'Filter',
'team.yaml' => 'Team',
'group.yaml' => 'Group',
'file.yaml' => 'AttachmentFile',
);
$errors = array();
foreach ($models as $yaml=>$m)
if ($objects = $this->getTemplate($yaml)->getData())
foreach ($objects as $o)
// Model::create($o)
call_user_func_array(
array($m, 'create'), array($o, &$errors));
// Priorities
$priorities = $this->getTemplate('priority.yaml')->getData();
foreach ($priorities as $name=>$info) {
$sql = 'INSERT INTO '.PRIORITY_TABLE
.' SET priority='.db_input($name)
.', priority_desc='.db_input($info['priority_desc'])
.', priority_color='.db_input($info['priority_color'])
.', priority_urgency='.db_input($info['priority_urgency']);
db_query($sql);
}
// Configuration
require_once INCLUDE_DIR.'class.config.php';
if (($tpl = $this->getTemplate('config.yaml'))
&& ($data = $tpl->getData())) {
foreach ($data as $section=>$items) {
$_config = new Config($section);
foreach ($items as $key=>$value)
$_config->set($key, $value);
}
}
// Pages
$_config = new OsticketConfig();
foreach (array('landing','thank-you','offline') as $type) {
$tpl = $this->getTemplate("templates/page/{$type}.yaml");
if (!($page = $tpl->getData()))
continue;
$sql = 'INSERT INTO '.PAGE_TABLE.' SET type='.db_input($type)
.', name='.db_input($page['name'])
.', body='.db_input($page['body'])
.', lang='.db_input($tpl->getLang())
.', notes='.db_input($page['notes'])
.', created=NOW(), updated=NOW(), isactive=1';
if (db_query($sql) && ($id = db_insert_id()))
$_config->set("{$type}_page_id", $id);
}
// Default Language
$_config->set('system_language', $this->langs[0]);
// Canned response examples
if (($tpl = $this->getTemplate('templates/premade.yaml'))
&& ($canned = $tpl->getData())) {
foreach ($canned as $c) {
if (($id = Canned::create($c, $errors))
$premade = Canned::lookup($id);
foreach ($c['attachments'] as $a) {
$premade->attachments->save($a, false);
}
}
}
}
// Email templates
// TODO: Lookup tpl_id
if ($objects = $this->getTemplate('email_template_group.yaml')->getData()) {
foreach ($objects as $o) {
$o['lang_id'] = $this->langs[0];
$tpl = EmailTemplateGroup::create($o, $errors);
}
}
// This shouldn't be necessary
foreach ($tpl::$all_names as $name=>$info) {
if (($tp = $this->getTemplate("templates/email/$name.yaml"))
&& ($t = $tp->getData())) {
$t['tpl_id'] = $tpl->getId();
$t['code_name'] = $name;
$id = EmailTemplate::create($t, $errors);
if ($id && ($template = EmailTemplate::lookup($id))
&& ($ids = Draft::getAttachmentIds($t['body'])))
$template->attachments->upload($ids, true);
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
static function availableLanguages($base=I18N_DIR) {
$langs = (include I18N_DIR . 'langs.php');
// Consider all subdirectories and .phar files in the base dir
$dirs = glob(I18N_DIR . '*', GLOB_ONLYDIR | GLOB_NOSORT);
$phars = glob(I18N_DIR . '*.phar', GLOB_NOSORT);
$installed = array();
foreach (array_merge($dirs, $phars) as $f) {
$base = basename($f, '.phar');
@list($code, $locale) = explode('_', $base);
if (isset($langs[$code])) {
$installed[strtolower($base)] =
$langs[$code] + array(
'lang' => $code,
'locale' => $locale,
'path' => $f,
'code' => $base,
'desc' => sprintf("%s%s (%s)",
$langs[$code]['nativeName'],
$locale ? sprintf(' - %s', $locale) : '',
$langs[$code]['name']),
);
}
}
usort($installed, function($a, $b) { return strcasecmp($a['code'], $b['code']); });
return $installed;
}
// TODO: Move this to the REQUEST class or some middleware when that
// exists.
// Algorithm borrowed from Drupal 7 (locale.inc)
static function getDefaultLanguage() {
global $cfg;
if (empty($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
return $cfg->getSystemLanguage();
$languages = self::availableLanguages();
// The Accept-Language header contains information about the
// language preferences configured in the user's browser / operating
// system. RFC 2616 (section 14.4) defines the Accept-Language
// header as follows:
// Accept-Language = "Accept-Language" ":"
// 1#( language-range [ ";" "q" "=" qvalue ] )
// language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
// Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
$browser_langcodes = array();
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
if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@',
trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
// We can safely use strtolower() here, tags are ASCII.
// RFC2616 mandates that the decimal part is no more than three
// digits, so we multiply the qvalue by 1000 to avoid floating
// point comparisons.
$langcode = strtolower($match[1]);
$qvalue = isset($match[2]) ? (float) $match[2] : 1;
$browser_langcodes[$langcode] = (int) ($qvalue * 1000);
}
}
// We should take pristine values from the HTTP headers, but
// Internet Explorer from version 7 sends only specific language
// tags (eg. fr-CA) without the corresponding generic tag (fr)
// unless explicitly configured. In that case, we assume that the
// lowest value of the specific tags is the value of the generic
// language to be as close to the HTTP 1.1 spec as possible.
//
// References:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
// http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx
asort($browser_langcodes);
foreach ($browser_langcodes as $langcode => $qvalue) {
$generic_tag = strtok($langcode, '-');
if (!isset($browser_langcodes[$generic_tag])) {
$browser_langcodes[$generic_tag] = $qvalue;
}
}
// Find the enabled language with the greatest qvalue, following the rules
// of RFC 2616 (section 14.4). If several languages have the same qvalue,
// prefer the one with the greatest weight.
$best_match_langcode = FALSE;
$max_qvalue = 0;
foreach ($languages as $langcode => $language) {
// Language tags are case insensitive (RFC2616, sec 3.10).
// We use _ as the location separator
$langcode = str_replace('_','-',strtolower($langcode));
// If nothing matches below, the default qvalue is the one of the wildcard
// language, if set, or is 0 (which will never match).
$qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0;
// Find the longest possible prefix of the browser-supplied language
// ('the language-range') that matches this site language ('the language tag').
$prefix = $langcode;
do {
if (isset($browser_langcodes[$prefix])) {
$qvalue = $browser_langcodes[$prefix];
break;
}
} while ($prefix = substr($prefix, 0, strrpos($prefix, '-')));
// Find the best match.
if ($qvalue > $max_qvalue) {
$best_match_langcode = $language['code'];
$max_qvalue = $qvalue;
}
}
return $best_match_langcode;
}
}
class DataTemplate {
// Base folder for default data and templates
var $base = I18N_DIR;
var $filepath;
var $data;
/**
* Searches for the files matching the template in the order of the
* received languages. Once matched, the language is captured so that
* template itself does not have to keep track of the language for which
* it is defined.
*/
function DataTemplate($path, $langs=array('en_US')) {
foreach ($langs as $l) {
if (file_exists("{$this->base}/$l/$path")) {
$this->lang = $l;
$this->filepath = Misc::realpath("{$this->base}/$l/$path");
elseif (Phar::isValidPharFilename("{$this->base}/$l.phar")
&& file_exists("phar://{$this->base}/$l.phar/$path")) {
$this->lang = $l;
$this->filepath = "phar://{$this->base}/$l.phar/$path";
break;
}
}
}
function getData() {
if (!isset($this->data) && $this->filepath)
$this->data = YamlDataParser::load($this->filepath);
// TODO: If there was a parsing error, attempt to try the next
// language in the list of requested languages
return $this->data;
}
function getLang() {
return $this->lang;
}
}
?>