1<?php
2/**
3 * 2007-2016 PrestaShop
4 *
5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA
6 * Copyright (C) 2017-2018 thirty bees
7 *
8 * NOTICE OF LICENSE
9 *
10 * This source file is subject to the Open Software License (OSL 3.0)
11 * that is bundled with this package in the file LICENSE.txt.
12 * It is also available through the world-wide-web at this URL:
13 * http://opensource.org/licenses/osl-3.0.php
14 * If you did not receive a copy of the license and are unable to
15 * obtain it through the world-wide-web, please send an email
16 * to license@thirtybees.com so we can send you a copy immediately.
17 *
18 * DISCLAIMER
19 *
20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
21 * versions in the future. If you wish to customize PrestaShop for your
22 * needs please refer to https://www.thirtybees.com for more information.
23 *
24 * @author    thirty bees <contact@thirtybees.com>
25 * @author    PrestaShop SA <contact@prestashop.com>
26 * @copyright 2017-2018 thirty bees
27 * @copyright 2007-2016 PrestaShop SA
28 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
29 *  PrestaShop is an internationally registered trademark & property of PrestaShop SA
30 */
31
32/**
33 * Class TranslateCore
34 *
35 * @since 1.0.0
36 */
37class TranslateCore
38{
39    /**
40     * Get a translation for an admin controller
41     *
42     * @param string $string
43     * @param string $class
44     * @param bool   $addslashes
45     * @param bool   $htmlentities
46     *
47     * @return string
48     *
49     * @since   1.0.0
50     * @version 1.0.0 Initial version
51     */
52    public static function getAdminTranslation($string, $class = 'AdminTab', $addslashes = false, $htmlentities = true, $sprintf = null)
53    {
54        static $modulesTabs = null;
55
56        global $_LANGADM;
57
58        if ($modulesTabs === null) {
59            try {
60                $modulesTabs = Tab::getModuleTabList();
61            } catch (PrestaShopException $e) {
62                $modulesTabs = [];
63            }
64        }
65
66        if ($_LANGADM == null) {
67            $iso = Context::getContext()->language->iso_code;
68            if (empty($iso)) {
69                try {
70                    $iso = Language::getIsoById((int) Configuration::get('PS_LANG_DEFAULT'));
71                } catch (PrestaShopException $e) {
72                    $iso = 'en';
73                }
74            }
75            if (file_exists(_PS_TRANSLATIONS_DIR_.$iso.'/admin.php')) {
76                include_once(_PS_TRANSLATIONS_DIR_.$iso.'/admin.php');
77            }
78        }
79
80        if (isset($modulesTabs[strtolower($class)])) {
81            $classNameController = $class.'controller';
82            // if the class is extended by a module, use modules/[module_name]/xx.php lang file
83            if (class_exists($classNameController) && Module::getModuleNameFromClass($classNameController)) {
84                return Translate::getModuleTranslation(Module::$classInModule[$classNameController], $string, $classNameController, $sprintf, $addslashes);
85            }
86        }
87
88        $string = preg_replace("/\\\*'/", "\'", $string);
89        $key = md5($string);
90        if (isset($_LANGADM[$class.$key]) && $_LANGADM[$class.$key] !== '') {
91            $str = $_LANGADM[$class.$key];
92        } else {
93            $str = Translate::getGenericAdminTranslation($string, $key, $_LANGADM);
94        }
95
96        if ($htmlentities) {
97            $str = htmlspecialchars($str, ENT_QUOTES, 'utf-8');
98        }
99        $str = str_replace('"', '&quot;', $str);
100
101        if ($sprintf !== null) {
102            $str = Translate::checkAndReplaceArgs($str, $sprintf);
103        }
104
105        return ($addslashes ? addslashes($str) : stripslashes($str));
106    }
107
108    /**
109     * Get a translation for a module
110     *
111     * @param string|Module $module
112     * @param string $string
113     * @param string $source
114     * @param array $sprintf
115     * @param bool $js
116     * @return string
117     *
118     * @since   1.0.0
119     * @version 1.0.0 Initial version
120     */
121    public static function getModuleTranslation($module, $string, $source, $sprintf = null, $js = false)
122    {
123        global $_MODULES, $_MODULE, $_LANGADM;
124
125        static $langCache = [];
126        // $_MODULES is a cache of translations for all module.
127        // $translations_merged is a cache of wether a specific module's translations have already been added to $_MODULES
128        static $translationsMerged = [];
129
130        $name = $module instanceof Module ? $module->name : $module;
131
132        $language = Context::getContext()->language;
133
134        if (!isset($translationsMerged[$name]) && isset(Context::getContext()->language)) {
135            $filesByPriority = [
136                // Translations in theme
137                _PS_THEME_DIR_.'modules/'.$name.'/translations/'.$language->iso_code.'.php',
138                _PS_THEME_DIR_.'modules/'.$name.'/'.$language->iso_code.'.php',
139                // PrestaShop 1.5 translations
140                _PS_MODULE_DIR_.$name.'/translations/'.$language->iso_code.'.php',
141                // PrestaShop 1.4 translations
142                _PS_MODULE_DIR_.$name.'/'.$language->iso_code.'.php',
143            ];
144            foreach ($filesByPriority as $file) {
145                if (file_exists($file)) {
146                    include_once($file);
147                    $_MODULES = !empty($_MODULES) ? $_MODULES + $_MODULE : $_MODULE; //we use "+" instead of array_merge() because array merge erase existing values.
148                    $translationsMerged[$name] = true;
149                }
150            }
151        }
152        $string = preg_replace("/\\\*'/", "\'", $string);
153        $key = md5($string);
154
155        $cacheKey = $name.'|'.$string.'|'.$source.'|'.(int) $js;
156
157        if (!isset($langCache[$cacheKey])) {
158            if ($_MODULES == null) {
159                return static::escapeModuleTranslation($string, $sprintf, $js);
160            }
161
162            $currentKey = strtolower('<{'.$name.'}'._THEME_NAME_.'>'.$source).'_'.$key;
163            $defaultKey = strtolower('<{'.$name.'}thirtybees>'.$source).'_'.$key;
164            $prestaShopKey = strtolower('<{'.$name.'}prestashop>'.$source).'_'.$key;
165
166            if ('controller' == substr($source, -10, 10)) {
167                $file = substr($source, 0, -10);
168                $currentKeyFile = strtolower('<{'.$name.'}'._THEME_NAME_.'>'.$file).'_'.$key;
169                $defaultKeyFile = strtolower('<{'.$name.'}thirtybees>'.$file).'_'.$key;
170                $prestaShopKeyFile = strtolower('<{'.$name.'}prestashop>'.$file).'_'.$key;
171            }
172
173            if (isset($currentKeyFile) && !empty($_MODULES[$currentKeyFile])) {
174                $ret = $_MODULES[$currentKeyFile];
175            } elseif (isset($defaultKeyFile) && !empty($_MODULES[$defaultKeyFile])) {
176                $ret = $_MODULES[$defaultKeyFile];
177            } elseif (isset($prestaShopKeyFile) && !empty($_MODULES[$prestaShopKeyFile])) {
178                $ret = $_MODULES[$prestaShopKeyFile];
179            } elseif (!empty($_MODULES[$currentKey])) {
180                $ret = $_MODULES[$currentKey];
181            } elseif (!empty($_MODULES[$defaultKey])) {
182                $ret = $_MODULES[$defaultKey];
183            } elseif (!empty($_MODULES[$prestaShopKey])) {
184                $ret = $_MODULES[$prestaShopKey];
185            } elseif (!empty($_LANGADM)) {
186                $ret = Translate::getGenericAdminTranslation($string, $key, $_LANGADM);
187            } else {
188                $ret = $string;
189            }
190
191            $ret = static::escapeModuleTranslation($ret, $sprintf, $js);
192
193            if ($sprintf === null) {
194                $langCache[$cacheKey] = $ret;
195            } else {
196                return $ret;
197            }
198        }
199
200        return $langCache[$cacheKey];
201    }
202
203    /**
204     * Helper method to escape return value for getModuleTranslation
205     *
206     * @param string $input
207     * @param array $sprintf
208     * @param bool $js
209     *
210     * @return string
211     */
212    protected static function escapeModuleTranslation($input, $sprintf, $js)
213    {
214        if (! $input) {
215            return '';
216        }
217
218        $ret = stripslashes($input);
219
220        if ($sprintf !== null) {
221            $ret = Translate::checkAndReplaceArgs($ret, $sprintf);
222        }
223
224        return $js ? addslashes($ret) : htmlspecialchars($ret, ENT_COMPAT, 'UTF-8');
225    }
226
227    /**
228     * Check if string use a specif syntax for sprintf and replace arguments if use it
229     *
230     * @param string $string
231     * @param array  $args
232     *
233     * @return string
234     *
235     * @since   1.0.0
236     * @version 1.0.0 Initial version
237     */
238    public static function checkAndReplaceArgs($string, $args)
239    {
240        if (preg_match_all('#(?:%%|%(?:[0-9]+\$)?[+-]?(?:[ 0]|\'.)?-?[0-9]*(?:\.[0-9]+)?[bcdeufFosxX])#', $string, $matches) && !is_null($args)) {
241            if (!is_array($args)) {
242                $args = [$args];
243            }
244
245            return vsprintf($string, $args);
246        }
247
248        return $string;
249    }
250
251    /**
252     * Return the translation for a string if it exists for the base AdminController or for helpers
253     *
254     * @param string      $string     string to translate
255     * @param string|null $key        md5 key if already calculated (optional)
256     * @param array       $langArray  Global array of admin translations
257     *
258     * @return string translation
259     *
260     * @since   1.0.0
261     * @version 1.0.0 Initial version
262     */
263    public static function getGenericAdminTranslation($string, $key = null, &$langArray)
264    {
265        $string = preg_replace("/\\\*'/", "\'", $string);
266        if (is_null($key)) {
267            $key = md5($string);
268        }
269
270        if (isset($langArray['AdminController'.$key])) {
271            $str = $langArray['AdminController'.$key];
272        } elseif (isset($langArray['Helper'.$key])) {
273            $str = $langArray['Helper'.$key];
274        } elseif (isset($langArray['AdminTab'.$key])) {
275            $str = $langArray['AdminTab'.$key];
276        } else {
277            // note in 1.5, some translations has moved from AdminXX to helper/*.tpl
278            $str = $string;
279        }
280
281        return $str !== '' ? $str : $string;
282    }
283
284    /**
285     * Get a translation for a PDF
286     *
287     * @param string $string
288     *
289     * @param null   $sprintf
290     *
291     * @return string
292     * @since   1.0.0
293     * @version 1.0.0 Initial version
294     */
295    public static function getPdfTranslation($string, $sprintf = null)
296    {
297        global $_LANGPDF;
298
299        $iso = Context::getContext()->language->iso_code;
300
301        if (!Validate::isLangIsoCode($iso)) {
302            Tools::displayError(sprintf('Invalid iso lang (%s)', Tools::safeOutput($iso)));
303        }
304
305        $overrideI18NFile = _PS_THEME_DIR_.'pdf/lang/'.$iso.'.php';
306        $i18NFile = _PS_TRANSLATIONS_DIR_.$iso.'/pdf.php';
307        if (file_exists($overrideI18NFile)) {
308            $i18NFile = $overrideI18NFile;
309        }
310
311        if (!include($i18NFile)) {
312            Tools::displayError(sprintf('Cannot include PDF translation language file : %s', $i18NFile));
313        }
314
315        if (!isset($_LANGPDF) || !is_array($_LANGPDF)) {
316            return str_replace('"', '&quot;', $string);
317        }
318
319        $string = preg_replace("/\\\*'/", "\'", $string);
320        $key = 'PDF' . md5($string);
321
322        $str = array_key_exists($key, $_LANGPDF) && $_LANGPDF[$key] !== '' ? $_LANGPDF[$key] : $string;
323
324        if ($sprintf !== null) {
325            $str = Translate::checkAndReplaceArgs($str, $sprintf);
326        }
327
328        return $str;
329    }
330
331    /**
332     * Compatibility method that just calls postProcessTranslation.
333     *
334     * @deprecated 1.0.0 renamed this to postProcessTranslation, since it is not only used in relation to smarty.
335     */
336    public static function smartyPostProcessTranslation($string, $params)
337    {
338        return Translate::postProcessTranslation($string, $params);
339    }
340
341    /**
342     * Perform operations on translations after everything is escaped and before displaying it
343     *
344     * @since   1.0.0
345     * @version 1.0.0 Initial version
346     *
347     * @param string $string
348     * @param array  $params
349     *
350     * @return mixed
351     */
352    public static function postProcessTranslation($string, $params)
353    {
354        // If tags were explicitely provided, we want to use them *after* the translation string is escaped.
355        if (!empty($params['tags'])) {
356            foreach ($params['tags'] as $index => $tag) {
357                // Make positions start at 1 so that it behaves similar to the %1$d etc. sprintf positional params
358                $position = $index + 1;
359                // extract tag name
360                $match = [];
361                if (preg_match('/^\s*<\s*(\w+)/', $tag, $match)) {
362                    $opener = $tag;
363                    $closer = '</'.$match[1].'>';
364
365                    $string = str_replace('['.$position.']', $opener, $string);
366                    $string = str_replace('[/'.$position.']', $closer, $string);
367                    $string = str_replace('['.$position.'/]', $opener.$closer, $string);
368                }
369            }
370        }
371
372        return $string;
373    }
374
375    /**
376     * Helper function to make calls to postProcessTranslation more readable.
377     *
378     * @param string $string
379     * @param array  $tags
380     *
381     * @return mixed
382     *
383     * @since   1.0.0
384     * @version 1.0.0 Initial version
385     */
386    public static function ppTags($string, $tags)
387    {
388        return Translate::postProcessTranslation($string, ['tags' => $tags]);
389    }
390}
391