Test Failed
Push — master ( d56fde...b8c830 )
by Alxarafe
37:54
created

Translate::get_available_languages()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 29
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 6
nop 3
dl 0
loc 29
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/* Copyright (C) 2001      Eric Seigne         <[email protected]>
3
 * Copyright (C) 2004-2015 Destailleur Laurent <[email protected]>
4
 * Copyright (C) 2005-2010 Regis Houssin       <[email protected]>
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 <http://www.gnu.org/licenses/>.
18
 */
19
20
/**
21
 *   	\file       htdocs/core/class/translate.class.php
22
 *      \ingroup    core
23
 *		\brief      File for Tanslate class
24
 */
25
26
27
/**
28
 *		Class to manage translations
29
 */
30
class Translate
31
{
32
	public $dir;                          // Directories that contains /langs subdirectory
33
34
	public $defaultlang;                  // Current language for current user
35
	public $charset_output='UTF-8';       // Codage used by "trans" method outputs
36
37
	public $tab_translate=array();        // Array of all translations key=>value
38
	private $_tab_loaded=array();         // Array to store result after loading each language file
39
40
	public $cache_labels=array();         // Cache for labels return by getLabelFromKey method
41
	public $cache_currencies=array();     // Cache to store currency symbols
42
    private $cache_currencies_all_loaded=false;
43
44
45
	/**
46
	 *	Constructor
47
	 *
48
	 *  @param	string	$dir            Force directory that contains /langs subdirectory (value is sometimes '..' like into install/* pages or support/* pages). Use '' by default.
49
	 *  @param  Conf	$conf			Object with Dolibarr configuration
50
	 */
51
	function __construct($dir, $conf)
52
	{
53
		if (! empty($conf->file->character_set_client)) $this->charset_output=$conf->file->character_set_client;	// If charset output is forced
54
		if ($dir) $this->dir=array($dir);
55
		else $this->dir=$conf->file->dol_document_root;
56
	}
57
58
59
	/**
60
	 *  Set accessor for this->defaultlang
61
	 *
62
	 *  @param	string	$srclang     	Language to use. If '' or 'auto', we use browser lang.
63
	 *  @return	void
64
	 */
65
	function setDefaultLang($srclang='en_US')
66
	{
67
		global $conf;
68
69
		//dol_syslog(get_class($this)."::setDefaultLang srclang=".$srclang,LOG_DEBUG);
70
71
		// If a module ask to force a priority on langs directories (to use its own lang files)
72
		if (! empty($conf->global->MAIN_FORCELANGDIR))
73
		{
74
			$more=array();
75
			$i=0;
76
			foreach($conf->file->dol_document_root as $dir)
77
			{
78
				$newdir=$dir.$conf->global->MAIN_FORCELANGDIR;    // For example $conf->global->MAIN_FORCELANGDIR is '/mymodule' meaning we search files into '/mymodule/langs/xx_XX'
79
				if (! in_array($newdir,$this->dir))
80
				{
81
				    $more['module_'.$i]=$newdir; $i++;   // We add the forced dir into the array $more. Just after, we add entries into $more to list of lang dir $this->dir.
82
				}
83
			}
84
			$this->dir=array_merge($more,$this->dir);    // Forced dir ($more) are before standard dirs ($this->dir)
85
		}
86
87
		$this->origlang=$srclang;
88
89
		if (empty($srclang) || $srclang == 'auto')
90
		{
91
			$langpref=empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])?'':$_SERVER['HTTP_ACCEPT_LANGUAGE'];
92
			$langpref=preg_replace("/;([^,]*)/i","",$langpref);
93
			$langpref=str_replace("-","_",$langpref);
94
			$langlist=preg_split("/[;,]/",$langpref);
95
			$codetouse=$langlist[0];
96
		}
97
		else $codetouse=$srclang;
98
99
		// We redefine $srclang
100
		$langpart=explode("_",$codetouse);
101
		//print "Short code before _ : ".$langpart[0].' / Short code after _ : '.$langpart[1].'<br>';
102
		if (! empty($langpart[1]))	// If it's for a codetouse that is a long code xx_YY
103
		{
104
			// Array force long code from first part, even if long code is defined
105
			$longforshort=array('ar'=>'ar_SA');
106
			$longforshortexcep=array('ar_EG');
107
			if (isset($longforshort[strtolower($langpart[0])]) && ! in_array($codetouse, $longforshortexcep)) $srclang=$longforshort[strtolower($langpart[0])];
108
			else if (! is_numeric($langpart[1])) {		// Second part YY may be a numeric with some Chrome browser
109
				$srclang=strtolower($langpart[0])."_".strtoupper($langpart[1]);
110
				$longforlong=array('no_nb'=>'nb_NO');
111
				if (isset($longforlong[strtolower($srclang)])) $srclang=$longforlong[strtolower($srclang)];
112
			}
113
			else $srclang=strtolower($langpart[0])."_".strtoupper($langpart[0]);
114
		}
115
		else {						// If it's for a codetouse that is a short code xx
116
    	    // Array to convert short lang code into long code.
117
	        $longforshort=array('ar'=>'ar_SA', 'el'=>'el_GR', 'ca'=>'ca_ES', 'en'=>'en_US', 'nb'=>'nb_NO', 'no'=>'nb_NO');
118
			if (isset($longforshort[strtolower($langpart[0])])) $srclang=$longforshort[strtolower($langpart[0])];
119
			else if (! empty($langpart[0])) $srclang=strtolower($langpart[0])."_".strtoupper($langpart[0]);
120
			else $srclang='en_US';
121
		}
122
123
		$this->defaultlang=$srclang;
124
		//print 'this->defaultlang='.$this->defaultlang;
125
	}
126
127
128
	/**
129
	 *  Return active language code for current user
130
	 * 	It's an accessor for this->defaultlang
131
	 *
132
	 *  @param	int		$mode       0=Long language code, 1=Short language code (en, fr, es, ...)
133
	 *  @return string      		Language code used (en_US, en_AU, fr_FR, ...)
134
	 */
135
	function getDefaultLang($mode=0)
136
	{
137
	    if (empty($mode)) return $this->defaultlang;
138
	    else return substr($this->defaultlang,0,2);
139
	}
140
141
142
	/**
143
	 *  Load translation files.
144
     *
145
	 *  @param	array	$domains      		Array of lang files to load
146
	 *	@return	int							<0 if KO, 0 if already loaded or loading not required, >0 if OK
147
	 */
148
	function loadLangs($domains)
149
	{
150
	    foreach($domains as $domain)
151
	    {
152
	        $this->load($domain);
153
	    }
154
	}
155
156
	/**
157
	 *  Load translation key-value for a particular file, into a memory array.
158
	 *  If data for file already loaded, do nothing.
159
	 * 	All data in translation array are stored in UTF-8 format.
160
     *  tab_loaded is completed with $domain key.
161
     *  rule "we keep first entry found with we keep last entry found" so it is probably not what you want to do.
162
     *
163
     *  Value for hash are: 1:Loaded from disk, 2:Not found, 3:Loaded from cache
164
     *
165
	 *  @param	string	$domain      		File name to load (.lang file). Must be "file" or "file@module" for module language files:
166
 	 *										If $domain is "file@module" instead of "file" then we look for module lang file
167
	 *										in htdocs/custom/modules/mymodule/langs/code_CODE/file.lang
168
	 *										then in htdocs/module/langs/code_CODE/file.lang instead of htdocs/langs/code_CODE/file.lang
169
	 *  @param	integer	$alt         		0 (try xx_ZZ then 1), 1 (try xx_XX then 2), 2 (try en_US)
170
	 * 	@param	int		$stopafterdirection	Stop when the DIRECTION tag is found (optimize speed)
171
	 * 	@param	int		$forcelangdir		To force a different lang directory
172
	 *  @param  int     $loadfromfileonly   1=Do not load overwritten translation from file or old conf.
173
	 *	@return	int							<0 if KO, 0 if already loaded or loading not required, >0 if OK
174
	 *  @see loadLangs
175
	 */
176
	function load($domain,$alt=0,$stopafterdirection=0,$forcelangdir='',$loadfromfileonly=0)
177
	{
178
		global $conf,$db;
179
180
		//dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
181
182
		// Check parameters
183
		if (empty($domain))
184
		{
185
		    dol_print_error('',get_class($this)."::Load ErrorWrongParameters");
186
		    return -1;
187
		}
188
		if ($this->defaultlang == 'none_NONE') return 0;    // Special language code to not translate keys
189
190
191
		// Load $this->tab_translate[] from database
192
		if (empty($loadfromfileonly) && count($this->tab_translate) == 0) $this->loadFromDatabase($db);      // No translation was never loaded yet, so we load database.
193
194
195
		$newdomain = $domain;
196
		$modulename = '';
197
198
		// Search if a module directory name is provided into lang file name
199
		if (preg_match('/^([^@]+)@([^@]+)$/i',$domain,$regs))
200
		{
201
			$newdomain = $regs[1];
202
			$modulename = $regs[2];
203
		}
204
205
        // Check cache
206
		if (! empty($this->_tab_loaded[$newdomain]))	// File already loaded for this domain
207
		{
208
			//dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
209
			return 0;
210
		}
211
212
        $fileread=0;
213
		$langofdir=(empty($forcelangdir)?$this->defaultlang:$forcelangdir);
214
215
		// Redefine alt
216
		$langarray=explode('_',$langofdir);
217
		if ($alt < 1 && isset($langarray[1]) && (strtolower($langarray[0]) == strtolower($langarray[1]) || in_array(strtolower($langofdir), array('el_gr')))) $alt=1;
218
		if ($alt < 2 && strtolower($langofdir) == 'en_us') $alt=2;
219
220
		if (empty($langofdir))	// This may occurs when load is called without setting the language and without providing a value for forcelangdir
221
		{
222
			dol_syslog("Error: ".get_class($this)."::Load was called but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
223
			return -1;
224
		}
225
226
		foreach($this->dir as $keydir => $searchdir)
227
		{
228
			// Directory of translation files
229
			$file_lang = $searchdir.($modulename?'/'.$modulename:'')."/langs/".$langofdir."/".$newdomain.".lang";
230
			$file_lang_osencoded=dol_osencode($file_lang);
231
232
			$filelangexists=is_file($file_lang_osencoded);
233
234
			//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);
235
			//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";
236
237
			if ($filelangexists)
238
			{
239
				// TODO Move cache read out of loop on dirs or at least filelangexists
240
			    $found=false;
241
242
				// Enable caching of lang file in memory (not by default)
243
				$usecachekey='';
244
				// Using a memcached server
245
				if (! empty($conf->memcached->enabled) && ! empty($conf->global->MEMCACHED_SERVER))
246
				{
247
					$usecachekey=$newdomain.'_'.$langofdir.'_'.md5($file_lang);    // Should not contains special chars
248
				}
249
				// Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
250
				else if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02))
251
				{
252
					$usecachekey=$newdomain;
253
				}
254
255
				if ($usecachekey)
256
				{
257
			        //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
258
                    //global $aaa; $aaa+=1;
259
                    //print $aaa." ".$usecachekey."\n";
260
				    require_once DOL_DOCUMENT_ROOT .'/core/lib/memory.lib.php';
261
					$tmparray=dol_getcache($usecachekey);
262
					if (is_array($tmparray) && count($tmparray))
263
					{
264
				        $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.
265
						//print $newdomain."\n";
266
						//var_dump($this->tab_translate);
267
						if ($alt == 2) $fileread=1;
268
						$found=true;						// Found in dolibarr PHP cache
269
					}
270
				}
271
272
				if (! $found)
273
				{
274
					if ($fp = @fopen($file_lang,"rt"))
275
					{
276
						if ($usecachekey) $tabtranslatedomain=array();	// To save lang content in cache
277
278
						/**
279
						 * Read each lines until a '=' (with any combination of spaces around it)
280
						 * and split the rest until a line feed.
281
						 * This is more efficient than fgets + explode + trim by a factor of ~2.
282
						 */
283
						while ($line = fscanf($fp, "%[^= ]%*[ =]%[^\n]"))
284
						{
285
							if (isset($line[1]))
286
							{
287
								list($key, $value) = $line;
288
								//if ($domain == 'orders') print "Domain=$domain, found a string for $tab[0] with value $tab[1]. Currently in cache ".$this->tab_translate[$key]."<br>";
289
								//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>";
290
								if (empty($this->tab_translate[$key]))
291
								{ // 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)
292
									$value = preg_replace('/\\n/', "\n", $value); // Parse and render carriage returns
293
									if ($key == 'DIRECTION') { // This is to declare direction of language
294
										if ($alt < 2 || empty($this->tab_translate[$key])) { // We load direction only for primary files or if not yet loaded
295
											$this->tab_translate[$key] = $value;
296
											if ($stopafterdirection) {
297
												break; // We do not save tab if we stop after DIRECTION
298
											} elseif ($usecachekey) {
299
												$tabtranslatedomain[$key] = $value;
300
											}
301
										}
302
									}
303
									elseif ($key[0] == '#')
304
									{
305
									    continue;
306
									}
307
									else {
308
										$this->tab_translate[$key] = $value;
309
										//if ($domain == 'orders') print "$tab[0] value $value<br>";
310
										if ($usecachekey) {
311
											$tabtranslatedomain[$key] = $value;
312
										} // To save lang content in cache
313
									}
314
								}
315
							}
316
						}
317
						fclose($fp);
318
						$fileread=1;
319
320
						// TODO Move cache write out of loop on dirs
321
						// To save lang content for usecachekey into cache
322
						if ($usecachekey && count($tabtranslatedomain))
323
						{
324
							$ressetcache=dol_setcache($usecachekey,$tabtranslatedomain);
325
							if ($ressetcache < 0)
326
							{
327
							    $error='Failed to set cache for usecachekey='.$usecachekey.' result='.$ressetcache;
328
							    dol_syslog($error, LOG_ERR);
329
							}
330
						}
331
332
						if (empty($conf->global->MAIN_FORCELANGDIR)) break;		// Break loop on each root dir. If a module has forced dir, we do not stop loop.
333
					}
334
				}
335
			}
336
		}
337
338
		// Now we complete with next file (fr_CA->fr_FR, es_MX->ex_ES, ...)
339
		if ($alt == 0)
340
		{
341
			// This function MUST NOT contains call to syslog
342
			//dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
343
			$langofdir=strtolower($langarray[0]).'_'.strtoupper($langarray[0]);
344
			if ($langofdir == 'el_EL') $langofdir = 'el_GR';                     // main parent for el_CY is not 'el_EL' but 'el_GR'
345
			if ($langofdir == 'ar_AR') $langofdir = 'ar_SA';                     // main parent for ar_EG is not 'ar_AR' but 'ar_SA'
346
			$this->load($domain,$alt+1,$stopafterdirection,$langofdir);
347
		}
348
349
		// Now we complete with reference file (en_US)
350
		if ($alt == 1)
351
		{
352
			// This function MUST NOT contains call to syslog
353
			//dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
354
			$langofdir='en_US';
355
			$this->load($domain,$alt+1,$stopafterdirection,$langofdir);
356
		}
357
358
		// We are in the pass of the reference file. No more files to scan to complete.
359
		if ($alt == 2)
360
		{
361
			if ($fileread) $this->_tab_loaded[$newdomain]=1;								// Set domain file as found so loaded
362
363
			if (empty($this->_tab_loaded[$newdomain])) $this->_tab_loaded[$newdomain]=2;	// Set this file as not found
364
		}
365
366
		// This part is deprecated and replaced with table llx_overwrite_trans
367
		// Kept for backward compatibility.
368
		if (empty($loadfromfileonly))
369
		{
370
    		$overwritekey='MAIN_OVERWRITE_TRANS_'.$this->defaultlang;
371
            if (! empty($conf->global->$overwritekey))    // Overwrite translation with key1:newstring1,key2:newstring2
372
            {
373
        		// Overwrite translation with param MAIN_OVERWRITE_TRANS_xx_XX
374
                $tmparray=explode(',', $conf->global->$overwritekey);
375
                foreach($tmparray as $tmp)
376
                {
377
                    $tmparray2=explode(':',$tmp);
378
                    if (! empty($tmparray2[1])) $this->tab_translate[$tmparray2[0]]=$tmparray2[1];
379
                }
380
            }
381
		}
382
383
        // Check to be sure that SeparatorDecimal differs from SeparatorThousand
384
		if (! empty($this->tab_translate["SeparatorDecimal"]) && ! empty($this->tab_translate["SeparatorThousand"])
385
		&& $this->tab_translate["SeparatorDecimal"] == $this->tab_translate["SeparatorThousand"]) $this->tab_translate["SeparatorThousand"]='';
386
387
		return 1;
388
	}
389
390
	/**
391
	 *  Load translation key-value from database into a memory array.
392
	 *  If data already loaded, do nothing.
393
	 * 	All data in translation array are stored in UTF-8 format.
394
     *  tab_loaded is completed with $domain key.
395
     *  rule "we keep first entry found with we keep last entry found" so it is probably not what you want to do.
396
     *
397
     *  Value for hash are: 1:Loaded from disk, 2:Not found, 3:Loaded from cache
398
     *
399
     *  @param  Database    $db             Database handler
400
	 *	@return	int							<0 if KO, 0 if already loaded or loading not required, >0 if OK
401
	 */
402
	function loadFromDatabase($db)
403
	{
404
		global $conf;
405
406
		$domain='database';
407
408
		// Check parameters
409
		if (empty($db)) return 0;    // Database handler can't be used
410
411
		//dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
412
413
		$newdomain = $domain;
414
415
		// Check cache
416
		if (! empty($this->_tab_loaded[$newdomain]))	// File already loaded for this domain 'database'
417
		{
418
			//dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
419
			return 0;
420
		}
421
422
		$this->_tab_loaded[$newdomain] = 1;   // We want to be sure this function is called once only for domain 'database'
423
424
        $fileread=0;
425
		$langofdir=$this->defaultlang;
426
427
		if (empty($langofdir))	// This may occurs when load is called without setting the language and without providing a value for forcelangdir
428
		{
429
			dol_syslog("Error: ".get_class($this)."::Load was called but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
430
			return -1;
431
		}
432
433
		// TODO Move cache read out of loop on dirs or at least filelangexists
434
		$found=false;
435
436
		// Enable caching of lang file in memory (not by default)
437
		$usecachekey='';
438
		// Using a memcached server
439
		if (! empty($conf->memcached->enabled) && ! empty($conf->global->MEMCACHED_SERVER))
440
		{
441
			$usecachekey=$newdomain.'_'.$langofdir;    // Should not contains special chars
442
		}
443
		// Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
444
		else if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02))
445
		{
446
			$usecachekey=$newdomain;
447
		}
448
449
		if ($usecachekey)
450
		{
451
	        //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
452
            //global $aaa; $aaa+=1;
453
            //print $aaa." ".$usecachekey."\n";
454
		    require_once DOL_DOCUMENT_ROOT .'/core/lib/memory.lib.php';
455
			$tmparray=dol_getcache($usecachekey);
456
			if (is_array($tmparray) && count($tmparray))
457
			{
458
				$this->tab_translate+=$tmparray;	// Faster than array_merge($tmparray,$this->tab_translate). Note: If a valuer already exists into tab_translate, value into tmparaay is not added.
459
				//print $newdomain."\n";
460
				//var_dump($this->tab_translate);
461
				$fileread=1;
462
				$found=true;						// Found in dolibarr PHP cache
463
			}
464
		}
465
466
		if (! $found && ! empty($conf->global->MAIN_ENABLE_OVERWRITE_TRANSLATION))
467
		{
468
    		// Overwrite translation with database read
469
            $sql="SELECT transkey, transvalue FROM ".MAIN_DB_PREFIX."overwrite_trans where lang='".$db->escape($this->defaultlang)."'";
470
		    $resql=$db->query($sql);
471
472
		    if ($resql)
473
		    {
474
    		    $num = $db->num_rows($resql);
475
    		    if ($num)
476
    		    {
477
    				if ($usecachekey) $tabtranslatedomain=array();	// To save lang content in cache
478
479
    				$i = 0;
480
    				while ($i < $num)	// Ex: Need 225ms for all fgets on all lang file for Third party page. Same speed than file_get_contents
481
    				{
482
    				    $obj=$db->fetch_object($resql);
483
484
    				    $key=$obj->transkey;
485
    					$value=$obj->transvalue;
486
487
						//print "Domain=$domain, found a string for $tab[0] with value $tab[1]<br>";
488
						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)
489
						{
490
							$value=trim(preg_replace('/\\n/',"\n",$value));
491
492
							$this->tab_translate[$key]=$value;
493
							if ($usecachekey) $tabtranslatedomain[$key]=$value;	// To save lang content in cache
494
						}
495
496
    					$i++;
497
    				}
498
499
    				$fileread=1;
500
501
    				// TODO Move cache write out of loop on dirs
502
    				// To save lang content for usecachekey into cache
503
    				if ($usecachekey && count($tabtranslatedomain))
504
    				{
505
    					$ressetcache=dol_setcache($usecachekey,$tabtranslatedomain);
506
    					if ($ressetcache < 0)
507
    					{
508
    					    $error='Failed to set cache for usecachekey='.$usecachekey.' result='.$ressetcache;
509
    					    dol_syslog($error, LOG_ERR);
510
    					}
511
    				}
512
    			}
513
		    }
514
		    else
515
		    {
516
		        dol_print_error($db);
517
		    }
518
		}
519
520
		if ($fileread) $this->_tab_loaded[$newdomain]=1;	// Set domain file as loaded
521
522
		if (empty($this->_tab_loaded[$newdomain])) $this->_tab_loaded[$newdomain]=2;           // Marque ce cas comme non trouve (no lines found for language)
523
524
		return 1;
525
	}
526
527
528
529
	/**
530
	 * Return translated value of key for special keys ("Currency...", "Civility...", ...).
531
	 * Search in lang file, then into database. Key must be any complete entry into lang file: CurrencyEUR, ...
532
	 * If not found, return key.
533
	 * The string return is not formated (translated with transnoentitiesnoconv)
534
	 * NOTE: To avoid infinite loop (getLabelFromKey->transnoentities->getTradFromKey), if you modify this function,
535
	 * check that getLabelFromKey is not called with same value than input.
536
	 *
537
	 * @param	string		$key		Key to translate
538
	 * @return 	string					Translated string (translated with transnoentitiesnoconv)
539
	 */
540
	private function getTradFromKey($key)
541
	{
542
		global $conf, $db;
543
544
		if (! is_string($key)) return 'ErrorBadValueForParamNotAString';	// Avoid multiple errors with code not using function correctly.
545
546
		$newstr=$key;
547
	    if (preg_match('/^Civility([0-9A-Z]+)$/i',$key,$reg))
548
        {
549
            $newstr=$this->getLabelFromKey($db,$reg[1],'c_civility','code','label');
550
        }
551
		elseif (preg_match('/^Currency([A-Z][A-Z][A-Z])$/i',$key,$reg))
552
		{
553
			$newstr=$this->getLabelFromKey($db,$reg[1],'c_currencies','code_iso','label');
554
		}
555
		elseif (preg_match('/^SendingMethod([0-9A-Z]+)$/i',$key,$reg))
556
		{
557
			$newstr=$this->getLabelFromKey($db,$reg[1],'c_shipment_mode','code','libelle');
558
		}
559
        elseif (preg_match('/^PaymentTypeShort([0-9A-Z]+)$/i',$key,$reg))
560
        {
561
            $newstr=$this->getLabelFromKey($db,$reg[1],'c_paiement','code','libelle','',1);
562
        }
563
        elseif (preg_match('/^OppStatus([0-9A-Z]+)$/i',$key,$reg))
564
        {
565
            $newstr=$this->getLabelFromKey($db,$reg[1],'c_lead_status','code','label');
566
        }
567
        elseif (preg_match('/^OrderSource([0-9A-Z]+)$/i',$key,$reg))
568
        {
569
        	// TODO OrderSourceX must be replaced with content of table llx_c_input_reason or llx_c_input_method
570
            //$newstr=$this->getLabelFromKey($db,$reg[1],'c_ordersource','code','label');
571
        }
572
573
        /* Disabled. There is too many cases where translation of $newstr is not defined is normal (like when output with setEventMessage an already translated string)
574
        if (! empty($conf->global->MAIN_FEATURES_LEVEL) && $conf->global->MAIN_FEATURES_LEVEL >= 2)
575
        {
576
        	dol_syslog(__METHOD__." MAIN_FEATURES_LEVEL=DEVELOP: missing translation for key '".$newstr."' in ".$_SERVER["PHP_SELF"], LOG_DEBUG);
577
        }*/
578
579
        return $newstr;
580
	}
581
582
583
	/**
584
	 *  Return text translated of text received as parameter (and encode it into HTML)
585
	 *              Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
586
	 *              et si toujours pas trouve, il est retourne tel quel
587
	 *              Les parametres de cette methode peuvent contenir de balises HTML.
588
	 *
589
	 *  @param	string	$key        Key to translate
590
	 *  @param  string	$param1     chaine de param1
591
	 *  @param  string	$param2     chaine de param2
592
	 *  @param  string	$param3     chaine de param3
593
	 *  @param  string	$param4     chaine de param4
594
	 *	@param	int		$maxsize	Max length of text
595
	 *  @return string      		Translated string (encoded into HTML entities and UTF8)
596
	 */
597
	function trans($key, $param1='', $param2='', $param3='', $param4='', $maxsize=0)
598
	{
599
        global $conf;
600
601
	    if (! empty($this->tab_translate[$key]))	// Translation is available
602
		{
603
            $str=$this->tab_translate[$key];
604
605
    		// Make some string replacement after translation
606
            $replacekey='MAIN_REPLACE_TRANS_'.$this->defaultlang;
607
            if (! empty($conf->global->$replacekey))    // Replacement translation variable with string1:newstring1;string2:newstring2
608
            {
609
                $tmparray=explode(';', $conf->global->$replacekey);
610
                foreach($tmparray as $tmp)
611
                {
612
                    $tmparray2=explode(':',$tmp);
613
                    $str=preg_replace('/'.preg_quote($tmparray2[0]).'/',$tmparray2[1],$str);
614
                }
615
            }
616
617
            if (! preg_match('/^Format/',$key))
618
            {
619
            	//print $str;
620
            	$str=sprintf($str,$param1,$param2,$param3,$param4);	// Replace %s and %d except for FormatXXX strings.
621
            }
622
623
			if ($maxsize) $str=dol_trunc($str,$maxsize);
624
625
			// We replace some HTML tags by __xx__ to avoid having them encoded by htmlentities
626
            $str=str_replace(array('<','>','"',),array('__lt__','__gt__','__quot__'),$str);
627
628
			// Crypt string into HTML
629
			$str=htmlentities($str, ENT_COMPAT, $this->charset_output);	// Do not convert simple quotes in translation (strings in html are enmbraced by "). Use dol_escape_htmltag around text in HTML content
630
631
			// Restore HTML tags
632
            $str=str_replace(array('__lt__','__gt__','__quot__'),array('<','>','"',),$str);
633
634
			return $str;
635
		}
636
		else								// Translation is not available
637
		{
638
		    //if ($key[0] == '$') { return dol_eval($key,1); }
639
			return $this->getTradFromKey($key);
640
		}
641
	}
642
643
644
	/**
645
	 *  Return translated value of a text string
646
	 *               Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
647
	 *               et si toujours pas trouve, il est retourne tel quel.
648
	 *               Parameters of this method must not contains any HTML tags.
649
	 *
650
	 *  @param	string	$key        Key to translate
651
	 *  @param  string	$param1     chaine de param1
652
	 *  @param  string	$param2     chaine de param2
653
	 *  @param  string	$param3     chaine de param3
654
	 *  @param  string	$param4     chaine de param4
655
	 *  @param  string	$param5     chaine de param5
656
	 *  @return string      		Translated string (encoded into UTF8)
657
	 */
658
	function transnoentities($key, $param1='', $param2='', $param3='', $param4='', $param5='')
659
	{
660
		return $this->convToOutputCharset($this->transnoentitiesnoconv($key, $param1, $param2, $param3, $param4, $param5));
661
	}
662
663
664
	/**
665
	 *  Return translated value of a text string
666
	 *               Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
667
	 *               et si toujours pas trouve, il est retourne tel quel.
668
	 *               No convert to encoding charset of lang object is done.
669
	 *               Parameters of this method must not contains any HTML tags.
670
	 *
671
	 *  @param	string	$key        Key to translate
672
	 *  @param  string	$param1     chaine de param1
673
	 *  @param  string	$param2     chaine de param2
674
	 *  @param  string	$param3     chaine de param3
675
	 *  @param  string	$param4     chaine de param4
676
	 *  @param  string	$param5     chaine de param5
677
	 *  @return string      		Translated string
678
	 */
679
	function transnoentitiesnoconv($key, $param1='', $param2='', $param3='', $param4='', $param5='')
680
	{
681
		global $conf;
682
683
		if (! empty($this->tab_translate[$key]))	// Translation is available
684
		{
685
		    $str=$this->tab_translate[$key];
686
687
    		// Make some string replacement after translation
688
            $replacekey='MAIN_REPLACE_TRANS_'.$this->defaultlang;
689
            if (! empty($conf->global->$replacekey))    // Replacement translation variable with string1:newstring1;string2:newstring2
690
            {
691
                $tmparray=explode(';', $conf->global->$replacekey);
692
                foreach($tmparray as $tmp)
693
                {
694
                    $tmparray2=explode(':',$tmp);
695
                    $str=preg_replace('/'.preg_quote($tmparray2[0]).'/',$tmparray2[1],$str);
696
                }
697
            }
698
699
            if (! preg_match('/^Format/',$key))
700
            {
701
            	//print $str;
702
           		$str=sprintf($str, $param1, $param2, $param3, $param4, $param5);	// Replace %s and %d except for FormatXXX strings.
703
            }
704
705
            return $str;
706
		}
707
		else
708
		{
709
		    if ($key[0] == '$') { return dol_eval($key,1); }
710
			return $this->getTradFromKey($key);
711
		}
712
	}
713
714
715
	/**
716
	 *  Return translation of a key depending on country
717
	 *
718
	 *  @param	string	$str            string root to translate
719
	 *  @param  string	$countrycode    country code (FR, ...)
720
	 *  @return	string         			translated string
721
	 */
722
	function transcountry($str, $countrycode)
723
	{
724
		if ($this->tab_translate["$str$countrycode"]) return $this->trans("$str$countrycode");
725
		else return $this->trans($str);
726
	}
727
728
729
	/**
730
	 *  Retourne la version traduite du texte passe en parametre complete du code pays
731
	 *
732
	 *  @param	string	$str            string root to translate
733
	 *  @param  string	$countrycode    country code (FR, ...)
734
	 *  @return string         			translated string
735
	 */
736
	function transcountrynoentities($str, $countrycode)
737
	{
738
		if ($this->tab_translate["$str$countrycode"]) return $this->transnoentities("$str$countrycode");
739
		else return $this->transnoentities($str);
740
	}
741
742
743
	/**
744
	 *  Convert a string into output charset (this->charset_output that should be defined to conf->file->character_set_client)
745
	 *
746
	 *  @param	string	$str            String to convert
747
	 *  @param	string	$pagecodefrom	Page code of src string
748
	 *  @return string         			Converted string
749
	 */
750
	function convToOutputCharset($str,$pagecodefrom='UTF-8')
751
	{
752
		if ($pagecodefrom == 'ISO-8859-1' && $this->charset_output == 'UTF-8')  $str=utf8_encode($str);
753
		if ($pagecodefrom == 'UTF-8' && $this->charset_output == 'ISO-8859-1')	$str=utf8_decode(str_replace('€',chr(128),$str));
754
		return $str;
755
	}
756
757
758
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
759
	/**
760
	 *  Return list of all available languages
761
	 *
762
	 * 	@param	string	$langdir		Directory to scan
763
	 *  @param  integer	$maxlength   	Max length for each value in combo box (will be truncated)
764
	 *  @param	int		$usecode		1=Show code instead of country name for language variant, 2=Show only code
765
	 *  @return array     				List of languages
766
	 */
767
	function get_available_languages($langdir=DOL_DOCUMENT_ROOT,$maxlength=0,$usecode=0)
768
	{
769
        // phpcs:enable
770
		global $conf;
771
772
		// We scan directory langs to detect available languages
773
		$handle=opendir($langdir."/langs");
774
		$langs_available=array();
775
		while ($dir = trim(readdir($handle)))
776
		{
777
			if (preg_match('/^[a-z]+_[A-Z]+/i',$dir))
778
			{
779
				$this->load("languages");
780
781
				if ($usecode == 2)
782
				{
783
				    $langs_available[$dir] = $dir;
784
				}
785
				if ($usecode == 1 || ! empty($conf->global->MAIN_SHOW_LANGUAGE_CODE))
786
				{
787
				    $langs_available[$dir] = $dir.': '.dol_trunc($this->trans('Language_'.$dir),$maxlength);
788
				}
789
				else
790
				{
791
					$langs_available[$dir] = $this->trans('Language_'.$dir);
792
				}
793
			}
794
		}
795
		return $langs_available;
796
	}
797
798
799
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
800
	/**
801
	 *  Return if a filename $filename exists for current language (or alternate language)
802
	 *
803
	 *  @param	string	$filename       Language filename to search
804
	 *  @param  integer	$searchalt      Search also alernate language file
805
	 *  @return boolean         		true if exists and readable
806
	 */
807
	function file_exists($filename,$searchalt=0)
808
	{
809
        // phpcs:enable
810
		// Test si fichier dans repertoire de la langue
811
		foreach($this->dir as $searchdir)
812
		{
813
			if (is_readable(dol_osencode($searchdir."/langs/".$this->defaultlang."/".$filename))) return true;
814
815
			if ($searchalt)
816
			{
817
				// Test si fichier dans repertoire de la langue alternative
818
				if ($this->defaultlang != "en_US") $filenamealt = $searchdir."/langs/en_US/".$filename;
819
				//else $filenamealt = $searchdir."/langs/fr_FR/".$filename;
820
				if (is_readable(dol_osencode($filenamealt))) return true;
821
			}
822
		}
823
824
		return false;
825
	}
826
827
828
	/**
829
	 *      Return full text translated to language label for a key. Store key-label in a cache.
830
     *      This function need module "numberwords" to be installed. If not it will return
831
     *      same number (this module is not provided by default as it use non GPL source code).
832
	 *
833
	 *		@param	int		$number		Number to encode in full text
834
	 * 		@param	int		$isamount	1=It's an amount, 0=it's just a number
835
	 *      @return string				Label translated in UTF8 (but without entities)
836
	 * 									10 if setDefaultLang was en_US => ten
837
	 * 									123 if setDefaultLang was fr_FR => cent vingt trois
838
	 */
839
	function getLabelFromNumber($number,$isamount=0)
840
	{
841
		global $conf;
842
843
		$newnumber=$number;
844
845
		$dirsubstitutions=array_merge(array(),$conf->modules_parts['substitutions']);
846
		foreach($dirsubstitutions as $reldir)
847
		{
848
		    $dir=dol_buildpath($reldir,0);
849
		    $newdir=dol_osencode($dir);
850
851
		    // Check if directory exists
852
		    if (! is_dir($newdir)) continue;	// We must not use dol_is_dir here, function may not be loaded
853
854
			$fonc='numberwords';
855
			if (file_exists($newdir.'/functions_'.$fonc.'.lib.php'))
856
			{
857
				include_once $newdir.'/functions_'.$fonc.'.lib.php';
858
				$newnumber=numberwords_getLabelFromNumber($this,$number,$isamount);
0 ignored issues
show
Bug introduced by
The function numberwords_getLabelFromNumber was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

858
				$newnumber=/** @scrutinizer ignore-call */ numberwords_getLabelFromNumber($this,$number,$isamount);
Loading history...
859
				break;
860
			}
861
		}
862
863
		return $newnumber;
864
	}
865
866
867
	/**
868
	 *      Return a label for a key.
869
	 *      Search into translation array, then into cache, then if still not found, search into database.
870
	 *      Store key-label found into cache variable $this->cache_labels to save SQL requests to get labels.
871
	 *
872
	 * 		@param	DoliDB	$db				Database handler
873
	 * 		@param	string	$key			Translation key to get label (key in language file)
874
	 * 		@param	string	$tablename		Table name without prefix
875
	 * 		@param	string	$fieldkey		Field for key
876
	 * 		@param	string	$fieldlabel		Field for label
877
	 *      @param	string	$keyforselect	Use another value than the translation key for the where into select
878
	 *      @param  int		$filteronentity	Use a filter on entity
879
	 *      @return string					Label in UTF8 (but without entities)
880
	 *      @see dol_getIdFromCode
881
	 */
882
	function getLabelFromKey($db,$key,$tablename,$fieldkey,$fieldlabel,$keyforselect='',$filteronentity=0)
883
	{
884
		// If key empty
885
		if ($key == '') return '';
886
887
        //print 'param: '.$key.'-'.$keydatabase.'-'.$this->trans($key); exit;
888
889
		// Check if a translation is available (this can call getTradFromKey)
890
		$tmp=$this->transnoentitiesnoconv($key);
891
		if ($tmp != $key && $tmp != 'ErrorBadValueForParamNotAString')
892
		{
893
			return $tmp;    // Found in language array
894
		}
895
896
		// Check in cache
897
		if (isset($this->cache_labels[$tablename][$key]))	// Can be defined to 0 or ''
898
		{
899
			return $this->cache_labels[$tablename][$key];   // Found in cache
900
		}
901
902
		$sql = "SELECT ".$fieldlabel." as label";
903
		$sql.= " FROM ".MAIN_DB_PREFIX.$tablename;
904
		$sql.= " WHERE ".$fieldkey." = '".$db->escape($keyforselect?$keyforselect:$key)."'";
905
		if ($filteronentity) $sql.= " AND entity IN (" . getEntity($tablename). ')';
906
		dol_syslog(get_class($this).'::getLabelFromKey', LOG_DEBUG);
907
		$resql = $db->query($sql);
908
		if ($resql)
909
		{
910
			$obj = $db->fetch_object($resql);
911
			if ($obj) $this->cache_labels[$tablename][$key]=$obj->label;
912
			else $this->cache_labels[$tablename][$key]=$key;
913
914
			$db->free($resql);
915
			return $this->cache_labels[$tablename][$key];
916
		}
917
		else
918
		{
919
			$this->error=$db->lasterror();
920
			return -1;
921
		}
922
	}
923
924
925
	/**
926
	 *	Return a currency code into its symbol
927
	 *
928
	 *  @param	string	$currency_code		Currency Code
929
	 *  @param	string	$amount				If not '', show currency + amount according to langs ($10, 10€).
930
	 *  @return	string						Amount + Currency symbol encoded into UTF8
931
	 *  @deprecated							Use method price to output a price
932
	 *  @see price()
933
	 */
934
	function getCurrencyAmount($currency_code, $amount)
935
	{
936
		$symbol=$this->getCurrencySymbol($currency_code);
937
938
		if (in_array($currency_code, array('USD'))) return $symbol.$amount;
939
		else return $amount.$symbol;
940
	}
941
942
	/**
943
	 *	Return a currency code into its symbol.
944
	 *  If mb_convert_encoding is not available, return currency code.
945
	 *
946
	 *  @param	string	$currency_code		Currency code
947
	 *  @param	integer	$forceloadall		1=Force to load all currencies into cache. We know we need to use all of them. By default read and cache only required currency.
948
	 *  @return	string						Currency symbol encoded into UTF8
949
	 */
950
	function getCurrencySymbol($currency_code, $forceloadall=0)
951
	{
952
		$currency_sign = '';	// By default return iso code
953
954
		if (function_exists("mb_convert_encoding"))
955
		{
956
			$this->loadCacheCurrencies($forceloadall?'':$currency_code);
957
958
			if (isset($this->cache_currencies[$currency_code]) && ! empty($this->cache_currencies[$currency_code]['unicode']) && is_array($this->cache_currencies[$currency_code]['unicode']))
959
			{
960
				foreach($this->cache_currencies[$currency_code]['unicode'] as $unicode)
961
				{
962
					$currency_sign .= mb_convert_encoding("&#{$unicode};", "UTF-8", 'HTML-ENTITIES');
963
				}
964
			}
965
		}
966
967
		return ($currency_sign?$currency_sign:$currency_code);
968
	}
969
970
	/**
971
	 *  Load into the cache this->cache_currencies, all currencies
972
	 *
973
	 *	@param	string	$currency_code		Get only currency. Get all if ''.
974
	 *  @return int             			Nb of loaded lines, 0 if already loaded, <0 if KO
975
	 */
976
	public function loadCacheCurrencies($currency_code)
977
	{
978
		global $db;
979
980
		if ($this->cache_currencies_all_loaded) return 0;                                           // Cache already loaded for all
981
		if (! empty($currency_code) && isset($this->cache_currencies[$currency_code])) return 0;    // Cache already loaded for the currency
982
983
		$sql = "SELECT code_iso, label, unicode";
984
		$sql.= " FROM ".MAIN_DB_PREFIX."c_currencies";
985
		$sql.= " WHERE active = 1";
986
		if (! empty($currency_code)) $sql.=" AND code_iso = '".$db->escape($currency_code)."'";
987
		//$sql.= " ORDER BY code_iso ASC"; // Not required, a sort is done later
988
989
		dol_syslog(get_class($this).'::loadCacheCurrencies', LOG_DEBUG);
990
		$resql = $db->query($sql);
991
		if ($resql)
992
		{
993
			$this->load("dict");
994
			$label=array();
995
			if (! empty($currency_code)) foreach($this->cache_currencies as $key => $val) $label[$key]=$val['label']; // Label in already loaded cache
996
997
			$num = $db->num_rows($resql);
998
			$i = 0;
999
			while ($i < $num)
1000
			{
1001
				$obj = $db->fetch_object($resql);
1002
1003
				// Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
1004
				$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:''));
1005
				$this->cache_currencies[$obj->code_iso]['unicode'] = (array) json_decode($obj->unicode, true);
1006
				$label[$obj->code_iso] = $this->cache_currencies[$obj->code_iso]['label'];
1007
				$i++;
1008
			}
1009
			if (empty($currency_code)) $this->cache_currencies_all_loaded=true;
1010
			//print count($label).' '.count($this->cache_currencies);
1011
1012
			// Resort cache
1013
			array_multisort($label, SORT_ASC, $this->cache_currencies);
1014
			//var_dump($this->cache_currencies);	$this->cache_currencies is now sorted onto label
1015
			return $num;
1016
		}
1017
		else
1018
		{
1019
			dol_print_error($db);
1020
			return -1;
1021
		}
1022
	}
1023
1024
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1025
	/**
1026
	 * Return an array with content of all loaded translation keys (found into this->tab_translate) so
1027
	 * we get a substitution array we can use for substitutions (for mail or ODT generation for example)
1028
	 *
1029
	 * @return array	Array of translation keys lang_key => string_translation_loaded
1030
	 */
1031
	function get_translations_for_substitutions()
1032
	{
1033
        // phpcs:enable
1034
		$substitutionarray = array();
1035
1036
		foreach($this->tab_translate as $code => $label) {
1037
			$substitutionarray['lang_'.$code] = $label;
1038
			$substitutionarray['__('.$code.')__'] = $label;
1039
		}
1040
1041
		return $substitutionarray;
1042
	}
1043
}
1044