Passed
Push — master ( 0f9140...c4489d )
by Alxarafe
22:27
created

Base/AlLangs.php (3 issues)

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

804
                $newnumber = /** @scrutinizer ignore-call */ numberwords_getLabelFromNumber($this, $number, $isamount);
Loading history...
805
                break;
806
            }
807
        }
808
809
        return $newnumber;
810
    }
811
812
    /**
813
     *      Return a label for a key.
814
     *      Search into translation array, then into cache, then if still not found, search into database.
815
     *      Store key-label found into cache variable $this->cache_labels to save SQL requests to get labels.
816
     *
817
     * 		@param	DoliDB	$db				Database handler
0 ignored issues
show
The type Alixar\Base\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
818
     * 		@param	string	$key			Translation key to get label (key in language file)
819
     * 		@param	string	$tablename		Table name without prefix
820
     * 		@param	string	$fieldkey		Field for key
821
     * 		@param	string	$fieldlabel		Field for label
822
     *      @param	string	$keyforselect	Use another value than the translation key for the where into select
823
     *      @param  int		$filteronentity	Use a filter on entity
824
     *      @return string					Label in UTF8 (but without entities)
825
     *      @see dol_getIdFromCode
826
     */
827
    function getLabelFromKey($db, $key, $tablename, $fieldkey, $fieldlabel, $keyforselect = '', $filteronentity = 0)
828
    {
829
        // If key empty
830
        if ($key == '')
831
            return '';
832
833
        //print 'param: '.$key.'-'.$keydatabase.'-'.$this->trans($key); exit;
834
        // Check if a translation is available (this can call getTradFromKey)
835
        $tmp = $this->transnoentitiesnoconv($key);
836
        if ($tmp != $key && $tmp != 'ErrorBadValueForParamNotAString') {
837
            return $tmp;    // Found in language array
838
        }
839
840
        // Check in cache
841
        if (isset($this->cache_labels[$tablename][$key])) { // Can be defined to 0 or ''
842
            return $this->cache_labels[$tablename][$key];   // Found in cache
843
        }
844
845
        $sql = "SELECT " . $fieldlabel . " as label";
846
        $sql .= " FROM " . MAIN_DB_PREFIX . $tablename;
847
        $sql .= " WHERE " . $fieldkey . " = '" . $db->escape($keyforselect ? $keyforselect : $key) . "'";
848
        if ($filteronentity)
849
            $sql .= " AND entity IN (" . getEntity($tablename) . ')';
850
        AlDolUtils::dol_syslog(get_class($this) . '::getLabelFromKey', LOG_DEBUG);
851
        $resql = $db->query($sql);
852
        if ($resql) {
853
            $obj = $db->fetch_object($resql);
854
            if ($obj)
855
                $this->cache_labels[$tablename][$key] = $obj->label;
856
            else
857
                $this->cache_labels[$tablename][$key] = $key;
858
859
            $db->free($resql);
860
            return $this->cache_labels[$tablename][$key];
861
        }
862
        else {
863
            $this->error = $db->lasterror();
864
            return -1;
865
        }
866
    }
867
868
    /**
869
     * 	Return a currency code into its symbol
870
     *
871
     *  @param	string	$currency_code		Currency Code
872
     *  @param	string	$amount				If not '', show currency + amount according to langs ($10, 10€).
873
     *  @return	string						Amount + Currency symbol encoded into UTF8
874
     *  @deprecated							Use method price to output a price
875
     *  @see price()
876
     */
877
    function getCurrencyAmount($currency_code, $amount)
878
    {
879
        $symbol = $this->getCurrencySymbol($currency_code);
880
881
        if (in_array($currency_code, array('USD')))
882
            return $symbol . $amount;
883
        else
884
            return $amount . $symbol;
885
    }
886
887
    /**
888
     * 	Return a currency code into its symbol.
889
     *  If mb_convert_encoding is not available, return currency code.
890
     *
891
     *  @param	string	$currency_code		Currency code
892
     *  @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.
893
     *  @return	string						Currency symbol encoded into UTF8
894
     */
895
    function getCurrencySymbol($currency_code, $forceloadall = 0)
896
    {
897
        $currency_sign = ''; // By default return iso code
898
899
        if (function_exists("mb_convert_encoding")) {
900
            $this->loadCacheCurrencies($forceloadall ? '' : $currency_code);
901
902
            if (isset($this->cache_currencies[$currency_code]) && !empty($this->cache_currencies[$currency_code]['unicode']) && is_array($this->cache_currencies[$currency_code]['unicode'])) {
903
                foreach ($this->cache_currencies[$currency_code]['unicode'] as $unicode) {
904
                    $currency_sign .= mb_convert_encoding("&#{$unicode};", "UTF-8", 'HTML-ENTITIES');
905
                }
906
            }
907
        }
908
909
        return ($currency_sign ? $currency_sign : $currency_code);
910
    }
911
912
    /**
913
     *  Load into the cache this->cache_currencies, all currencies
914
     *
915
     * 	@param	string	$currency_code		Get only currency. Get all if ''.
916
     *  @return int             			Nb of loaded lines, 0 if already loaded, <0 if KO
917
     */
918
    public function loadCacheCurrencies($currency_code)
919
    {
920
        global $db;
921
922
        if ($this->cache_currencies_all_loaded)
923
            return 0;                                           // Cache already loaded for all
924
        if (!empty($currency_code) && isset($this->cache_currencies[$currency_code]))
925
            return 0;    // Cache already loaded for the currency
926
927
        $sql = "SELECT code_iso, label, unicode";
928
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_currencies";
929
        $sql .= " WHERE active = 1";
930
        if (!empty($currency_code))
931
            $sql .= " AND code_iso = '" . $db->escape($currency_code) . "'";
932
        //$sql.= " ORDER BY code_iso ASC"; // Not required, a sort is done later
933
934
        AlDolUtils::dol_syslog(get_class($this) . '::loadCacheCurrencies', LOG_DEBUG);
935
        $resql = $db->query($sql);
936
        if ($resql) {
937
            $this->load("dict");
938
            $label = array();
939
            if (!empty($currency_code))
940
                foreach ($this->cache_currencies as $key => $val)
941
                    $label[$key] = $val['label']; // Label in already loaded cache
942
943
                $num = $db->num_rows($resql);
944
            $i = 0;
945
            while ($i < $num) {
946
                $obj = $db->fetch_object($resql);
947
948
                // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
949
                $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 : ''));
950
                $this->cache_currencies[$obj->code_iso]['unicode'] = (array) json_decode($obj->unicode, true);
951
                $label[$obj->code_iso] = $this->cache_currencies[$obj->code_iso]['label'];
952
                $i++;
953
            }
954
            if (empty($currency_code))
955
                $this->cache_currencies_all_loaded = true;
956
            //print count($label).' '.count($this->cache_currencies);
957
            // Resort cache
958
            array_multisort($label, SORT_ASC, $this->cache_currencies);
959
            //var_dump($this->cache_currencies);	$this->cache_currencies is now sorted onto label
960
            return $num;
961
        }
962
        else {
963
            dol_print_error($db);
964
            return -1;
965
        }
966
    }
967
968
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
969
    /**
970
     * Return an array with content of all loaded translation keys (found into this->tab_translate) so
971
     * we get a substitution array we can use for substitutions (for mail or ODT generation for example)
972
     *
973
     * @return array	Array of translation keys lang_key => string_translation_loaded
974
     */
975
    function get_translations_for_substitutions()
976
    {
977
        // phpcs:enable
978
        $substitutionarray = array();
979
980
        foreach ($this->tab_translate as $code => $label) {
981
            $substitutionarray['lang_' . $code] = $label;
982
            $substitutionarray['__(' . $code . ')__'] = $label;
983
        }
984
985
        return $substitutionarray;
986
    }
987
}
988