dolibarr  18.0.0-alpha
translate.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2001 Eric Seigne <erics@rycks.com>
3  * Copyright (C) 2004-2015 Destailleur Laurent <eldy@users.sourceforge.net>
4  * Copyright (C) 2005-2010 Regis Houssin <regis.houssin@inodbox.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <https://www.gnu.org/licenses/>.
18  */
19 
30 class Translate
31 {
32  public $dir; // Directories that contains /langs subdirectory
33 
34  public $defaultlang; // Current language for current user
35  public $shortlang; // Short language for current user
36  public $charset_output = 'UTF-8'; // Codage used by "trans" method outputs
37 
38  public $tab_translate = array(); // Array of all translations key=>value
39  private $_tab_loaded = array(); // Array to store result after loading each language file
40 
41  public $cache_labels = array(); // Cache for labels return by getLabelFromKey method
42  public $cache_currencies = array(); // Cache to store currency symbols
43  private $cache_currencies_all_loaded = false;
44  public $origlang;
45  public $error;
46  public $errors = array();
47 
48 
55  public function __construct($dir, $conf)
56  {
57  if (!empty($conf->file->character_set_client)) {
58  $this->charset_output = $conf->file->character_set_client; // If charset output is forced
59  }
60  if ($dir) {
61  $this->dir = array($dir);
62  } else {
63  $this->dir = $conf->file->dol_document_root;
64  }
65  }
66 
67 
74  public function setDefaultLang($srclang = 'en_US')
75  {
76  global $conf;
77 
78  //dol_syslog(get_class($this)."::setDefaultLang srclang=".$srclang,LOG_DEBUG);
79 
80  // If a module ask to force a priority on langs directories (to use its own lang files)
81  if (!empty($conf->global->MAIN_FORCELANGDIR)) {
82  $more = array();
83  $i = 0;
84  foreach ($conf->file->dol_document_root as $dir) {
85  $newdir = $dir.$conf->global->MAIN_FORCELANGDIR; // For example $conf->global->MAIN_FORCELANGDIR is '/mymodule' meaning we search files into '/mymodule/langs/xx_XX'
86  if (!in_array($newdir, $this->dir)) {
87  $more['module_'.$i] = $newdir;
88  $i++; // We add the forced dir into the array $more. Just after, we add entries into $more to list of lang dir $this->dir.
89  }
90  }
91  $this->dir = array_merge($more, $this->dir); // Forced dir ($more) are before standard dirs ($this->dir)
92  }
93 
94  $this->origlang = $srclang;
95 
96  if (empty($srclang) || $srclang == 'auto') {
97  // $_SERVER['HTTP_ACCEPT_LANGUAGE'] can be 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,it;q=0.6' but can contains also malicious content
98  $langpref = empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? '' : $_SERVER['HTTP_ACCEPT_LANGUAGE'];
99  $langpref = preg_replace("/;([^,]*)/i", "", $langpref); // Remove the 'q=x.y,' part
100  $langpref = str_replace("-", "_", $langpref);
101  $langlist = preg_split("/[;,]/", $langpref);
102  $codetouse = preg_replace('/[^_a-zA-Z]/', '', $langlist[0]);
103  } else {
104  $codetouse = $srclang;
105  }
106 
107  // We redefine $srclang
108  $langpart = explode("_", $codetouse);
109  //print "Short code before _ : ".$langpart[0].' / Short code after _ : '.$langpart[1].'<br>';
110  if (!empty($langpart[1])) { // If it's for a codetouse that is a long code xx_YY
111  // Array force long code from first part, even if long code is defined
112  $longforshort = array('ar'=>'ar_SA');
113  $longforshortexcep = array('ar_EG');
114  if (isset($longforshort[strtolower($langpart[0])]) && !in_array($codetouse, $longforshortexcep)) {
115  $srclang = $longforshort[strtolower($langpart[0])];
116  } elseif (!is_numeric($langpart[1])) { // Second part YY may be a numeric with some Chrome browser
117  $srclang = strtolower($langpart[0])."_".strtoupper($langpart[1]);
118  $longforlong = array('no_nb'=>'nb_NO');
119  if (isset($longforlong[strtolower($srclang)])) {
120  $srclang = $longforlong[strtolower($srclang)];
121  }
122  } else {
123  $srclang = strtolower($langpart[0])."_".strtoupper($langpart[0]);
124  }
125  } else { // If it's for a codetouse that is a short code xx
126  // Array to convert short lang code into long code.
127  $longforshort = array(
128  'am'=>'am_ET', 'ar'=>'ar_SA', 'bn'=>'bn_DB', 'el'=>'el_GR', 'ca'=>'ca_ES', 'cs'=>'cs_CZ', 'en'=>'en_US', 'fa'=>'fa_IR',
129  'gl'=>'gl_ES', 'he'=>'he_IL', 'hi'=>'hi_IN', 'ja'=>'ja_JP',
130  'ka'=>'ka_GE', 'km'=>'km_KH', 'kn'=>'kn_IN', 'ko'=>'ko_KR', 'lo'=>'lo_LA', 'nb'=>'nb_NO', 'no'=>'nb_NO', 'ne'=>'ne_NP',
131  'sl'=>'sl_SI', 'sq'=>'sq_AL', 'sr'=>'sr_RS', 'sv'=>'sv_SE', 'uk'=>'uk_UA', 'vi'=>'vi_VN', 'zh'=>'zh_CN'
132  );
133  if (isset($longforshort[strtolower($langpart[0])])) {
134  $srclang = $longforshort[strtolower($langpart[0])];
135  } elseif (!empty($langpart[0])) {
136  $srclang = strtolower($langpart[0])."_".strtoupper($langpart[0]);
137  } else {
138  $srclang = 'en_US';
139  }
140  }
141 
142  $this->defaultlang = $srclang;
143  $this->shortlang = substr($srclang, 0, 2);
144  //print 'this->defaultlang='.$this->defaultlang;
145  }
146 
147 
155  public function getDefaultLang($mode = 0)
156  {
157  if (empty($mode)) {
158  return $this->defaultlang;
159  } else {
160  return substr($this->defaultlang, 0, 2);
161  }
162  }
163 
164 
171  public function loadLangs($domains)
172  {
173  foreach ($domains as $domain) {
174  $this->load($domain);
175  }
176  }
177 
199  public function load($domain, $alt = 0, $stopafterdirection = 0, $forcelangdir = '', $loadfromfileonly = 0, $forceloadifalreadynotfound = 0)
200  {
201  global $conf, $db;
202 
203  //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
204 
205  // Check parameters
206  if (empty($domain)) {
207  dol_print_error('', get_class($this)."::Load ErrorWrongParameters");
208  return -1;
209  }
210  if ($this->defaultlang === 'none_NONE') {
211  return 0; // Special language code to not translate keys
212  }
213 
214 
215  // Load $this->tab_translate[] from database
216  if (empty($loadfromfileonly) && count($this->tab_translate) == 0) {
217  $this->loadFromDatabase($db); // No translation was never loaded yet, so we load database.
218  }
219 
220 
221  $newdomain = $domain;
222  $modulename = '';
223 
224  // Search if a module directory name is provided into lang file name
225  $regs = array();
226  if (preg_match('/^([^@]+)@([^@]+)$/i', $domain, $regs)) {
227  $newdomain = $regs[1];
228  $modulename = $regs[2];
229  }
230 
231  // Check cache
232  if (!empty($this->_tab_loaded[$newdomain])
233  && ($this->_tab_loaded[$newdomain] != 2 || empty($forceloadifalreadynotfound))) { // File already loaded and found and not forced for this domain
234  //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
235  return 0;
236  }
237 
238  $fileread = 0;
239  $langofdir = (empty($forcelangdir) ? $this->defaultlang : $forcelangdir);
240 
241  // Redefine alt
242  $langarray = explode('_', $langofdir);
243  if ($alt < 1 && isset($langarray[1]) && (strtolower($langarray[0]) == strtolower($langarray[1]) || in_array(strtolower($langofdir), array('el_gr')))) {
244  $alt = 1;
245  }
246  if ($alt < 2 && strtolower($langofdir) == 'en_us') {
247  $alt = 2;
248  }
249 
250  if (empty($langofdir)) { // This may occurs when load is called without setting the language and without providing a value for forcelangdir
251  dol_syslog("Error: ".get_class($this)."::load was called for domain=".$domain." but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
252  return -1;
253  }
254 
255  foreach ($this->dir as $searchdir) {
256  // Directory of translation files
257  $file_lang = $searchdir.($modulename ? '/'.$modulename : '')."/langs/".$langofdir."/".$newdomain.".lang";
258  $file_lang_osencoded = dol_osencode($file_lang);
259 
260  $filelangexists = is_file($file_lang_osencoded);
261 
262  //dol_syslog(get_class($this).'::Load Try to read for alt='.$alt.' langofdir='.$langofdir.' domain='.$domain.' newdomain='.$newdomain.' modulename='.$modulename.' file_lang='.$file_lang." => filelangexists=".$filelangexists);
263  //print 'Try to read for alt='.$alt.' langofdir='.$langofdir.' domain='.$domain.' newdomain='.$newdomain.' modulename='.$modulename.' this->_tab_loaded[newdomain]='.$this->_tab_loaded[$newdomain].' file_lang='.$file_lang." => filelangexists=".$filelangexists."\n";
264 
265  if ($filelangexists) {
266  // TODO Move cache read out of loop on dirs or at least filelangexists
267  $found = false;
268 
269  // Enable caching of lang file in memory (not by default)
270  $usecachekey = '';
271  // Using a memcached server
272  if (!empty($conf->memcached->enabled) && !empty($conf->global->MEMCACHED_SERVER)) {
273  $usecachekey = $newdomain.'_'.$langofdir.'_'.md5($file_lang); // Should not contains special chars
274  } elseif (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) {
275  // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
276  $usecachekey = $newdomain;
277  }
278 
279  if ($usecachekey) {
280  //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
281  //global $aaa; $aaa+=1;
282  //print $aaa." ".$usecachekey."\n";
283  require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php';
284  $tmparray = dol_getcache($usecachekey);
285  if (is_array($tmparray) && count($tmparray)) {
286  $this->tab_translate += $tmparray; // Faster than array_merge($tmparray,$this->tab_translate). Note: If a value already exists into tab_translate, value into tmparaay is not added.
287  //print $newdomain."\n";
288  //var_dump($this->tab_translate);
289  if ($alt == 2) {
290  $fileread = 1;
291  }
292  $found = true; // Found in dolibarr PHP cache
293  }
294  }
295 
296  if (!$found) {
297  if ($fp = @fopen($file_lang, "rt")) {
298  if ($usecachekey) {
299  $tabtranslatedomain = array(); // To save lang content in cache
300  }
301 
307  while ($line = fscanf($fp, "%[^= ]%*[ =]%[^\n\r]")) {
308  if (isset($line[1])) {
309  list($key, $value) = $line;
310  //if ($domain == 'orders') print "Domain=$domain, found a string for $tab[0] with value $tab[1]. Currently in cache ".$this->tab_translate[$key]."<br>";
311  //if ($key == 'Order') print "Domain=$domain, found a string for key=$key=$tab[0] with value $tab[1]. Currently in cache ".$this->tab_translate[$key]."<br>";
312  if (empty($this->tab_translate[$key])) { // If translation was already found, we must not continue, even if MAIN_FORCELANGDIR is set (MAIN_FORCELANGDIR is to replace lang dir, not to overwrite entries)
313  if ($key == 'DIRECTION') { // This is to declare direction of language
314  if ($alt < 2 || empty($this->tab_translate[$key])) { // We load direction only for primary files or if not yet loaded
315  $this->tab_translate[$key] = $value;
316  if ($stopafterdirection) {
317  break; // We do not save tab if we stop after DIRECTION
318  } elseif ($usecachekey) {
319  $tabtranslatedomain[$key] = $value;
320  }
321  }
322  } elseif ($key[0] == '#') {
323  continue;
324  } else {
325  // Convert some strings: Parse and render carriage returns. Also, change '\\s' into '\s' because transifex sync pull the string '\s' into string '\\s'
326  $this->tab_translate[$key] = str_replace(array('\\n', '\\\\s'), array("\n", '\s'), $value);
327  if ($usecachekey) {
328  $tabtranslatedomain[$key] = $value;
329  } // To save lang content in cache
330  }
331  }
332  }
333  }
334  fclose($fp);
335  $fileread = 1;
336 
337  // TODO Move cache write out of loop on dirs
338  // To save lang content for usecachekey into cache
339  if ($usecachekey && count($tabtranslatedomain)) {
340  $ressetcache = dol_setcache($usecachekey, $tabtranslatedomain);
341  if ($ressetcache < 0) {
342  $error = 'Failed to set cache for usecachekey='.$usecachekey.' result='.$ressetcache;
343  dol_syslog($error, LOG_ERR);
344  }
345  }
346 
347  if (empty($conf->global->MAIN_FORCELANGDIR)) {
348  break; // Break loop on each root dir. If a module has forced dir, we do not stop loop.
349  }
350  }
351  }
352  }
353  }
354 
355  // Now we complete with next file (fr_CA->fr_FR, es_MX->ex_ES, ...)
356  if ($alt == 0) {
357  // This function MUST NOT contains call to syslog
358  //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
359  $langofdir = strtolower($langarray[0]).'_'.strtoupper($langarray[0]);
360  if ($langofdir == 'el_EL') {
361  $langofdir = 'el_GR'; // main parent for el_CY is not 'el_EL' but 'el_GR'
362  }
363  if ($langofdir == 'ar_AR') {
364  $langofdir = 'ar_SA'; // main parent for ar_EG is not 'ar_AR' but 'ar_SA'
365  }
366  $this->load($domain, $alt + 1, $stopafterdirection, $langofdir);
367  }
368 
369  // Now we complete with reference file (en_US)
370  if ($alt == 1) {
371  // This function MUST NOT contains call to syslog
372  //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
373  $langofdir = 'en_US';
374  $this->load($domain, $alt + 1, $stopafterdirection, $langofdir);
375  }
376 
377  // We are in the pass of the reference file. No more files to scan to complete.
378  if ($alt == 2) {
379  if ($fileread) {
380  $this->_tab_loaded[$newdomain] = 1; // Set domain file as found so loaded
381  }
382 
383  if (empty($this->_tab_loaded[$newdomain])) {
384  $this->_tab_loaded[$newdomain] = 2; // Set this file as not found
385  }
386  }
387 
388  // This part is deprecated and replaced with table llx_overwrite_trans
389  // Kept for backward compatibility.
390  if (empty($loadfromfileonly)) {
391  $overwritekey = 'MAIN_OVERWRITE_TRANS_'.$this->defaultlang;
392  if (!empty($conf->global->$overwritekey)) { // Overwrite translation with key1:newstring1,key2:newstring2
393  // Overwrite translation with param MAIN_OVERWRITE_TRANS_xx_XX
394  $tmparray = explode(',', $conf->global->$overwritekey);
395  foreach ($tmparray as $tmp) {
396  $tmparray2 = explode(':', $tmp);
397  if (!empty($tmparray2[1])) {
398  $this->tab_translate[$tmparray2[0]] = $tmparray2[1];
399  }
400  }
401  }
402  }
403 
404  // Check to be sure that SeparatorDecimal differs from SeparatorThousand
405  if (!empty($this->tab_translate["SeparatorDecimal"]) && !empty($this->tab_translate["SeparatorThousand"])
406  && $this->tab_translate["SeparatorDecimal"] == $this->tab_translate["SeparatorThousand"]) {
407  $this->tab_translate["SeparatorThousand"] = '';
408  }
409 
410  return 1;
411  }
412 
425  public function loadFromDatabase($db)
426  {
427  global $conf;
428 
429  $domain = 'database';
430 
431  // Check parameters
432  if (empty($db)) {
433  return 0; // Database handler can't be used
434  }
435 
436  //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
437 
438  $newdomain = $domain;
439 
440  // Check cache
441  if (!empty($this->_tab_loaded[$newdomain])) { // File already loaded for this domain 'database'
442  //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
443  return 0;
444  }
445 
446  $this->_tab_loaded[$newdomain] = 1; // We want to be sure this function is called once only for domain 'database'
447 
448  $fileread = 0;
449  $langofdir = $this->defaultlang;
450 
451  if (empty($langofdir)) { // This may occurs when load is called without setting the language and without providing a value for forcelangdir
452  dol_syslog("Error: ".get_class($this)."::loadFromDatabase was called but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
453  return -1;
454  }
455 
456  // TODO Move cache read out of loop on dirs or at least filelangexists
457  $found = false;
458 
459  // Enable caching of lang file in memory (not by default)
460  $usecachekey = '';
461  // Using a memcached server
462  if (!empty($conf->memcached->enabled) && !empty($conf->global->MEMCACHED_SERVER)) {
463  $usecachekey = $newdomain.'_'.$langofdir; // Should not contains special chars
464  } elseif (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) {
465  // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
466  $usecachekey = $newdomain;
467  }
468 
469  if ($usecachekey) {
470  //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
471  //global $aaa; $aaa+=1;
472  //print $aaa." ".$usecachekey."\n";
473  require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php';
474  $tmparray = dol_getcache($usecachekey);
475  if (is_array($tmparray) && count($tmparray)) {
476  $this->tab_translate += $tmparray; // Faster than array_merge($tmparray,$this->tab_translate). Note: If a value already exists into tab_translate, value into tmparaay is not added.
477  //print $newdomain."\n";
478  //var_dump($this->tab_translate);
479  $fileread = 1;
480  $found = true; // Found in dolibarr PHP cache
481  }
482  }
483 
484  if (!$found && !empty($conf->global->MAIN_ENABLE_OVERWRITE_TRANSLATION)) {
485  // Overwrite translation with database read
486  $sql = "SELECT transkey, transvalue FROM ".$db->prefix()."overwrite_trans where lang='".$db->escape($this->defaultlang)."' OR lang IS NULL";
487  $sql .= " AND entity IN (0, ".getEntity('overwrite_trans').")";
488  $sql .= $db->order("lang", "DESC");
489  $resql = $db->query($sql);
490 
491  if ($resql) {
492  $num = $db->num_rows($resql);
493  if ($num) {
494  if ($usecachekey) {
495  $tabtranslatedomain = array(); // To save lang content in cache
496  }
497 
498  $i = 0;
499  while ($i < $num) { // Ex: Need 225ms for all fgets on all lang file for Third party page. Same speed than file_get_contents
500  $obj = $db->fetch_object($resql);
501 
502  $key = $obj->transkey;
503  $value = $obj->transvalue;
504 
505  //print "Domain=$domain, found a string for $tab[0] with value $tab[1]<br>";
506  if (empty($this->tab_translate[$key])) { // If translation was already found, we must not continue, even if MAIN_FORCELANGDIR is set (MAIN_FORCELANGDIR is to replace lang dir, not to overwrite entries)
507  // Convert some strings: Parse and render carriage returns. Also, change '\\s' int '\s' because transifex sync pull the string '\s' into string '\\s'
508  $this->tab_translate[$key] = str_replace(array('\\n', '\\\\s'), array("\n", '\s'), $value);
509 
510  if ($usecachekey) {
511  $tabtranslatedomain[$key] = $value; // To save lang content in cache
512  }
513  }
514 
515  $i++;
516  }
517 
518  $fileread = 1;
519 
520  // TODO Move cache write out of loop on dirs
521  // To save lang content for usecachekey into cache
522  if ($usecachekey && count($tabtranslatedomain)) {
523  $ressetcache = dol_setcache($usecachekey, $tabtranslatedomain);
524  if ($ressetcache < 0) {
525  $error = 'Failed to set cache for usecachekey='.$usecachekey.' result='.$ressetcache;
526  dol_syslog($error, LOG_ERR);
527  }
528  }
529  }
530  } else {
531  dol_print_error($db);
532  }
533  }
534 
535  if ($fileread) {
536  $this->_tab_loaded[$newdomain] = 1; // Set domain file as loaded
537  }
538 
539  if (empty($this->_tab_loaded[$newdomain])) {
540  $this->_tab_loaded[$newdomain] = 2; // Mark this case as not found (no lines found for language)
541  }
542 
543  return 1;
544  }
545 
552  public function isLoaded($domain)
553  {
554  return $this->_tab_loaded[$domain];
555  }
556 
568  private function getTradFromKey($key)
569  {
570  global $conf, $db;
571 
572  if (!is_string($key)) {
573  //xdebug_print_function_stack('ErrorBadValueForParamNotAString');
574  return 'ErrorBadValueForParamNotAString'; // Avoid multiple errors with code not using function correctly.
575  }
576 
577  $newstr = $key;
578  $reg = array();
579  if (preg_match('/^Civility([0-9A-Z]+)$/i', $key, $reg)) {
580  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_civility', 'code', 'label');
581  } elseif (preg_match('/^Currency([A-Z][A-Z][A-Z])$/i', $key, $reg)) {
582  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_currencies', 'code_iso', 'label');
583  } elseif (preg_match('/^SendingMethod([0-9A-Z]+)$/i', $key, $reg)) {
584  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_shipment_mode', 'code', 'libelle');
585  } elseif (preg_match('/^PaymentType(?:Short)?([0-9A-Z]+)$/i', $key, $reg)) {
586  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_paiement', 'code', 'libelle', '', 1);
587  } elseif (preg_match('/^OppStatus([0-9A-Z]+)$/i', $key, $reg)) {
588  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_lead_status', 'code', 'label');
589  } elseif (preg_match('/^OrderSource([0-9A-Z]+)$/i', $key, $reg)) {
590  // TODO OrderSourceX must be replaced with content of table llx_c_input_reason or llx_c_input_method
591  //$newstr=$this->getLabelFromKey($db,$reg[1],'llx_c_input_reason','code','label');
592  }
593 
594  /* Disabled. There is too many cases where translation of $newstr is not defined is normal (like when output with setEventMessage an already translated string)
595  if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2)
596  {
597  dol_syslog(__METHOD__." MAIN_FEATURES_LEVEL=DEVELOP: missing translation for key '".$newstr."' in ".$_SERVER["PHP_SELF"], LOG_DEBUG);
598  }*/
599 
600  return $newstr;
601  }
602 
603 
617  public function trans($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $maxsize = 0)
618  {
619  global $conf;
620 
621  if (!empty($this->tab_translate[$key])) { // Translation is available
622  $str = $this->tab_translate[$key];
623 
624  // Make some string replacement after translation
625  $replacekey = 'MAIN_REPLACE_TRANS_'.$this->defaultlang;
626  if (!empty($conf->global->$replacekey)) { // Replacement translation variable with string1:newstring1;string2:newstring2
627  $tmparray = explode(';', $conf->global->$replacekey);
628  foreach ($tmparray as $tmp) {
629  $tmparray2 = explode(':', $tmp);
630  $str = preg_replace('/'.preg_quote($tmparray2[0]).'/', $tmparray2[1], $str);
631  }
632  }
633 
634  // We replace some HTML tags by __xx__ to avoid having them encoded by htmlentities because
635  // we want to keep '"' '<b>' '</b>' '<strong' '</strong>' '<a ' '</a>' '<br>' '< ' '<span' '</span>' that are reliable HTML tags inside translation strings.
636  $str = str_replace(
637  array('"', '<b>', '</b>', '<u>', '</u>', '<i', '</i>', '<center>', '</center>', '<strong>', '</strong>', '<a ', '</a>', '<br>', '<span', '</span>', '< ', '>'), // We accept '< ' but not '<'. We can accept however '>'
638  array('__quot__', '__tagb__', '__tagbend__', '__tagu__', '__taguend__', '__tagi__', '__tagiend__', '__tagcenter__', '__tagcenterend__', '__tagb__', '__tagbend__', '__taga__', '__tagaend__', '__tagbr__', '__tagspan__', '__tagspanend__', '__ltspace__', '__gt__'),
639  $str
640  );
641 
642  if (strpos($key, 'Format') !== 0) {
643  try {
644  $str = sprintf($str, $param1, $param2, $param3, $param4); // Replace %s and %d except for FormatXXX strings.
645  } catch (Exception $e) {
646  // No exception managed
647  }
648  }
649 
650  // Crypt string into HTML
651  $str = htmlentities($str, ENT_COMPAT, $this->charset_output); // Do not convert simple quotes in translation (strings in html are embraced by "). Use dol_escape_htmltag around text in HTML content
652 
653  // Restore reliable HTML tags into original translation string
654  $str = str_replace(
655  array('__quot__', '__tagb__', '__tagbend__', '__tagu__', '__taguend__', '__tagi__', '__tagiend__', '__tagcenter__', '__tagcenterend__', '__taga__', '__tagaend__', '__tagbr__', '__tagspan__', '__tagspanend__', '__ltspace__', '__gt__'),
656  array('"', '<b>', '</b>', '<u>', '</u>', '<i', '</i>', '<center>', '</center>', '<a ', '</a>', '<br>', '<span', '</span>', '< ', '>'),
657  $str
658  );
659 
660  if ($maxsize) {
661  $str = dol_trunc($str, $maxsize);
662  }
663 
664  return $str;
665  } else { // Translation is not available
666  //if ($key[0] == '$') { return dol_eval($key, 1, 1, '1'); }
667  return $this->getTradFromKey($key);
668  }
669  }
670 
671 
686  public function transnoentities($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $param5 = '')
687  {
688  return $this->convToOutputCharset($this->transnoentitiesnoconv($key, $param1, $param2, $param3, $param4, $param5));
689  }
690 
691 
707  public function transnoentitiesnoconv($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $param5 = '')
708  {
709  global $conf;
710 
711  if (!empty($this->tab_translate[$key])) { // Translation is available
712  $str = $this->tab_translate[$key];
713 
714  // Make some string replacement after translation
715  $replacekey = 'MAIN_REPLACE_TRANS_'.$this->defaultlang;
716  if (!empty($conf->global->$replacekey)) { // Replacement translation variable with string1:newstring1;string2:newstring2
717  $tmparray = explode(';', $conf->global->$replacekey);
718  foreach ($tmparray as $tmp) {
719  $tmparray2 = explode(':', $tmp);
720  $str = preg_replace('/'.preg_quote($tmparray2[0]).'/', $tmparray2[1], $str);
721  }
722  }
723 
724  if (!preg_match('/^Format/', $key)) {
725  //print $str;
726  $str = sprintf($str, $param1, $param2, $param3, $param4, $param5); // Replace %s and %d except for FormatXXX strings.
727  }
728 
729  return $str;
730  } else {
731  /*if ($key[0] == '$') {
732  return dol_eval($key, 1, 1, '1');
733  }*/
734  return $this->getTradFromKey($key);
735  }
736  }
737 
738 
747  public function transcountry($str, $countrycode)
748  {
749  if (!empty($this->tab_translate["$str$countrycode"])) {
750  return $this->trans("$str$countrycode");
751  } else {
752  return $this->trans($str);
753  }
754  }
755 
756 
765  public function transcountrynoentities($str, $countrycode)
766  {
767  if (!empty($this->tab_translate["$str$countrycode"])) {
768  return $this->transnoentities("$str$countrycode");
769  } else {
770  return $this->transnoentities($str);
771  }
772  }
773 
774 
782  public function convToOutputCharset($str, $pagecodefrom = 'UTF-8')
783  {
784  if ($pagecodefrom == 'ISO-8859-1' && $this->charset_output == 'UTF-8') {
785  $str = utf8_encode($str);
786  }
787  if ($pagecodefrom == 'UTF-8' && $this->charset_output == 'ISO-8859-1') {
788  $str = utf8_decode(str_replace('€', chr(128), $str));
789  }
790  return $str;
791  }
792 
793 
794  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
804  public function get_available_languages($langdir = DOL_DOCUMENT_ROOT, $maxlength = 0, $usecode = 0, $mainlangonly = 0)
805  {
806  // phpcs:enable
807  global $conf;
808 
809  $this->load("languages");
810 
811  // We scan directory langs to detect available languages
812  $handle = opendir($langdir."/langs");
813  $langs_available = array();
814  while ($dir = trim(readdir($handle))) {
815  $regs = array();
816  if (preg_match('/^([a-z]+)_([A-Z]+)/i', $dir, $regs)) {
817  // We must keep only main languages
818  if ($mainlangonly) {
819  $arrayofspecialmainlanguages = array(
820  'en'=>'en_US',
821  'am'=>'am_ET',
822  'ar'=>'ar_SA',
823  'bn'=>'bn_DB',
824  'bs'=>'bs_BA',
825  'ca'=>'ca_ES',
826  'cs'=>'cs_CZ',
827  'da'=>'da_DK',
828  'et'=>'et_EE',
829  'el'=>'el_GR',
830  'eu'=>'eu_ES',
831  'fa'=>'fa_IR',
832  'he'=>'he_IL',
833  'ka'=>'ka_GE',
834  'km'=>'km_KH',
835  'kn'=>'kn_IN',
836  'ko'=>'ko_KR',
837  'ja'=>'ja_JP',
838  'lo'=>'lo_LA',
839  'nb'=>'nb_NO',
840  'sq'=>'sq_AL',
841  'sr'=>'sr_RS',
842  'sv'=>'sv_SE',
843  'sl'=>'sl_SI',
844  'uk'=>'uk_UA',
845  'vi'=>'vi_VN',
846  'zh'=>'zh_CN'
847  );
848  if (strtolower($regs[1]) != strtolower($regs[2]) && !in_array($dir, $arrayofspecialmainlanguages)) {
849  continue;
850  }
851  }
852  // We must keep only languages into MAIN_LANGUAGES_ALLOWED
853  if (!empty($conf->global->MAIN_LANGUAGES_ALLOWED) && !in_array($dir, explode(',', $conf->global->MAIN_LANGUAGES_ALLOWED))) {
854  continue;
855  }
856 
857  if ($usecode == 2) {
858  $langs_available[$dir] = $dir;
859  }
860 
861  if ($usecode == 1 || !empty($conf->global->MAIN_SHOW_LANGUAGE_CODE)) {
862  $langs_available[$dir] = $dir.': '.dol_trunc($this->trans('Language_'.$dir), $maxlength);
863  } else {
864  $langs_available[$dir] = $this->trans('Language_'.$dir);
865  }
866  if ($mainlangonly) {
867  $langs_available[$dir] = str_replace(' (United States)', '', $langs_available[$dir]);
868  }
869  }
870  }
871  return $langs_available;
872  }
873 
874 
875  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
883  public function file_exists($filename, $searchalt = 0)
884  {
885  // phpcs:enable
886  // Test si fichier dans repertoire de la langue
887  foreach ($this->dir as $searchdir) {
888  if (is_readable(dol_osencode($searchdir."/langs/".$this->defaultlang."/".$filename))) {
889  return true;
890  }
891 
892  if ($searchalt) {
893  // Test si fichier dans repertoire de la langue alternative
894  if ($this->defaultlang != "en_US") {
895  $filenamealt = $searchdir."/langs/en_US/".$filename;
896  }
897  //else $filenamealt = $searchdir."/langs/fr_FR/".$filename;
898  if (is_readable(dol_osencode($filenamealt))) {
899  return true;
900  }
901  }
902  }
903 
904  return false;
905  }
906 
907 
919  public function getLabelFromNumber($number, $isamount = '')
920  {
921  global $conf;
922 
923  $newnumber = $number;
924 
925  $dirsubstitutions = array_merge(array(), $conf->modules_parts['substitutions']);
926  foreach ($dirsubstitutions as $reldir) {
927  $dir = dol_buildpath($reldir, 0);
928  $newdir = dol_osencode($dir);
929 
930  // Check if directory exists
931  if (!is_dir($newdir)) {
932  continue; // We must not use dol_is_dir here, function may not be loaded
933  }
934 
935  $fonc = 'numberwords';
936  if (file_exists($newdir.'/functions_'.$fonc.'.lib.php')) {
937  include_once $newdir.'/functions_'.$fonc.'.lib.php';
938  if (function_exists('numberwords_getLabelFromNumber')) {
939  $newnumber = numberwords_getLabelFromNumber($this, $number, $isamount);
940  break;
941  }
942  }
943  }
944 
945  return $newnumber;
946  }
947 
948 
964  public function getLabelFromKey($db, $key, $tablename, $fieldkey, $fieldlabel, $keyforselect = '', $filteronentity = 0)
965  {
966  // If key empty
967  if ($key == '') {
968  return '';
969  }
970  // Test should be useless because the 3 variables are never set from user input but we keep it in case of.
971  if (preg_match('/[^0-9A-Z_]/i', $tablename) || preg_match('/[^0-9A-Z_]/i', $fieldkey) || preg_match('/[^0-9A-Z_]/i', $fieldlabel)) {
972  $this->error = 'Bad value for parameter tablename, fieldkey or fieldlabel';
973  return -1;
974  }
975 
976  //print 'param: '.$key.'-'.$keydatabase.'-'.$this->trans($key); exit;
977 
978  // Check if a translation is available (Note: this can call getTradFromKey that can call getLabelFromKey)
979  $tmp = $this->transnoentitiesnoconv($key);
980  if ($tmp != $key && $tmp != 'ErrorBadValueForParamNotAString') {
981  return $tmp; // Found in language array
982  }
983 
984  // Check in cache
985  if (isset($this->cache_labels[$tablename][$key])) { // Can be defined to 0 or ''
986  return $this->cache_labels[$tablename][$key]; // Found in cache
987  }
988 
989  // Not found in loaded language file nor in cache. So we will take the label into database.
990  $sql = "SELECT ".$fieldlabel." as label";
991  $sql .= " FROM ".$db->prefix().$tablename;
992  $sql .= " WHERE ".$fieldkey." = '".$db->escape($keyforselect ? $keyforselect : $key)."'";
993  if ($filteronentity) {
994  $sql .= " AND entity IN (".getEntity($tablename).')';
995  }
996  dol_syslog(get_class($this).'::getLabelFromKey', LOG_DEBUG);
997  $resql = $db->query($sql);
998  if ($resql) {
999  $obj = $db->fetch_object($resql);
1000  if ($obj) {
1001  $this->cache_labels[$tablename][$key] = $obj->label;
1002  } else {
1003  $this->cache_labels[$tablename][$key] = $key;
1004  }
1005 
1006  $db->free($resql);
1007  return $this->cache_labels[$tablename][$key];
1008  } else {
1009  $this->error = $db->lasterror();
1010  return -1;
1011  }
1012  }
1013 
1014 
1024  public function getCurrencyAmount($currency_code, $amount)
1025  {
1026  $symbol = $this->getCurrencySymbol($currency_code);
1027 
1028  if (in_array($currency_code, array('USD'))) {
1029  return $symbol.$amount;
1030  } else {
1031  return $amount.$symbol;
1032  }
1033  }
1034 
1043  public function getCurrencySymbol($currency_code, $forceloadall = 0)
1044  {
1045  $currency_sign = ''; // By default return iso code
1046 
1047  if (function_exists("mb_convert_encoding")) {
1048  $this->loadCacheCurrencies($forceloadall ? '' : $currency_code);
1049 
1050  if (isset($this->cache_currencies[$currency_code]) && !empty($this->cache_currencies[$currency_code]['unicode']) && is_array($this->cache_currencies[$currency_code]['unicode'])) {
1051  foreach ($this->cache_currencies[$currency_code]['unicode'] as $unicode) {
1052  $currency_sign .= mb_convert_encoding("&#{$unicode};", "UTF-8", 'HTML-ENTITIES');
1053  }
1054  }
1055  }
1056 
1057  return ($currency_sign ? $currency_sign : $currency_code);
1058  }
1059 
1066  public function loadCacheCurrencies($currency_code)
1067  {
1068  global $db;
1069 
1070  if ($this->cache_currencies_all_loaded) {
1071  return 0; // Cache already loaded for all
1072  }
1073  if (!empty($currency_code) && isset($this->cache_currencies[$currency_code])) {
1074  return 0; // Cache already loaded for the currency
1075  }
1076 
1077  $sql = "SELECT code_iso, label, unicode";
1078  $sql .= " FROM ".$db->prefix()."c_currencies";
1079  $sql .= " WHERE active = 1";
1080  if (!empty($currency_code)) {
1081  $sql .= " AND code_iso = '".$db->escape($currency_code)."'";
1082  }
1083  //$sql.= " ORDER BY code_iso ASC"; // Not required, a sort is done later
1084 
1085  dol_syslog(get_class($this).'::loadCacheCurrencies', LOG_DEBUG);
1086  $resql = $db->query($sql);
1087  if ($resql) {
1088  $this->load("dict");
1089  $label = array();
1090  if (!empty($currency_code)) {
1091  foreach ($this->cache_currencies as $key => $val) {
1092  $label[$key] = $val['label']; // Label in already loaded cache
1093  }
1094  }
1095 
1096  $num = $db->num_rows($resql);
1097  $i = 0;
1098  while ($i < $num) {
1099  $obj = $db->fetch_object($resql);
1100  if ($obj) {
1101  // If a translation exists, we use it lese we use the default label
1102  $this->cache_currencies[$obj->code_iso]['label'] = ($obj->code_iso && $this->trans("Currency".$obj->code_iso) != "Currency".$obj->code_iso ? $this->trans("Currency".$obj->code_iso) : ($obj->label != '-' ? $obj->label : ''));
1103  $this->cache_currencies[$obj->code_iso]['unicode'] = (array) json_decode((empty($obj->unicode) ? '' : $obj->unicode), true);
1104  $label[$obj->code_iso] = $this->cache_currencies[$obj->code_iso]['label'];
1105  }
1106  $i++;
1107  }
1108  if (empty($currency_code)) {
1109  $this->cache_currencies_all_loaded = true;
1110  }
1111  //print count($label).' '.count($this->cache_currencies);
1112 
1113  // Resort cache
1114  array_multisort($label, SORT_ASC, $this->cache_currencies);
1115  //var_dump($this->cache_currencies); $this->cache_currencies is now sorted onto label
1116  return $num;
1117  } else {
1118  dol_print_error($db);
1119  return -1;
1120  }
1121  }
1122 
1123  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1131  {
1132  // phpcs:enable
1133  $substitutionarray = array();
1134 
1135  foreach ($this->tab_translate as $code => $label) {
1136  $substitutionarray['lang_'.$code] = $label;
1137  $substitutionarray['__('.$code.')__'] = $label;
1138  }
1139 
1140  return $substitutionarray;
1141  }
1142 }
Translate\getLabelFromKey
getLabelFromKey($db, $key, $tablename, $fieldkey, $fieldlabel, $keyforselect='', $filteronentity=0)
Return a label for a key.
Definition: translate.class.php:964
Translate\getCurrencyAmount
getCurrencyAmount($currency_code, $amount)
Return a currency code into its symbol.
Definition: translate.class.php:1024
dol_setcache
dol_setcache($memoryid, $data, $expire=0)
Save data into a memory area shared by all users, all sessions on server.
Definition: memory.lib.php:68
Translate\get_available_languages
get_available_languages($langdir=DOL_DOCUMENT_ROOT, $maxlength=0, $usecode=0, $mainlangonly=0)
Return list of all available languages.
Definition: translate.class.php:804
dol_trunc
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
Definition: functions.lib.php:3950
Translate\getDefaultLang
getDefaultLang($mode=0)
Return active language code for current user It's an accessor for this->defaultlang.
Definition: translate.class.php:155
Translate\trans
trans($key, $param1='', $param2='', $param3='', $param4='', $maxsize=0)
Return text translated of text received as parameter (and encode it into HTML) If there is no match f...
Definition: translate.class.php:617
dol_getcache
dol_getcache($memoryid)
Read a memory area shared by all users, all sessions on server.
Definition: memory.lib.php:140
Translate\isLoaded
isLoaded($domain)
Get information with result of loading data for domain.
Definition: translate.class.php:552
dol_osencode
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
Definition: functions.lib.php:8875
$sql
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $sql
Social contributions to pay.
Definition: index.php:745
dol_print_error
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
Definition: functions.lib.php:4994
Translate\convToOutputCharset
convToOutputCharset($str, $pagecodefrom='UTF-8')
Convert a string into output charset (this->charset_output that should be defined to conf->file->char...
Definition: translate.class.php:782
Translate
Class to manage translations.
Definition: translate.class.php:30
dol_buildpath
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
Definition: functions.lib.php:1072
Translate\getTradFromKey
getTradFromKey($key)
Return translated value of key for special keys ("Currency...", "Civility...", ......
Definition: translate.class.php:568
Translate\get_translations_for_substitutions
get_translations_for_substitutions()
Return an array with content of all loaded translation keys (found into this->tab_translate) so we ge...
Definition: translate.class.php:1130
Exception
Translate\loadLangs
loadLangs($domains)
Load translation files.
Definition: translate.class.php:171
Translate\load
load($domain, $alt=0, $stopafterdirection=0, $forcelangdir='', $loadfromfileonly=0, $forceloadifalreadynotfound=0)
Load translation key-value for a particular file, into a memory array.
Definition: translate.class.php:199
Translate\setDefaultLang
setDefaultLang($srclang='en_US')
Set accessor for this->defaultlang.
Definition: translate.class.php:74
Translate\transnoentitiesnoconv
transnoentitiesnoconv($key, $param1='', $param2='', $param3='', $param4='', $param5='')
Return translated value of a text string If there is no match for this text, we look in alternative f...
Definition: translate.class.php:707
dol_syslog
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
Definition: functions.lib.php:1639
Translate\__construct
__construct($dir, $conf)
Constructor.
Definition: translate.class.php:55
Translate\transcountry
transcountry($str, $countrycode)
Return translation of a key depending on country.
Definition: translate.class.php:747
Translate\transnoentities
transnoentities($key, $param1='', $param2='', $param3='', $param4='', $param5='')
Return translated value of a text string If there is no match for this text, we look in alternative f...
Definition: translate.class.php:686
Translate\getLabelFromNumber
getLabelFromNumber($number, $isamount='')
Return full text translated to language label for a key.
Definition: translate.class.php:919
Translate\transcountrynoentities
transcountrynoentities($str, $countrycode)
Retourne la version traduite du texte passe en parametre complete du code pays.
Definition: translate.class.php:765
Translate\loadFromDatabase
loadFromDatabase($db)
Load translation key-value from database into a memory array.
Definition: translate.class.php:425
Translate\loadCacheCurrencies
loadCacheCurrencies($currency_code)
Load into the cache this->cache_currencies, all currencies.
Definition: translate.class.php:1066
Translate\getCurrencySymbol
getCurrencySymbol($currency_code, $forceloadall=0)
Return a currency code into its symbol.
Definition: translate.class.php:1043
Translate\file_exists
file_exists($filename, $searchalt=0)
Return if a filename $filename exists for current language (or alternate language)
Definition: translate.class.php:883