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('"', '"', $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('"', '"', $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