1 | <?php |
||
2 | /** |
||
3 | * EGroupware API - Translations |
||
4 | * |
||
5 | * @link http://www.egroupware.org |
||
6 | * @author Joseph Engo <[email protected]> |
||
7 | * @author Dan Kuykendall <[email protected]> |
||
8 | * Copyright (C) 2000, 2001 Joseph Engo |
||
9 | * @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License |
||
10 | * @package api |
||
11 | */ |
||
12 | |||
13 | namespace EGroupware\Api; |
||
14 | |||
15 | /** |
||
16 | * EGroupware API - Translations |
||
17 | * |
||
18 | * All methods of this class can now be called static. |
||
19 | * |
||
20 | * Translations are cached tree-wide via Cache class. |
||
21 | * |
||
22 | * Translations are no longer stored in database, but load directly from *.lang files into cache. |
||
23 | * Only exception as instance specific translations: mainscreen, loginscreen and custom (see $instance_specific_translations) |
||
24 | */ |
||
25 | class Translation |
||
26 | { |
||
27 | /** |
||
28 | * Language of current user, will be set by init() |
||
29 | * |
||
30 | * @var string |
||
31 | */ |
||
32 | static $userlang = 'en'; |
||
33 | |||
34 | /** |
||
35 | * Already loaded translations by applicaton |
||
36 | * |
||
37 | * @var array $app => $lang pairs |
||
38 | */ |
||
39 | static $loaded_apps = array(); |
||
40 | |||
41 | /** |
||
42 | * Loaded phrases |
||
43 | * |
||
44 | * @var array $message_id => $translation pairs |
||
45 | */ |
||
46 | static $lang_arr = array(); |
||
47 | |||
48 | /** |
||
49 | * Tables used by this class |
||
50 | */ |
||
51 | const LANG_TABLE = 'egw_lang'; |
||
52 | const LANGUAGES_TABLE = 'egw_languages'; |
||
53 | |||
54 | /** |
||
55 | * Directory for language files |
||
56 | */ |
||
57 | const LANG_DIR = 'lang'; |
||
58 | |||
59 | /** |
||
60 | * Prefix of language files |
||
61 | */ |
||
62 | const LANGFILE_PREFIX = 'egw_'; |
||
63 | |||
64 | /** |
||
65 | * Prefix of language files |
||
66 | */ |
||
67 | const LANGFILE_EXTENSION = '.lang'; |
||
68 | |||
69 | /** |
||
70 | * Reference to global db-class |
||
71 | * |
||
72 | * @var Db |
||
73 | */ |
||
74 | static $db; |
||
75 | |||
76 | /** |
||
77 | * System charset |
||
78 | * |
||
79 | * @var string |
||
80 | */ |
||
81 | static $system_charset; |
||
82 | |||
83 | /** |
||
84 | * Is the mbstring extension available |
||
85 | * |
||
86 | * @var boolean |
||
87 | */ |
||
88 | static $mbstring; |
||
89 | /** |
||
90 | * Internal encoding / charset of PHP / mbstring (if loaded) |
||
91 | * |
||
92 | * @var string |
||
93 | */ |
||
94 | static $default_charset; |
||
95 | |||
96 | /** |
||
97 | * Application which translations have to be cached instance- and NOT tree-specific |
||
98 | * |
||
99 | * @var array |
||
100 | */ |
||
101 | static $instance_specific_translations = array('loginscreen','mainscreen','custom'); |
||
102 | |||
103 | /** |
||
104 | * returns the charset to use (!$lang) or the charset of the lang-files or $lang |
||
105 | * |
||
106 | * @param string|boolean $lang =False return charset of the active user-lang, or $lang if specified |
||
107 | * @return string charset |
||
108 | */ |
||
109 | static function charset($lang=False) |
||
110 | { |
||
111 | static $charsets = array(); |
||
112 | |||
113 | if ($lang) |
||
114 | { |
||
115 | if (!isset($charsets[$lang])) |
||
116 | { |
||
117 | if (!($charsets[$lang] = self::$db->select(self::LANG_TABLE,'content',array( |
||
118 | 'lang' => $lang, |
||
119 | 'message_id'=> 'charset', |
||
120 | 'app_name' => 'common', |
||
121 | ),__LINE__,__FILE__)->fetchColumn())) |
||
122 | { |
||
123 | $charsets[$lang] = 'utf-8'; |
||
124 | } |
||
125 | } |
||
126 | return $charsets[$lang]; |
||
127 | } |
||
128 | if (self::$system_charset) // do we have a system-charset ==> return it |
||
129 | { |
||
130 | $charset = self::$system_charset; |
||
131 | } |
||
132 | else |
||
133 | { |
||
134 | // if no translations are loaded (system-startup) use a default, else lang('charset') |
||
135 | $charset = !self::$lang_arr ? 'utf-8' : strtolower(self::translate('charset')); |
||
136 | } |
||
137 | // in case no charset is set, default to utf-8 |
||
138 | if (empty($charset) || $charset == 'charset') $charset = 'utf-8'; |
||
139 | |||
140 | // we need to set our charset as mbstring.internal_encoding if mbstring.func_overlaod > 0 |
||
141 | // else we get problems for a charset is different from the default utf-8 |
||
142 | $ini_default_charset = version_compare(PHP_VERSION, '5.6', '<') ? 'mbstring.internal_encoding' : 'default_charset'; |
||
143 | if (ini_get($ini_default_charset) && self::$default_charset != $charset) |
||
144 | { |
||
145 | ini_set($ini_default_charset, self::$default_charset = $charset); |
||
146 | } |
||
147 | return $charset; |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * Initialises global lang-array and loads the 'common' and app-spec. translations |
||
152 | * |
||
153 | * @param boolean $load_translations =true should we also load translations for common and currentapp |
||
154 | */ |
||
155 | static function init($load_translations=true) |
||
156 | { |
||
157 | if (!isset(self::$db)) |
||
158 | { |
||
159 | self::$db = isset($GLOBALS['egw_setup']) && isset($GLOBALS['egw_setup']->db) ? $GLOBALS['egw_setup']->db : $GLOBALS['egw']->db; |
||
160 | } |
||
161 | if (!isset($GLOBALS['egw_setup'])) |
||
162 | { |
||
163 | self::$system_charset = $GLOBALS['egw_info']['server']['system_charset']; |
||
164 | } |
||
165 | else |
||
166 | { |
||
167 | self::$system_charset =& $GLOBALS['egw_setup']->system_charset; |
||
168 | } |
||
169 | if ((self::$mbstring = check_load_extension('mbstring'))) |
||
170 | { |
||
171 | if(!empty(self::$system_charset)) |
||
172 | { |
||
173 | $ini_default_charset = version_compare(PHP_VERSION, '5.6', '<') ? 'mbstring.internal_encoding' : 'default_charset'; |
||
174 | ini_set($ini_default_charset, self::$system_charset); |
||
175 | } |
||
176 | } |
||
177 | |||
178 | // try loading load_via from tree-wide cache and check if it contains more rules |
||
179 | if (($load_via = Cache::getTree(__CLASS__, 'load_via')) && |
||
180 | $load_via >= self::$load_via && // > for array --> contains more elements |
||
181 | // little sanity check: cached array contains all stock keys, otherwise ignore it |
||
182 | !array_diff_key(self::$load_via, $load_via)) |
||
183 | { |
||
184 | self::$load_via = $load_via; |
||
185 | //error_log(__METHOD__."() load_via set from tree-wide cache to ".array2string(self::$load_via)); |
||
186 | } |
||
187 | self::$lang_arr = self::$loaded_apps = array(); |
||
188 | |||
189 | if ($load_translations) |
||
190 | { |
||
191 | if ($GLOBALS['egw_info']['user']['preferences']['common']['lang']) |
||
192 | { |
||
193 | self::$userlang = $GLOBALS['egw_info']['user']['preferences']['common']['lang']; |
||
194 | } |
||
195 | $apps = array('common'); |
||
196 | // for eTemplate apps, load etemplate before app itself (allowing app to overwrite etemplate translations) |
||
197 | if (class_exists('EGroupware\\Api\\Etemplate', false) || class_exists('etemplate', false)) $apps[] = 'etemplate'; |
||
198 | if ($GLOBALS['egw_info']['flags']['currentapp']) $apps[] = $GLOBALS['egw_info']['flags']['currentapp']; |
||
199 | // load instance specific translations last, so they can overwrite everything |
||
200 | $apps[] = 'custom'; |
||
201 | self::add_app($apps); |
||
202 | |||
203 | if (!count(self::$lang_arr)) |
||
204 | { |
||
205 | self::$userlang = 'en'; |
||
206 | self::add_app($apps); |
||
207 | } |
||
208 | } |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * translates a phrase and evtl. substitute some variables |
||
213 | * |
||
214 | * @param string $key phrase to translate, may contain placeholders %N (N=1,2,...) for vars |
||
215 | * @param array $vars =null vars to replace the placeholders, or null for none |
||
216 | * @param string $not_found ='*' what to add to not found phrases, default '*' |
||
217 | * @return string with translation |
||
218 | */ |
||
219 | static function translate($key, $vars=null, $not_found='' ) |
||
220 | { |
||
221 | if (!self::$lang_arr) |
||
222 | { |
||
223 | self::init(); |
||
224 | } |
||
225 | $ret = $key; // save key if we dont find a translation |
||
226 | if ($not_found) $ret .= $not_found; |
||
227 | |||
228 | if (isset(self::$lang_arr[$key])) |
||
229 | { |
||
230 | $ret = self::$lang_arr[$key]; |
||
231 | } |
||
232 | else |
||
233 | { |
||
234 | $new_key = strtolower($key); |
||
235 | |||
236 | if (isset(self::$lang_arr[$new_key])) |
||
237 | { |
||
238 | $ret = self::$lang_arr[$new_key]; |
||
239 | } |
||
240 | } |
||
241 | if (is_array($vars) && count($vars)) |
||
242 | { |
||
243 | if (count($vars) > 1) |
||
244 | { |
||
245 | static $placeholders = array('%3','%2','%1','|%2|','|%3|','%4','%5','%6','%7','%8','%9','%10'); |
||
246 | // to cope with $vars[0] containing '%2' (eg. an urlencoded path like a referer), |
||
247 | // we first replace '%2' in $ret with '|%2|' and then use that as 2. placeholder |
||
248 | // we do that for %3 as well, ... |
||
249 | $vars = array_merge(array('|%3|','|%2|'),$vars); // push '|%2|' (and such) as first replacement on $vars |
||
250 | $ret = str_replace($placeholders,$vars,$ret); |
||
251 | } |
||
252 | else |
||
253 | { |
||
254 | $ret = str_replace('%1',$vars[0],$ret); |
||
255 | } |
||
256 | } |
||
257 | return $ret; |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Translates a phrase according to the given user's language preference, |
||
262 | * which may be different from the current user. |
||
263 | * |
||
264 | * @param int $account_id |
||
265 | * @param string $message |
||
266 | * @param array $vars =null vars to replace the placeholders, or null for none |
||
267 | */ |
||
268 | static function translate_as($account_id, $message, $vars=null) |
||
269 | { |
||
270 | if(!is_numeric($account_id)) |
||
271 | { |
||
272 | return static::translate($message, $vars); |
||
273 | } |
||
274 | |||
275 | $preferences = new Preferences($account_id); |
||
276 | $prefs = $preferences->read(); |
||
277 | if($prefs['common']['lang'] != $GLOBALS['egw_info']['user']['preferences']['common']['lang']) |
||
278 | { |
||
279 | $old_lang = self::$userlang; |
||
280 | $GLOBALS['egw_info']['user']['preferences']['common']['lang'] = $prefs['common']['lang']; |
||
281 | $apps = array_keys(self::$loaded_apps); |
||
282 | self::init(true); |
||
283 | self::add_app($apps); |
||
284 | } |
||
285 | $phrase = static::translate($message, $vars); |
||
286 | if($old_lang) |
||
287 | { |
||
288 | $GLOBALS['egw_info']['user']['preferences']['common']['lang'] = $old_lang; |
||
289 | self::init(true); |
||
290 | self::add_app($apps); |
||
291 | } |
||
292 | return $phrase; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Adds translations for (multiple) application(s) |
||
297 | * |
||
298 | * By default the translations are read from the tree-wide cache |
||
299 | * |
||
300 | * @param string|array $apps name(s) of application(s) to add (or 'common' for the general translations) |
||
301 | * if multiple names given, they are requested in one request from cache and loaded in given order |
||
302 | * @param string $lang =false 2 or 5 char lang-code or false for the users language |
||
303 | */ |
||
304 | static function add_app($apps, $lang=null) |
||
305 | { |
||
306 | //error_log(__METHOD__."(".array2string($apps).", $lang) count(self::\$lang_arr)=".count(self::$lang_arr)); |
||
307 | //$start = microtime(true); |
||
308 | if (!$lang) $lang = self::$userlang; |
||
309 | $tree_level = $instance_level = array(); |
||
310 | if (!is_array($apps)) $apps = (array)$apps; |
||
311 | foreach($apps as $key => $app) |
||
312 | { |
||
313 | if (!isset(self::$loaded_apps[$app]) || self::$loaded_apps[$app] != $lang && $app != 'common') |
||
314 | { |
||
315 | if (in_array($app, self::$instance_specific_translations)) |
||
316 | { |
||
317 | $instance_level[] = $app.':'.($app == 'custom' ? 'en' : $lang); |
||
318 | } |
||
319 | else |
||
320 | { |
||
321 | $tree_level[] = $app.':'.$lang; |
||
322 | } |
||
323 | } |
||
324 | else |
||
325 | { |
||
326 | unset($apps[$key]); |
||
327 | } |
||
328 | } |
||
329 | // load all translations from cache at once |
||
330 | if ($tree_level) $tree_level = Cache::getTree(__CLASS__, $tree_level); |
||
331 | if ($instance_level) $instance_level = Cache::getInstance(__CLASS__, $instance_level); |
||
332 | |||
333 | // merging loaded translations together |
||
334 | $updated_load_via = false; |
||
335 | foreach((array)$apps as $app) |
||
336 | { |
||
337 | $l = $app == 'custom' ? 'en' : $lang; |
||
338 | if (isset($tree_level[$app.':'.$l])) |
||
339 | { |
||
340 | $loaded =& $tree_level[$app.':'.$l]; |
||
341 | } |
||
342 | elseif (isset($instance_level[$app.':'.$l])) |
||
343 | { |
||
344 | $loaded =& $instance_level[$app.':'.$l]; |
||
345 | } |
||
346 | else |
||
347 | { |
||
348 | if (($instance_specific = in_array($app, self::$instance_specific_translations))) |
||
349 | { |
||
350 | $loaded =& self::load_app($app, $l); |
||
351 | } |
||
352 | else |
||
353 | { |
||
354 | $loaded =& self::load_app_files($app, $l, null, $updated_load_via); |
||
355 | } |
||
356 | //error_log(__METHOD__."('$app', '$lang') instance_specific=$instance_specific, load_app(_files)() returned ".(is_array($loaded)?'Array('.count($loaded).')':array2string($loaded))); |
||
357 | if ($loaded || $instance_specific) |
||
358 | { |
||
359 | Cache::setCache($instance_specific ? Cache::INSTANCE : Cache::TREE, |
||
360 | __CLASS__, $app.':'.$l, $loaded); |
||
361 | //error_log(__METHOD__."('$app', '$lang') caching now ".(is_array($loaded)?'Array('.count($loaded).')':array2string($loaded))); |
||
362 | } |
||
363 | } |
||
364 | if ($loaded) |
||
365 | { |
||
366 | self::$lang_arr = array_merge(self::$lang_arr, $loaded); |
||
367 | self::$loaded_apps[$app] = $l; // dont set something not existing to $loaded_apps, no need to load client-side |
||
368 | } |
||
369 | } |
||
370 | // Re-merge custom over instance level, they have higher precidence |
||
371 | if($tree_level && !$instance_level && self::$instance_specific_translations) |
||
372 | { |
||
373 | $custom = Cache::getInstance(__CLASS__, 'custom:en'); |
||
374 | if($custom) |
||
375 | { |
||
376 | self::$lang_arr = array_merge(self::$lang_arr, $custom); |
||
377 | } |
||
378 | } |
||
379 | if ($updated_load_via) |
||
380 | { |
||
381 | self::update_load_via(); |
||
382 | } |
||
383 | //error_log(__METHOD__.'('.array2string($apps).", '$lang') took ".(1000*(microtime(true)-$start))." ms, loaded_apps=".array2string(self::$loaded_apps).", loaded ".count($loaded)." phrases -> total=".count(self::$lang_arr));//.": ".function_backtrace()); |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * Loads translations for an application from the database or direct from the lang-file for setup |
||
388 | * |
||
389 | * Never use directly, use add_app(), which employes caching (it has to be public, to act as callback for the cache!). |
||
390 | * |
||
391 | * @param string $app name of the application to add (or 'common' for the general translations) |
||
392 | * @param string $lang =false 2 or 5 char lang-code or false for the users language |
||
393 | * @return array the loaded strings |
||
394 | */ |
||
395 | static function &load_app($app,$lang) |
||
396 | { |
||
397 | //$start = microtime(true); |
||
398 | if (is_null(self::$db)) self::init(false); |
||
399 | $loaded = array(); |
||
400 | foreach(self::$db->select(self::LANG_TABLE,'message_id,content',array( |
||
401 | 'lang' => $lang, |
||
402 | 'app_name' => $app, |
||
403 | ),__LINE__,__FILE__) as $row) |
||
404 | { |
||
405 | $loaded[strtolower($row['message_id'])] = $row['content']; |
||
406 | } |
||
407 | //error_log(__METHOD__."($app,$lang) took ".(1000*(microtime(true)-$start))." ms to load ".count($loaded)." phrases"); |
||
408 | return $loaded; |
||
409 | } |
||
410 | |||
411 | /** |
||
412 | * How to load translations for a given app |
||
413 | * |
||
414 | * Translations for common, preferences or admin are in spread over all applications. |
||
415 | * Api, old phpgwapi and etemplate have translations for some pseudo-apps. |
||
416 | * |
||
417 | * @var array app => app(s) or string 'all-apps' |
||
418 | */ |
||
419 | static $load_via = array( |
||
420 | 'common' => 'all-apps', |
||
421 | 'preferences' => 'all-apps', |
||
422 | 'admin' => 'all-apps', |
||
423 | 'jscalendar' => array('phpgwapi'), |
||
424 | 'sitemgr-link' => array('sitemgr'), |
||
425 | 'groupdav' => array('api'), |
||
426 | 'developer_tools' => array('etemplate'), |
||
427 | 'login' => array('api','registration'), |
||
428 | ); |
||
429 | |||
430 | /** |
||
431 | * Check if cached translations are up to date or invalidate cache if not |
||
432 | * |
||
433 | * Called via login.php for each interactive login. |
||
434 | */ |
||
435 | static function check_invalidate_cache() |
||
436 | { |
||
437 | $lang = $GLOBALS['egw_info']['user']['preferences']['common']['lang']; |
||
438 | $apps = array_keys($GLOBALS['egw_info']['apps']); |
||
439 | foreach($apps as $app) |
||
440 | { |
||
441 | $file = self::get_lang_file($app, $lang); |
||
442 | // check if file has changed compared to what's cached |
||
443 | if (file_exists($file)) |
||
444 | { |
||
445 | $cached_time = Cache::getTree(__CLASS__, $file); |
||
446 | $file_time = filemtime($file); |
||
447 | if ($cached_time != $file_time) |
||
448 | { |
||
449 | //error_log(__METHOD__."() $file MODIFIED ($cached_time != $file_time)"); |
||
450 | self::invalidate_lang_file($app, $lang); |
||
451 | } |
||
452 | //else error_log(__METHOD__."() $file unchanged ($cached_time == $file_time)"); |
||
453 | } |
||
454 | } |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Invalidate cache for lang-file of $app and $lang |
||
459 | * |
||
460 | * @param string $app |
||
461 | * @param string $lang |
||
462 | */ |
||
463 | static function invalidate_lang_file($app, $lang) |
||
464 | { |
||
465 | //error_log(__METHOD__."('$app', '$lang') invalidate translations $app:$lang"); |
||
466 | Cache::unsetTree(__CLASS__, $app.':'.$lang); |
||
467 | Cache::unsetTree(__CLASS__, self::get_lang_file($app, $lang)); |
||
468 | |||
469 | foreach(self::$load_via as $load => $via) |
||
470 | { |
||
471 | //error_log("load_via[load='$load'] = via = ".array2string($via)); |
||
472 | if ($via === 'all-apps' || in_array($app, (array)$via)) |
||
473 | { |
||
474 | //error_log(__METHOD__."('$app', '$lang') additional invalidate translations $load:$lang"); |
||
475 | Cache::unsetTree(__CLASS__, $load.':'.$lang); |
||
476 | Cache::unsetTree(__CLASS__, self::get_lang_file($load, $lang)); |
||
477 | } |
||
478 | } |
||
479 | // unset statistics |
||
480 | Cache::unsetTree(__CLASS__, 'statistics'); |
||
481 | } |
||
482 | |||
483 | const STATISTIC_CACHE_TIMEOUT = 86400; |
||
484 | |||
485 | /** |
||
486 | * Statistical values about how much a language and app is translated, number or valid phrases per $lang or $lang/$app |
||
487 | * |
||
488 | * @param string $_lang =null |
||
489 | * @return array $lang or $app => number pairs |
||
490 | */ |
||
491 | static function statistics($_lang=null) |
||
492 | { |
||
493 | $cache = Cache::getTree(__CLASS__, 'statistics'); |
||
494 | |||
495 | if (!isset($cache[(string)$_lang])) |
||
496 | { |
||
497 | $cache[(string)$_lang] = array(); |
||
498 | if (empty($_lang)) |
||
499 | { |
||
500 | $en_phrases = array_keys(self::load_app_files(null, 'en', 'all-apps')); |
||
501 | $cache['']['en'] = count($en_phrases); |
||
502 | foreach(array_keys(self::get_available_langs()) as $lang) |
||
503 | { |
||
504 | if ($lang == 'en') continue; |
||
505 | $lang_phrases = array_keys(self::load_app_files(null, $lang, 'all-apps')); |
||
506 | $valid_phrases = array_intersect($lang_phrases, $en_phrases); |
||
507 | $cache[''][$lang] = count($valid_phrases); |
||
508 | } |
||
509 | } |
||
510 | else |
||
511 | { |
||
512 | $cache['en'] = array(); |
||
513 | foreach(scandir(EGW_SERVER_ROOT) as $app) |
||
514 | { |
||
515 | if ($app[0] == '.' || !is_dir(EGW_SERVER_ROOT.'/'.$app) || |
||
516 | !file_exists(self::get_lang_file($app, 'en'))) |
||
517 | { |
||
518 | continue; |
||
519 | } |
||
520 | $en_phrases = array_keys(self::load_app_files(null, 'en', $app)); |
||
521 | if (count($en_phrases) <= 2) continue; |
||
522 | $cache['en'][$app] = count($en_phrases); |
||
523 | $lang_phrases = array_keys(self::load_app_files(null, $_lang, $app)); |
||
524 | $valid_phrases = array_intersect($lang_phrases, $en_phrases); |
||
525 | $cache[$_lang][$app] = count($valid_phrases); |
||
526 | } |
||
527 | asort($cache['en'], SORT_NUMERIC); |
||
528 | $cache['en'] = array_reverse($cache['en'], true); |
||
529 | } |
||
530 | asort($cache[(string)$_lang], SORT_NUMERIC); |
||
531 | $cache[(string)$_lang] = array_reverse($cache[(string)$_lang], true); |
||
532 | Cache::setTree(__CLASS__, 'statistics', $cache, self::STATISTIC_CACHE_TIMEOUT); |
||
533 | } |
||
534 | return $cache[(string)$_lang]; |
||
535 | } |
||
536 | |||
537 | /** |
||
538 | * Get a state / etag for a given app's translations |
||
539 | * |
||
540 | * We currently only use a single state for all none-instance-specific apps depending on self::max_lang_time(). |
||
541 | * |
||
542 | * @param string $_app |
||
543 | * @param string $_lang |
||
544 | * @return string |
||
545 | */ |
||
546 | static function etag($_app, $_lang) |
||
547 | { |
||
548 | if (!in_array($_app, self::$instance_specific_translations)) |
||
549 | { |
||
550 | // check if cache is NOT invalided by checking if we have a modification time for concerned lang-file |
||
551 | $time = Cache::getTree(__CLASS__, $file=self::get_lang_file($_app, $_lang)); |
||
552 | // if we dont have one, cache has been invalidated and we need to load translations |
||
553 | if (!isset($time)) self::add_app($_app, $_lang); |
||
554 | |||
555 | $etag = self::max_lang_time(); |
||
556 | } |
||
557 | else |
||
558 | { |
||
559 | $etag = md5(json_encode(Cache::getCache(Cache::INSTANCE, __CLASS__, $_app.':'.$_lang))); |
||
560 | } |
||
561 | //error_log(__METHOD__."('$_app', '$_lang') returning '$etag'"); |
||
562 | return $etag; |
||
563 | } |
||
564 | |||
565 | /** |
||
566 | * Get or set maximum / latest modification-time for files of not instance-specific translations |
||
567 | * |
||
568 | * @param type $time |
||
569 | * @return type |
||
570 | */ |
||
571 | static function max_lang_time($time=null) |
||
572 | { |
||
0 ignored issues
–
show
|
|||
573 | static $max_lang_time = null; |
||
574 | |||
575 | if (!isset($max_lang_time) || isset($time)) |
||
576 | { |
||
577 | $max_lang_time = Cache::getTree(__CLASS__, 'max_lang_time'); |
||
578 | } |
||
579 | if (isset($time) && $time > $max_lang_time) |
||
580 | { |
||
581 | //error_log(__METHOD__."($time) updating previous max_lang_time=$max_lang_time to $time"); |
||
582 | Cache::setTree(__CLASS__, 'max_lang_time', $max_lang_time=$time); |
||
583 | } |
||
584 | return $max_lang_time; |
||
585 | } |
||
586 | |||
587 | /** |
||
588 | * Loads translations for an application direct from the lang-file(s) |
||
589 | * |
||
590 | * Never use directly, use add_app(), which employes caching (it has to be public, to act as callback for the cache!). |
||
591 | * |
||
592 | * @param string $app name of the application to add (or 'common' for the general translations) |
||
593 | * @param string $lang =false 2 or 5 char lang-code or false for the users language |
||
594 | * @param string $just_app_file =null if given only that app is loaded ignoring self::$load_via |
||
595 | * @param boolean $updated_load_via =false on return true if self::$load_via was updated |
||
596 | * @return array the loaded strings |
||
597 | */ |
||
598 | static function &load_app_files($app, $lang, $just_app_file=null, &$updated_load_via=false) |
||
599 | { |
||
600 | //$start = microtime(true); |
||
601 | $load_app = isset($just_app_file) ? $just_app_file : (isset(self::$load_via[$app]) ? self::$load_via[$app] : $app); |
||
602 | $loaded = array(); |
||
603 | foreach($load_app == 'all-apps' ? scandir(EGW_SERVER_ROOT) : (array)$load_app as $app_dir) |
||
604 | { |
||
605 | if ($load_app == 'all-apps' && $app_dir=='..') continue; // do not try to break out of egw server root |
||
606 | if ($app_dir[0] == '.' || !is_dir(EGW_SERVER_ROOT.'/'.$app_dir) || |
||
607 | !@file_exists($file=self::get_lang_file($app_dir, $lang)) || |
||
608 | !($f = fopen($file, 'r'))) |
||
609 | { |
||
610 | continue; |
||
611 | } |
||
612 | // store ctime of file we parse |
||
613 | Cache::setTree(__CLASS__, $file, $time=filemtime($file)); |
||
614 | self::max_lang_time($time); |
||
615 | |||
616 | $line_nr = 0; |
||
617 | //use fgets and split the line, as php5.3.3 with squeeze does not support splitting lines with fgetcsv while reading properly |
||
618 | //if the first letter after the delimiter is a german umlaut (UTF8 representation thereoff) |
||
619 | //while(($line = fgetcsv($f, 1024, "\t"))) |
||
620 | while(($read = fgets($f))) |
||
621 | { |
||
622 | $line = explode("\t", trim($read)); |
||
623 | ++$line_nr; |
||
624 | if (count($line) != 4) continue; |
||
625 | list($l_id,$l_app,$l_lang,$l_translation) = $line; |
||
626 | if ($l_lang != $lang) continue; |
||
627 | if (!isset($just_app_file) && $l_app != $app) |
||
628 | { |
||
629 | // check if $l_app contained in file in $app_dir is mentioned in $load_via |
||
630 | if ($l_app != $app_dir && (!isset(self::$load_via[$l_app]) || |
||
631 | !array_intersect((array)self::$load_via[$l_app], array('all-apps', $app_dir)))) |
||
632 | { |
||
633 | if (!isset(self::$load_via[$l_app]) && !file_exists(EGW_SERVER_ROOT.'/'.$l_app)) |
||
634 | { |
||
635 | error_log(__METHOD__."() lang file $file contains invalid app '$l_app' on line $line_nr --> ignored"); |
||
636 | continue; |
||
637 | } |
||
638 | // if not update load_via accordingly and store it as config |
||
639 | //error_log(__METHOD__."() load_via does not contain $l_app => $app_dir"); |
||
640 | if (!isset(self::$load_via[$l_app])) self::$load_via[$l_app] = array($l_app); |
||
641 | if (!is_array(self::$load_via[$l_app])) self::$load_via[$l_app] = array(self::$load_via[$l_app]); |
||
642 | self::$load_via[$l_app][] = $app_dir; |
||
643 | $updated_load_via = true; |
||
644 | } |
||
645 | else if ($l_app != $app_dir && |
||
646 | array_intersect((array)self::$load_via[$l_app], array('all-apps', $app_dir))) |
||
647 | { |
||
648 | $loaded[$l_id] = $l_translation; |
||
649 | } |
||
650 | continue; |
||
651 | } |
||
652 | $loaded[$l_id] = $l_translation; |
||
653 | } |
||
654 | fclose($f); |
||
655 | } |
||
656 | //error_log(__METHOD__."('$app', '$lang') returning ".(is_array($loaded)?'Array('.count($loaded).')':array2string($loaded))." in ".number_format(microtime(true)-$start,3)." secs".' '.function_backtrace()); |
||
657 | return $loaded; |
||
658 | } |
||
659 | |||
660 | /** |
||
661 | * Update tree-wide stored load_via with our changes |
||
662 | * |
||
663 | * Merging in meantime stored changes from other instances to minimize race-conditions |
||
664 | */ |
||
665 | protected static function update_load_via() |
||
666 | { |
||
667 | if (($load_via = Cache::getTree(__CLASS__, 'load_via')) && |
||
668 | // little sanity check: cached array contains all stock keys, otherwise ignore it |
||
669 | !array_diff_key(self::$load_via, $load_via)) |
||
670 | { |
||
671 | foreach($load_via as $app => $via) |
||
672 | { |
||
673 | if (self::$load_via[$app] != $via) |
||
674 | { |
||
675 | //error_log(__METHOD__."() setting load_via[$app]=".array2string($via)); |
||
676 | self::$load_via[$app] = array_unique(array_merge((array)self::$load_via[$app], (array)$via)); |
||
677 | } |
||
678 | } |
||
679 | } |
||
680 | Cache::setTree(__CLASS__, 'load_via', self::$load_via); |
||
681 | } |
||
682 | |||
683 | /** |
||
684 | * Cached languages |
||
685 | * |
||
686 | * @var array |
||
687 | */ |
||
688 | static $langs; |
||
689 | |||
690 | /** |
||
691 | * Returns a list of available languages / translations |
||
692 | * |
||
693 | * @param boolean $translate =true translate language-names |
||
694 | * @param boolean $force_read =false force a re-read of the languages |
||
695 | * @return array with lang-code => descriptiv lang-name pairs |
||
696 | */ |
||
697 | static function get_available_langs($translate=true, $force_read=false) |
||
698 | { |
||
699 | if (!is_array(self::$langs) || $force_read) |
||
700 | { |
||
701 | if (!($f = fopen($file=EGW_SERVER_ROOT.'/setup/lang/languages','rb'))) |
||
702 | { |
||
703 | throw new Exception("List of available languages (%1) missing!", $file); |
||
0 ignored issues
–
show
|
|||
704 | } |
||
705 | while(($line = fgetcsv($f, null, "\t"))) |
||
706 | { |
||
707 | self::$langs[$line[0]] = $line[1]; |
||
708 | } |
||
709 | fclose($f); |
||
710 | |||
711 | if ($translate) |
||
712 | { |
||
713 | if (is_null(self::$db)) self::init(false); |
||
714 | |||
715 | foreach(self::$langs as $lang => $name) |
||
716 | { |
||
717 | self::$langs[$lang] = self::translate($name,False,''); |
||
718 | } |
||
719 | } |
||
720 | uasort(self::$langs,'strcasecmp'); |
||
721 | } |
||
722 | return self::$langs; |
||
723 | } |
||
724 | |||
725 | /** |
||
726 | * Returns a list of installed languages / translations |
||
727 | * |
||
728 | * Translations no longer need to be installed, therefore all available translations are returned here. |
||
729 | * |
||
730 | * @param boolean $force_read =false force a re-read of the languages |
||
731 | * @return array with lang-code => descriptiv lang-name pairs |
||
732 | */ |
||
733 | static function get_installed_langs($force_read=false) |
||
734 | { |
||
735 | return self::get_available_langs($force_read); |
||
736 | } |
||
737 | |||
738 | /** |
||
739 | * translates a 2 or 5 char lang-code into a (verbose) language |
||
740 | * |
||
741 | * @param string $lang |
||
742 | * @return string|false language or false if not found |
||
743 | */ |
||
744 | static function lang2language($lang) |
||
745 | { |
||
746 | if (isset(self::$langs[$lang])) // no need to query the DB |
||
747 | { |
||
748 | return self::$langs[$lang]; |
||
749 | } |
||
750 | return self::$db->select(self::LANGUAGES_TABLE,'lang_name',array('lang_id' => $lang),__LINE__,__FILE__)->fetchColumn(); |
||
751 | } |
||
752 | |||
753 | /** |
||
754 | * List all languages, first available ones, then the rest |
||
755 | * |
||
756 | * @param boolean $force_read =false |
||
757 | * @return array with lang_id => lang_name pairs |
||
758 | */ |
||
759 | static function list_langs($force_read=false) |
||
760 | { |
||
761 | if (!$force_read) |
||
762 | { |
||
763 | return Cache::getInstance(__CLASS__,'list_langs',array(__CLASS__,'list_langs'),array(true)); |
||
764 | } |
||
765 | $languages = self::get_installed_langs(); // available languages |
||
766 | $availible = "('".implode("','",array_keys($languages))."')"; |
||
767 | |||
768 | // this shows first the installed, then the available and then the rest |
||
769 | foreach(self::$db->select(self::LANGUAGES_TABLE,array( |
||
770 | 'lang_id','lang_name', |
||
771 | "CASE WHEN lang_id IN $availible THEN 1 ELSE 0 END AS availible", |
||
772 | ),"lang_id NOT IN ('".implode("','",array_keys($languages))."')",__LINE__,__FILE__,false,' ORDER BY availible DESC,lang_name') as $row) |
||
773 | { |
||
774 | $languages[$row['lang_id']] = $row['lang_name']; |
||
775 | } |
||
776 | return $languages; |
||
777 | } |
||
778 | |||
779 | /** |
||
780 | * provides centralization and compatibility to locate the lang files |
||
781 | * |
||
782 | * @param string $app application name |
||
783 | * @param string $lang language code |
||
784 | * @return the full path of the filename for the requested app and language |
||
785 | */ |
||
786 | static function get_lang_file($app,$lang,$root=EGW_SERVER_ROOT) |
||
787 | { |
||
788 | if ($app == 'common') $app = 'api'; |
||
0 ignored issues
–
show
The type
EGroupware\Api\the was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths
Loading history...
|
|||
789 | |||
790 | return $root.'/'.$app.'/'.self::LANG_DIR.'/'.self::LANGFILE_PREFIX.$lang.self::LANGFILE_EXTENSION; |
||
791 | } |
||
792 | |||
793 | /** |
||
794 | * returns a list of installed charsets |
||
795 | * |
||
796 | * @return array with charset as key and comma-separated list of langs useing the charset as data |
||
797 | */ |
||
798 | static function get_installed_charsets() |
||
799 | { |
||
800 | static $charsets=null; |
||
801 | |||
802 | if (!isset($charsets)) |
||
803 | { |
||
804 | $charsets = array( |
||
805 | 'utf-8' => lang('Unicode').' (utf-8)', |
||
806 | 'iso-8859-1' => lang('Western european').' (iso-8859-1)', |
||
807 | 'iso-8859-2' => lang('Eastern european').' (iso-8859-2)', |
||
808 | 'iso-8859-7' => lang('Greek').' (iso-8859-7)', |
||
809 | 'euc-jp' => lang('Japanese').' (euc-jp)', |
||
810 | 'euc-kr' => lang('Korean').' (euc-kr)', |
||
811 | 'koi8-r' => lang('Russian').' (koi8-r)', |
||
812 | 'windows-1251' => lang('Bulgarian').' (windows-1251)', |
||
813 | 'cp850' => lang('DOS International').' (CP850)', |
||
814 | ); |
||
815 | } |
||
816 | return $charsets; |
||
817 | } |
||
818 | |||
819 | /** |
||
820 | * Transliterate utf-8 filename to ascii, eg. 'Äpfel' --> 'Aepfel' |
||
821 | * |
||
822 | * @param string $_str |
||
823 | * @return string |
||
824 | */ |
||
825 | static function to_ascii($_str) |
||
826 | { |
||
827 | static $extra = array( |
||
828 | 'ß' => 'ss', |
||
829 | '̈' => 'e', // mb_convert_encoding return ̈ for all German umlauts |
||
830 | ); |
||
831 | if (function_exists('mb_convert_encoding')) |
||
832 | { |
||
833 | $entities = mb_convert_encoding($_str, 'html-entities', self::charset()); |
||
834 | } |
||
835 | else |
||
836 | { |
||
837 | $entities = htmlentities($_str, ENT_QUOTES, self::charset()); |
||
838 | } |
||
839 | |||
840 | $estr = str_replace(array_keys($extra),array_values($extra), $entities); |
||
841 | $ustr = preg_replace('/&([aAuUoO])uml;/','\\1e', $estr); // replace german umlauts with the letter plus one 'e' |
||
842 | $astr = preg_replace('/&([a-zA-Z])(grave|acute|circ|ring|cedil|tilde|slash|uml);/','\\1', $ustr); // remove all types of accents |
||
843 | |||
844 | return preg_replace('/[^\x20-\x7f]/', '', // remove all non-ascii |
||
845 | preg_replace('/&([a-zA-Z]+|#[0-9]+|);/','', $astr)); // remove all other entities |
||
846 | } |
||
847 | |||
848 | /** |
||
849 | * converts a string $data from charset $from to charset $to |
||
850 | * |
||
851 | * @param string|array $data string(s) to convert |
||
852 | * @param string|boolean $from charset $data is in or False if it should be detected |
||
853 | * @param string|boolean $to charset to convert to or False for the system-charset the converted string |
||
854 | * @param boolean $check_to_from =true internal to bypass all charset replacements |
||
855 | * @return NULL|string|array converted string(s) from $data |
||
856 | */ |
||
857 | static function convert($data,$from=False,$to=False,$check_to_from=true) |
||
858 | { |
||
859 | if (empty($data)) |
||
860 | { |
||
861 | return $data; // no need for any charset conversation (NULL, '', 0, '0', array()) |
||
862 | } |
||
863 | if ($check_to_from) |
||
864 | { |
||
865 | if ($from) $from = strtolower($from); |
||
866 | |||
867 | if ($to) $to = strtolower($to); |
||
868 | |||
869 | if (!$from) |
||
870 | { |
||
871 | $from = self::$mbstring ? strtolower(mb_detect_encoding($data)) : 'iso-8859-1'; |
||
872 | if($from == 'ascii') |
||
873 | { |
||
874 | $from = 'iso-8859-1'; |
||
875 | } |
||
876 | //echo "<p>autodetected charset of '$data' = '$from'</p>\n"; |
||
877 | } |
||
878 | /* |
||
879 | php does not seem to support gb2312 |
||
880 | but seems to be able to decode it as EUC-CN |
||
881 | */ |
||
882 | switch($from) |
||
883 | { |
||
884 | case 'ks_c_5601-1987': |
||
885 | $from = 'CP949'; |
||
886 | break; |
||
887 | case 'gb2312': |
||
888 | case 'gb18030': |
||
889 | $from = 'EUC-CN'; |
||
890 | break; |
||
891 | case 'windows-1252': |
||
892 | case 'mswin1252': |
||
893 | if (function_exists('iconv')) |
||
894 | { |
||
895 | $prefer_iconv = true; |
||
896 | break; |
||
897 | } |
||
898 | // fall throught to remap to iso-8859-1 |
||
899 | case 'us-ascii': |
||
900 | case 'macroman': |
||
901 | case 'iso8859-1': |
||
902 | case 'windows-1258': |
||
903 | $from = 'iso-8859-1'; |
||
904 | break; |
||
905 | case 'windows-1250': |
||
906 | $from = 'iso-8859-2'; |
||
907 | break; |
||
908 | case 'windows-1253': |
||
909 | $from = 'iso-8859-7'; |
||
910 | break; |
||
911 | case 'windows-1257': |
||
912 | $from = 'iso-8859-13'; |
||
913 | break; |
||
914 | case 'windows-874': |
||
915 | case 'tis-620': |
||
916 | case 'windows-1256': |
||
917 | $prefer_iconv = true; |
||
918 | break; |
||
919 | } |
||
920 | if (!$to) |
||
921 | { |
||
922 | $to = self::charset(); |
||
923 | } |
||
924 | if ($from == $to || !$from || !$to || !$data) |
||
925 | { |
||
926 | return $data; |
||
927 | } |
||
928 | } |
||
929 | if (is_array($data)) |
||
930 | { |
||
931 | foreach($data as $key => $str) |
||
932 | { |
||
933 | $ret[$key] = empty($str) ? $str : // do NOT convert null to '' (other empty values need no conversation too) |
||
934 | self::convert($str,$from,$to,false); // false = bypass the above checks, as they are already done |
||
935 | } |
||
936 | return $ret; |
||
937 | } |
||
938 | if ($from == 'iso-8859-1' && $to == 'utf-8') |
||
939 | { |
||
940 | return utf8_encode($data); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
941 | } |
||
942 | if ($to == 'iso-8859-1' && $from == 'utf-8') |
||
943 | { |
||
944 | return utf8_decode($data); |
||
945 | } |
||
946 | if (self::$mbstring && !$prefer_iconv && ($data = @mb_convert_encoding($data,$to,$from)) != '') |
||
947 | { |
||
948 | return $data; |
||
949 | } |
||
950 | if (function_exists('iconv')) |
||
951 | { |
||
952 | // iconv can not convert from/to utf7-imap |
||
953 | if ($to == 'utf7-imap' && function_exists(imap_utf7_encode)) |
||
954 | { |
||
955 | $data_iso = iconv($from, 'iso-8859-1', $data); |
||
956 | $convertedData = imap_utf7_encode($data_iso); |
||
957 | |||
0 ignored issues
–
show
|
|||
958 | return $convertedData; |
||
959 | } |
||
960 | |||
961 | if ($from == 'utf7-imap' && function_exists(imap_utf7_decode)) |
||
962 | { |
||
963 | $data_iso = imap_utf7_decode($data); |
||
964 | $convertedData = iconv('iso-8859-1', $to, $data_iso); |
||
965 | |||
0 ignored issues
–
show
|
|||
966 | return $convertedData; |
||
967 | } |
||
968 | |||
969 | // the following is to workaround patch #962307 |
||
970 | // if using EUC-CN, for iconv it strickly follow GB2312 and fail |
||
971 | // in an email on the first Traditional/Japanese/Korean character, |
||
972 | // but in reality when people send mails in GB2312, UMA mostly use |
||
973 | // extended GB13000/GB18030 which allow T/Jap/Korean characters. |
||
974 | if($from == 'euc-cn') |
||
975 | { |
||
976 | $from = 'gb18030'; |
||
977 | } |
||
978 | |||
979 | if (($convertedData = iconv($from,$to,$data))) |
||
980 | { |
||
981 | return $convertedData; |
||
982 | } |
||
983 | } |
||
984 | return $data; |
||
985 | } |
||
986 | |||
987 | /** |
||
988 | * converts a string $data from charset $from to something that is json_encode tested |
||
989 | * |
||
990 | * @param string|array $_data string(s) to convert |
||
991 | * @param string|boolean $from charset $data is in or False if it should be detected |
||
992 | * @return string|array converted string(s) from $data |
||
993 | */ |
||
994 | static function convert_jsonsafe($_data,$from=False) |
||
995 | { |
||
996 | if ($from===false) $from = self::detect_encoding($_data); |
||
997 | |||
998 | $data = self::convert($_data, strtolower($from)); |
||
999 | |||
1000 | // in a way, this tests if we are having real utf-8 (the displayCharset) by now; we should if charsets reported (or detected) are correct |
||
1001 | if (strtoupper(self::charset()) == 'UTF-8') |
||
1002 | { |
||
1003 | $test = @json_encode($data); |
||
1004 | //error_log(__METHOD__.__LINE__.' ->'.strlen($data).' Error:'.json_last_error().'<- data:#'.$test.'#'); |
||
1005 | if (($test=="null" || $test === false || is_null($test)) && strlen($data)>0) |
||
1006 | { |
||
1007 | // try to fix broken utf8 |
||
1008 | $x = (function_exists('mb_convert_encoding')?mb_convert_encoding($data,'UTF-8','UTF-8'):(function_exists('iconv')?@iconv("UTF-8","UTF-8//IGNORE",$data):$data)); |
||
1009 | $test = @json_encode($x); |
||
1010 | if (($test=="null" || $test === false || is_null($test)) && strlen($data)>0) |
||
1011 | { |
||
1012 | // this should not be needed, unless something fails with charset detection/ wrong charset passed |
||
1013 | error_log(__METHOD__.__LINE__.' Charset Reported:'.$from.' Charset Detected:'.self::detect_encoding($data)); |
||
1014 | $data = utf8_encode($data); |
||
1015 | } |
||
1016 | else |
||
1017 | { |
||
1018 | $data = $x; |
||
1019 | } |
||
1020 | } |
||
1021 | } |
||
1022 | return $data; |
||
1023 | } |
||
1024 | |||
1025 | /** |
||
1026 | * insert/update/delete one phrase in the lang-table |
||
1027 | * |
||
1028 | * @param string $lang |
||
1029 | * @param string $app |
||
1030 | * @param string $message_id |
||
1031 | * @param string $content translation or null to delete translation |
||
1032 | */ |
||
1033 | static function write($lang,$app,$message_id,$content) |
||
1034 | { |
||
1035 | if ($content) |
||
1036 | { |
||
1037 | self::$db->insert(self::LANG_TABLE,array( |
||
1038 | 'content' => $content, |
||
1039 | ),array( |
||
1040 | 'lang' => $lang, |
||
1041 | 'app_name' => $app, |
||
1042 | 'message_id' => $message_id, |
||
1043 | ),__LINE__,__FILE__); |
||
1044 | } |
||
1045 | else |
||
1046 | { |
||
1047 | self::$db->delete(self::LANG_TABLE,array( |
||
1048 | 'lang' => $lang, |
||
1049 | 'app_name' => $app, |
||
1050 | 'message_id' => $message_id, |
||
1051 | ),__LINE__,__FILE__); |
||
1052 | } |
||
1053 | // invalidate the cache |
||
1054 | if(!in_array($app,self::$instance_specific_translations)) |
||
1055 | { |
||
1056 | Cache::unsetCache(Cache::TREE,__CLASS__,$app.':'.$lang); |
||
1057 | } |
||
1058 | else |
||
1059 | { |
||
1060 | foreach(array_keys((array)self::get_installed_langs()) as $key) |
||
1061 | { |
||
1062 | Cache::unsetCache(Cache::INSTANCE,__CLASS__,$app.':'.$key); |
||
1063 | } |
||
1064 | } |
||
1065 | } |
||
1066 | |||
1067 | /** |
||
1068 | * read one phrase from the lang-table |
||
1069 | * |
||
1070 | * @param string $lang |
||
1071 | * @param string $app_name |
||
1072 | * @param string $message_id |
||
1073 | * @return string|boolean content or false if not found |
||
1074 | */ |
||
1075 | static function read($lang,$app_name,$message_id) |
||
1076 | { |
||
1077 | return self::$db->select(self::LANG_TABLE,'content',array( |
||
1078 | 'lang' => $lang, |
||
1079 | 'app_name' => $app_name, |
||
1080 | 'message_id' => $message_id, |
||
1081 | ),__LINE__,__FILE__)->fetchColumn(); |
||
1082 | } |
||
1083 | |||
1084 | /** |
||
1085 | * Return the message_id of a given translation |
||
1086 | * |
||
1087 | * @param string $translation |
||
1088 | * @param string $app ='' default check all apps |
||
1089 | * @param string $lang ='' default check all langs |
||
1090 | * @return string |
||
1091 | */ |
||
1092 | static function get_message_id($translation,$app=null,$lang=null) |
||
1093 | { |
||
1094 | $where = array('content '.self::$db->capabilities[Db::CAPABILITY_CASE_INSENSITIV_LIKE].' '.self::$db->quote($translation)); |
||
1095 | if ($app) $where['app_name'] = $app; |
||
1096 | if ($lang) $where['lang'] = $lang; |
||
1097 | |||
1098 | $id = self::$db->select(self::LANG_TABLE,'message_id',$where,__LINE__,__FILE__)->fetchColumn(); |
||
1099 | |||
1100 | // Check cache, since most things aren't in the DB anymore |
||
1101 | if(!$id) |
||
1102 | { |
||
1103 | $ids = array_filter(array_keys(self::$lang_arr), function($haystack) use($translation) { |
||
1104 | return stripos(self::$lang_arr[$haystack],$translation) !== false; |
||
1105 | }); |
||
1106 | $id = array_shift($ids); |
||
1107 | if(!$id && ($lang && $lang !== 'en' || self::$userlang != 'en')) |
||
1108 | { |
||
1109 | // Try english |
||
1110 | if (in_array($app, self::$instance_specific_translations)) |
||
1111 | { |
||
1112 | $instance_level[] = $app.':en'; |
||
1113 | } |
||
1114 | else |
||
1115 | { |
||
1116 | $tree_level[] = $app.':en'; |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
1117 | } |
||
1118 | |||
1119 | // load all translations from cache at once |
||
1120 | if ($tree_level) $lang_arr = Cache::getTree(__CLASS__, $tree_level); |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
1121 | if ($instance_level) $lang_arr = Cache::getInstance(__CLASS__, $instance_level); |
||
1122 | $lang_arr = $lang_arr[$app.':en']; |
||
1123 | $ids = array_filter(array_keys($lang_arr), function($haystack) use($translation, $lang_arr) { |
||
1124 | return stripos($lang_arr[$haystack],$translation) !== false; |
||
1125 | }); |
||
1126 | $id = array_shift($ids); |
||
1127 | } |
||
1128 | } |
||
1129 | |||
1130 | return $id; |
||
1131 | } |
||
1132 | |||
1133 | /** |
||
1134 | * detect_encoding - try to detect the encoding |
||
1135 | * only to be used if the string in question has no structure that determines his encoding |
||
1136 | * |
||
1137 | * @param string - to be evaluated |
||
1138 | * @param string $verify =null encoding to verify, get checked first and have a match for only ascii or no detection available |
||
1139 | * @return string - encoding |
||
1140 | */ |
||
1141 | static function detect_encoding($string, $verify=null) |
||
0 ignored issues
–
show
|
|||
1142 | { |
||
1143 | if (function_exists('iconv')) |
||
1144 | { |
||
1145 | $list = array('utf-8', 'iso-8859-1', 'windows-1251'); // list may be extended |
||
1146 | |||
1147 | if ($verify) array_unshift($list, $verify); |
||
1148 | |||
1149 | foreach ($list as $item) |
||
1150 | { |
||
1151 | $sample = iconv($item, $item, $string); |
||
1152 | if ($sample == $string) |
||
1153 | { |
||
1154 | return $item; |
||
1155 | } |
||
1156 | } |
||
1157 | } |
||
1158 | if (self::$mbstring) |
||
1159 | { |
||
1160 | $detected = strtolower(mb_detect_encoding($string)); |
||
1161 | } |
||
1162 | if ($verify && (!isset($detected) || $detected === 'ascii')) |
||
1163 | { |
||
1164 | return $verify; // ascii matches all charsets |
||
1165 | } |
||
1166 | return isset($detected) ? $detected : 'iso-8859-1'; // we choose to return iso-8859-1 as default |
||
1167 | } |
||
1168 | } |
||
1169 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths