1<?php 2/** 3 * Copyright since 2007 PrestaShop SA and Contributors 4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA 5 * 6 * NOTICE OF LICENSE 7 * 8 * This source file is subject to the Open Software License (OSL 3.0) 9 * that is bundled with this package in the file LICENSE.md. 10 * It is also available through the world-wide-web at this URL: 11 * https://opensource.org/licenses/OSL-3.0 12 * If you did not receive a copy of the license and are unable to 13 * obtain it through the world-wide-web, please send an email 14 * to license@prestashop.com so we can send you a copy immediately. 15 * 16 * DISCLAIMER 17 * 18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 19 * versions in the future. If you wish to customize PrestaShop for your 20 * needs please refer to https://devdocs.prestashop.com/ for more information. 21 * 22 * @author PrestaShop SA and Contributors <contact@prestashop.com> 23 * @copyright Since 2007 PrestaShop SA and Contributors 24 * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) 25 */ 26use Composer\CaBundle\CaBundle; 27use PHPSQLParser\PHPSQLParser; 28use PrestaShop\PrestaShop\Adapter\ContainerFinder; 29use PrestaShop\PrestaShop\Core\Exception\ContainerNotFoundException; 30use PrestaShop\PrestaShop\Core\Foundation\Filesystem\FileSystem as PsFileSystem; 31use PrestaShop\PrestaShop\Core\Localization\Exception\LocalizationException; 32use PrestaShop\PrestaShop\Core\Localization\Locale; 33use PrestaShop\PrestaShop\Core\Localization\Locale\Repository as LocaleRepository; 34use PrestaShop\PrestaShop\Core\String\CharacterCleaner; 35use PrestaShop\PrestaShop\Core\Util\ColorBrightnessCalculator; 36use Symfony\Component\Filesystem\Filesystem; 37use Symfony\Component\HttpFoundation\Request; 38 39class ToolsCore 40{ 41 const CACERT_LOCATION = 'https://curl.haxx.se/ca/cacert.pem'; 42 const SERVICE_LOCALE_REPOSITORY = 'prestashop.core.localization.locale.repository'; 43 public const CACHE_LIFETIME_SECONDS = 604800; 44 45 protected static $file_exists_cache = []; 46 protected static $_forceCompile; 47 protected static $_caching; 48 protected static $_user_plateform; 49 protected static $_user_browser; 50 protected static $request; 51 protected static $cldr_cache = []; 52 protected static $colorBrightnessCalculator; 53 protected static $fallbackParameters = []; 54 55 public static $round_mode = null; 56 57 /** 58 * @param Request $request 59 */ 60 public function __construct(Request $request = null) 61 { 62 if ($request) { 63 self::$request = $request; 64 } 65 } 66 67 /** 68 * Properly clean static cache 69 */ 70 public static function resetStaticCache() 71 { 72 static::$cldr_cache = []; 73 } 74 75 /** 76 * Reset the request set during the first new Tools($request) call. 77 */ 78 public static function resetRequest() 79 { 80 self::$request = null; 81 } 82 83 /** 84 * Random password generator. 85 * 86 * @param int $length Desired length (optional) 87 * @param string $flag Output type (NUMERIC, ALPHANUMERIC, NO_NUMERIC, RANDOM) 88 * 89 * @return bool|string Password 90 */ 91 public static function passwdGen($length = 8, $flag = 'ALPHANUMERIC') 92 { 93 $length = (int) $length; 94 95 if ($length <= 0) { 96 return false; 97 } 98 99 switch ($flag) { 100 case 'NUMERIC': 101 $str = '0123456789'; 102 103 break; 104 case 'NO_NUMERIC': 105 $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 106 107 break; 108 case 'RANDOM': 109 $num_bytes = ceil($length * 0.75); 110 $bytes = self::getBytes($num_bytes); 111 112 return substr(rtrim(base64_encode($bytes), '='), 0, $length); 113 case 'ALPHANUMERIC': 114 default: 115 $str = 'abcdefghijkmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 116 117 break; 118 } 119 120 $bytes = Tools::getBytes($length); 121 $position = 0; 122 $result = ''; 123 124 for ($i = 0; $i < $length; ++$i) { 125 $position = ($position + ord($bytes[$i])) % strlen($str); 126 $result .= $str[$position]; 127 } 128 129 return $result; 130 } 131 132 /** 133 * Random bytes generator. 134 * 135 * Limited to OpenSSL since 1.7.0.0 136 * 137 * @param int $length Desired length of random bytes 138 * 139 * @return bool|string Random bytes 140 */ 141 public static function getBytes($length) 142 { 143 $length = (int) $length; 144 145 if ($length <= 0) { 146 return false; 147 } 148 149 $bytes = openssl_random_pseudo_bytes($length, $cryptoStrong); 150 151 if ($cryptoStrong === true) { 152 return $bytes; 153 } 154 155 return false; 156 } 157 158 /** 159 * Replace text within a portion of a string. 160 * 161 * Replaces a string matching a search, (optionally) string from a certain position 162 * 163 * @param string $search The string to search in the input string 164 * @param string $replace The replacement string 165 * @param string $subject The input string 166 * @param int $cur Starting position cursor for the search 167 * 168 * @return string the result string is returned 169 */ 170 public static function strReplaceFirst($search, $replace, $subject, $cur = 0) 171 { 172 $strPos = strpos($subject, $search, $cur); 173 174 return $strPos !== false ? substr_replace($subject, $replace, (int) $strPos, strlen($search)) : $subject; 175 } 176 177 /** 178 * Redirect user to another page. 179 * 180 * Warning: uses exit 181 * 182 * @param string $url Desired URL 183 * @param string $base_uri Base URI (optional) 184 * @param Link $link 185 * @param string|array $headers A list of headers to send before redirection 186 */ 187 public static function redirect($url, $base_uri = __PS_BASE_URI__, Link $link = null, $headers = null) 188 { 189 if (!$link) { 190 $link = Context::getContext()->link; 191 } 192 193 if (strpos($url, 'http://') === false && strpos($url, 'https://') === false && $link) { 194 if (strpos($url, $base_uri) === 0) { 195 $url = substr($url, strlen($base_uri)); 196 } 197 if (strpos($url, 'index.php?controller=') !== false && strpos($url, 'index.php/') == 0) { 198 $url = substr($url, strlen('index.php?controller=')); 199 if (Configuration::get('PS_REWRITING_SETTINGS')) { 200 $url = Tools::strReplaceFirst('&', '?', $url); 201 } 202 } 203 204 $explode = explode('?', $url); 205 $url = $link->getPageLink($explode[0]); 206 if (isset($explode[1])) { 207 $url .= '?' . $explode[1]; 208 } 209 } 210 211 // Send additional headers 212 if ($headers) { 213 if (!is_array($headers)) { 214 $headers = [$headers]; 215 } 216 217 foreach ($headers as $header) { 218 header($header); 219 } 220 } 221 222 header('Location: ' . $url); 223 exit; 224 } 225 226 /** 227 * Redirect URLs already containing PS_BASE_URI. 228 * 229 * Warning: uses exit 230 * 231 * @param string $url Desired URL 232 */ 233 public static function redirectLink($url) 234 { 235 if (!preg_match('@^https?://@i', $url)) { 236 if (strpos($url, __PS_BASE_URI__) !== false && strpos($url, __PS_BASE_URI__) == 0) { 237 $url = substr($url, strlen(__PS_BASE_URI__)); 238 } 239 if (strpos($url, 'index.php?controller=') !== false && strpos($url, 'index.php/') == 0) { 240 $url = substr($url, strlen('index.php?controller=')); 241 } 242 $explode = explode('?', $url); 243 $url = Context::getContext()->link->getPageLink($explode[0]); 244 if (isset($explode[1])) { 245 $url .= '?' . $explode[1]; 246 } 247 } 248 header('Location: ' . $url); 249 exit; 250 } 251 252 /** 253 * Redirect user to another page (using header Location) 254 * 255 * Warning: uses exit 256 * 257 * @param string $url Desired URL 258 */ 259 public static function redirectAdmin($url) 260 { 261 header('Location: ' . $url); 262 exit; 263 } 264 265 /** 266 * Returns the available protocol for the current shop in use 267 * SSL if Configuration is set on and available for the server. 268 * 269 * @return string 270 */ 271 public static function getShopProtocol() 272 { 273 $protocol = (Configuration::get('PS_SSL_ENABLED') || (!empty($_SERVER['HTTPS']) 274 && Tools::strtolower($_SERVER['HTTPS']) != 'off')) ? 'https://' : 'http://'; 275 276 return $protocol; 277 } 278 279 /** 280 * Returns the set protocol according to configuration (http[s]). 281 * 282 * @param bool $use_ssl true if require ssl 283 * 284 * @return string (http|https) 285 */ 286 public static function getProtocol($use_ssl = null) 287 { 288 return null !== $use_ssl && $use_ssl ? 'https://' : 'http://'; 289 } 290 291 /** 292 * Returns the <b>current</b> host used, with the protocol (http or https) if $http is true 293 * This function should not be used to choose http or https domain name. 294 * Use Tools::getShopDomain() or Tools::getShopDomainSsl instead. 295 * 296 * @param bool $http 297 * @param bool $entities 298 * @param bool $ignore_port 299 * 300 * @return string host 301 */ 302 public static function getHttpHost($http = false, $entities = false, $ignore_port = false) 303 { 304 $httpHost = ''; 305 if (array_key_exists('HTTP_HOST', $_SERVER)) { 306 $httpHost = $_SERVER['HTTP_HOST']; 307 } 308 309 $host = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $httpHost); 310 if ($ignore_port && $pos = strpos($host, ':')) { 311 $host = substr($host, 0, $pos); 312 } 313 if ($entities) { 314 $host = htmlspecialchars($host, ENT_COMPAT, 'UTF-8'); 315 } 316 if ($http) { 317 $host = (Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://') . $host; 318 } 319 320 return $host; 321 } 322 323 /** 324 * Returns domain name according to configuration and ignoring ssl. 325 * 326 * @param bool $http if true, return domain name with protocol 327 * @param bool $entities if true, convert special chars to HTML entities 328 * 329 * @return string domain 330 */ 331 public static function getShopDomain($http = false, $entities = false) 332 { 333 if (!$domain = ShopUrl::getMainShopDomain()) { 334 $domain = Tools::getHttpHost(); 335 } 336 if ($entities) { 337 $domain = htmlspecialchars($domain, ENT_COMPAT, 'UTF-8'); 338 } 339 if ($http) { 340 $domain = 'http://' . $domain; 341 } 342 343 return $domain; 344 } 345 346 /** 347 * Returns domain name according to configuration and depending on ssl activation. 348 * 349 * @param bool $http if true, return domain name with protocol 350 * @param bool $entities if true, convert special chars to HTML entities 351 * 352 * @return string domain 353 */ 354 public static function getShopDomainSsl($http = false, $entities = false) 355 { 356 if (!$domain = ShopUrl::getMainShopDomainSSL()) { 357 $domain = Tools::getHttpHost(); 358 } 359 if ($entities) { 360 $domain = htmlspecialchars($domain, ENT_COMPAT, 'UTF-8'); 361 } 362 if ($http) { 363 $domain = (Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://') . $domain; 364 } 365 366 return $domain; 367 } 368 369 /** 370 * Get the server variable SERVER_NAME. 371 * Relies on $_SERVER 372 * 373 * @return string server name 374 */ 375 public static function getServerName() 376 { 377 if (isset($_SERVER['HTTP_X_FORWARDED_SERVER']) && $_SERVER['HTTP_X_FORWARDED_SERVER']) { 378 return $_SERVER['HTTP_X_FORWARDED_SERVER']; 379 } 380 381 return $_SERVER['SERVER_NAME']; 382 } 383 384 /** 385 * Get the server variable REMOTE_ADDR, or the first ip of HTTP_X_FORWARDED_FOR (when using proxy). 386 * 387 * @return string $remote_addr ip of client 388 */ 389 public static function getRemoteAddr() 390 { 391 if (function_exists('apache_request_headers')) { 392 $headers = apache_request_headers(); 393 } else { 394 $headers = $_SERVER; 395 } 396 397 if (array_key_exists('X-Forwarded-For', $headers)) { 398 $_SERVER['HTTP_X_FORWARDED_FOR'] = $headers['X-Forwarded-For']; 399 } 400 401 if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] && (!isset($_SERVER['REMOTE_ADDR']) 402 || preg_match('/^127\..*/i', trim($_SERVER['REMOTE_ADDR'])) || preg_match('/^172\.(1[6-9]|2\d|30|31)\..*/i', trim($_SERVER['REMOTE_ADDR'])) 403 || preg_match('/^192\.168\.*/i', trim($_SERVER['REMOTE_ADDR'])) || preg_match('/^10\..*/i', trim($_SERVER['REMOTE_ADDR'])))) { 404 if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')) { 405 $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); 406 407 return $ips[0]; 408 } else { 409 return $_SERVER['HTTP_X_FORWARDED_FOR']; 410 } 411 } else { 412 return $_SERVER['REMOTE_ADDR']; 413 } 414 } 415 416 /** 417 * Check if the current page use SSL connection on not. 418 * Relies on $_SERVER global being filled 419 * 420 * @return bool true if SSL is used 421 */ 422 public static function usingSecureMode() 423 { 424 if (isset($_SERVER['HTTPS'])) { 425 return in_array(Tools::strtolower($_SERVER['HTTPS']), [1, 'on']); 426 } 427 // $_SERVER['SSL'] exists only in some specific configuration 428 if (isset($_SERVER['SSL'])) { 429 return in_array(Tools::strtolower($_SERVER['SSL']), [1, 'on']); 430 } 431 // $_SERVER['REDIRECT_HTTPS'] exists only in some specific configuration 432 if (isset($_SERVER['REDIRECT_HTTPS'])) { 433 return in_array(Tools::strtolower($_SERVER['REDIRECT_HTTPS']), [1, 'on']); 434 } 435 if (isset($_SERVER['HTTP_SSL'])) { 436 return in_array(Tools::strtolower($_SERVER['HTTP_SSL']), [1, 'on']); 437 } 438 if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { 439 return Tools::strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'; 440 } 441 442 return false; 443 } 444 445 /** 446 * Get the current url prefix protocol (https/http). 447 * 448 * @return string protocol 449 */ 450 public static function getCurrentUrlProtocolPrefix() 451 { 452 if (Tools::usingSecureMode()) { 453 return 'https://'; 454 } else { 455 return 'http://'; 456 } 457 } 458 459 /** 460 * Returns a safe URL referrer. 461 * 462 * @param string $referrer URL referrer 463 * 464 * @return string secured referrer 465 */ 466 public static function secureReferrer($referrer) 467 { 468 if (static::urlBelongsToShop($referrer)) { 469 return $referrer; 470 } 471 472 return __PS_BASE_URI__; 473 } 474 475 /** 476 * Indicates if the provided URL belongs to this shop (relative urls count as belonging to the shop). 477 * 478 * @param string $url 479 * 480 * @return bool 481 */ 482 public static function urlBelongsToShop($url) 483 { 484 $urlHost = Tools::extractHost($url); 485 486 return empty($urlHost) || $urlHost === Tools::getServerName(); 487 } 488 489 /** 490 * Safely extracts the host part from an URL. 491 * 492 * @param string $url 493 * 494 * @return string 495 */ 496 public static function extractHost($url) 497 { 498 $parsed = parse_url($url); 499 if (!is_array($parsed)) { 500 return $url; 501 } 502 if (empty($parsed['host']) || empty($parsed['scheme'])) { 503 return ''; 504 } 505 506 return $parsed['host']; 507 } 508 509 /** 510 * Get a value from $_POST / $_GET 511 * if unavailable, take a default value. 512 * 513 * @param string $key Value key 514 * @param mixed $default_value (optional) 515 * 516 * @return mixed Value 517 */ 518 public static function getValue($key, $default_value = false) 519 { 520 if (empty($key) || !is_string($key)) { 521 return false; 522 } 523 524 if (getenv('kernel.environment') === 'test' && self::$request instanceof Request) { 525 $value = self::$request->request->get($key, self::$request->query->get($key, $default_value)); 526 } elseif (isset($_POST[$key]) || isset($_GET[$key])) { 527 $value = isset($_POST[$key]) ? $_POST[$key] : $_GET[$key]; 528 } elseif (isset(static::$fallbackParameters[$key])) { 529 $value = static::$fallbackParameters[$key]; 530 } 531 532 if (!isset($value)) { 533 $value = $default_value; 534 } 535 536 if (is_string($value)) { 537 return urldecode(preg_replace('/((\%5C0+)|(\%00+))/i', '', urlencode($value))); 538 } 539 540 return $value; 541 } 542 543 /** 544 * Get all values from $_POST/$_GET. 545 * 546 * @return mixed 547 */ 548 public static function getAllValues() 549 { 550 return $_POST + $_GET; 551 } 552 553 /** 554 * Checks if a key exists either in $_POST or $_GET. 555 * 556 * @param string $key 557 * 558 * @return bool 559 */ 560 public static function getIsset($key) 561 { 562 if (!is_string($key)) { 563 return false; 564 } 565 566 return isset($_POST[$key]) || isset($_GET[$key]); 567 } 568 569 /** 570 * Change language in cookie while clicking on a flag. 571 * 572 * @return string iso code 573 */ 574 public static function setCookieLanguage($cookie = null) 575 { 576 if (!$cookie) { 577 $cookie = Context::getContext()->cookie; 578 } 579 /* If language does not exist or is disabled, erase it */ 580 if ($cookie->id_lang) { 581 $lang = new Language((int) $cookie->id_lang); 582 if (!Validate::isLoadedObject($lang) || !$lang->active || !$lang->isAssociatedToShop()) { 583 $cookie->id_lang = null; 584 } 585 } 586 587 if (!Configuration::get('PS_DETECT_LANG')) { 588 unset($cookie->detect_language); 589 } 590 591 /* Automatically detect language if not already defined, detect_language is set in Cookie::update */ 592 if (!Tools::getValue('isolang') && !Tools::getValue('id_lang') && (!$cookie->id_lang || isset($cookie->detect_language)) 593 && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 594 $array = explode(',', Tools::strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'])); 595 $string = $array[0]; 596 597 if (Validate::isLanguageCode($string)) { 598 $lang = Language::getLanguageByIETFCode($string); 599 if (Validate::isLoadedObject($lang) && $lang->active && $lang->isAssociatedToShop()) { 600 Context::getContext()->language = $lang; 601 $cookie->id_lang = (int) $lang->id; 602 } 603 } 604 } 605 606 if (isset($cookie->detect_language)) { 607 unset($cookie->detect_language); 608 } 609 610 /* If language file not present, you must use default language file */ 611 if (!$cookie->id_lang || !Validate::isUnsignedId($cookie->id_lang)) { 612 $cookie->id_lang = (int) Configuration::get('PS_LANG_DEFAULT'); 613 } 614 615 $iso = Language::getIsoById((int) $cookie->id_lang); 616 @include_once _PS_THEME_DIR_ . 'lang/' . $iso . '.php'; 617 618 return $iso; 619 } 620 621 /** 622 * If necessary change cookie language ID and context language. 623 * 624 * @param Context|null $context 625 * 626 * @throws PrestaShopDatabaseException 627 * @throws PrestaShopException 628 */ 629 public static function switchLanguage(Context $context = null) 630 { 631 if (null === $context) { 632 $context = Context::getContext(); 633 } 634 635 // On PrestaShop installations Dispatcher::__construct() gets called (and so Tools::switchLanguage()) 636 // Stop in this case by checking the cookie 637 if (!isset($context->cookie)) { 638 return; 639 } 640 641 if ( 642 ($iso = Tools::getValue('isolang')) && 643 Validate::isLanguageIsoCode($iso) && 644 ($id_lang = (int) Language::getIdByIso($iso)) 645 ) { 646 $_GET['id_lang'] = $id_lang; 647 } 648 649 // Only switch if new ID is different from old ID 650 $newLanguageId = (int) Tools::getValue('id_lang'); 651 652 if ( 653 Validate::isUnsignedId($newLanguageId) && 654 $newLanguageId !== 0 && 655 $context->cookie->id_lang !== $newLanguageId 656 ) { 657 $context->cookie->id_lang = $newLanguageId; 658 $language = new Language($newLanguageId); 659 if (Validate::isLoadedObject($language) && $language->active && $language->isAssociatedToShop()) { 660 $context->language = $language; 661 } 662 } 663 664 Tools::setCookieLanguage($context->cookie); 665 } 666 667 public static function getCountry($address = null) 668 { 669 $countryId = Tools::getValue('id_country'); 670 if (Validate::isInt($countryId) 671 && (int) $countryId > 0 672 && !empty(Country::getIsoById((int) $countryId)) 673 ) { 674 return (int) $countryId; 675 } 676 677 if (!empty($address->id_country) && (int) $address->id_country > 0) { 678 return (int) $address->id_country; 679 } 680 681 return (int) Configuration::get('PS_COUNTRY_DEFAULT'); 682 } 683 684 /** 685 * Set cookie currency from POST or default currency. 686 * 687 * @return Currency object 688 */ 689 public static function setCurrency($cookie) 690 { 691 if (Tools::isSubmit('SubmitCurrency') && ($id_currency = Tools::getValue('id_currency'))) { 692 /** @var Currency $currency */ 693 $currency = Currency::getCurrencyInstance((int) $id_currency); 694 if (is_object($currency) && $currency->id && !$currency->deleted && $currency->isAssociatedToShop()) { 695 $cookie->id_currency = (int) $currency->id; 696 } 697 } 698 699 $currency = null; 700 if ((int) $cookie->id_currency) { 701 $currency = Currency::getCurrencyInstance((int) $cookie->id_currency); 702 } 703 if (!Validate::isLoadedObject($currency) || (bool) $currency->deleted || !(bool) $currency->active) { 704 $currency = Currency::getCurrencyInstance(Configuration::get('PS_CURRENCY_DEFAULT')); 705 } 706 707 $cookie->id_currency = (int) $currency->id; 708 if ($currency->isAssociatedToShop()) { 709 return $currency; 710 } else { 711 // get currency from context 712 $currency = Shop::getEntityIds('currency', Context::getContext()->shop->id, true, true); 713 if (isset($currency[0]) && $currency[0]['id_currency']) { 714 $cookie->id_currency = $currency[0]['id_currency']; 715 716 return Currency::getCurrencyInstance((int) $cookie->id_currency); 717 } 718 } 719 720 return $currency; 721 } 722 723 /** 724 * Return the CLDR associated with the context or given language_code. 725 * 726 * @see Tools::getContextLocale 727 * @deprecated since PrestaShop 1.7.6.0 728 * 729 * @param Context|null $context 730 * @param null $language_code 731 * 732 * @throws PrestaShopException 733 */ 734 public static function getCldr(Context $context = null, $language_code = null) 735 { 736 throw new PrestaShopException('This CLDR library has been removed. See Tools::getContextLocale instead.'); 737 } 738 739 /** 740 * Return price with currency sign for a given product. 741 * 742 * @deprecated Since 1.7.6.0. Please use Locale::formatPrice() instead 743 * @see PrestaShop\PrestaShop\Core\Localization\Locale 744 * 745 * @param float $price Product price 746 * @param int|Currency|array|null $currency Current currency (object, id_currency, NULL => context currency) 747 * @param bool $no_utf8 Not used anymore 748 * @param Context|null $context 749 * 750 * @return string Price correctly formatted (sign, decimal separator...) 751 * if you modify this function, don't forget to modify the Javascript function formatCurrency (in tools.js) 752 * 753 * @throws LocalizationException 754 */ 755 public static function displayPrice($price, $currency = null, $no_utf8 = false, Context $context = null) 756 { 757 @trigger_error( 758 'Tools::displayPrice() is deprecated since version 1.7.6.0. ' 759 . 'Use ' . Locale::class . '::formatPrice() instead.', 760 E_USER_DEPRECATED 761 ); 762 763 if (!is_numeric($price)) { 764 return $price; 765 } 766 767 $context = $context ?: Context::getContext(); 768 $currency = $currency ?: $context->currency; 769 770 if (is_int($currency)) { 771 $currency = Currency::getCurrencyInstance($currency); 772 } 773 774 $locale = static::getContextLocale($context); 775 $currencyCode = is_array($currency) ? $currency['iso_code'] : $currency->iso_code; 776 777 return $locale->formatPrice($price, $currencyCode); 778 } 779 780 /** 781 * Return current locale 782 * 783 * @param Context $context 784 * 785 * @return Locale 786 * 787 * @throws Exception 788 */ 789 public static function getContextLocale(Context $context) 790 { 791 $locale = $context->getCurrentLocale(); 792 if (null !== $locale) { 793 return $locale; 794 } 795 796 $containerFinder = new ContainerFinder($context); 797 $container = $containerFinder->getContainer(); 798 if (null === $context->container) { 799 $context->container = $container; 800 } 801 802 /** @var LocaleRepository $localeRepository */ 803 $localeRepository = $container->get(self::SERVICE_LOCALE_REPOSITORY); 804 $locale = $localeRepository->getLocale( 805 $context->language->getLocale() 806 ); 807 808 return $locale; 809 } 810 811 /** 812 * Returns a well formatted number. 813 * 814 * @deprecated Since 1.7.6.0. Please use Locale::formatNumber() instead 815 * @see Locale 816 * 817 * @param float $number The number to format 818 * @param null $currency not used anymore 819 * 820 * @return string The formatted number 821 * 822 * @throws Exception 823 * @throws LocalizationException 824 */ 825 public static function displayNumber($number, $currency = null) 826 { 827 @trigger_error( 828 'Tools::displayNumber() is deprecated since version 1.7.5.0. ' 829 . 'Use ' . Locale::class . ' instead.', 830 E_USER_DEPRECATED 831 ); 832 833 $context = Context::getContext(); 834 $locale = static::getContextLocale($context); 835 836 return $locale->formatNumber($number); 837 } 838 839 public static function displayPriceSmarty($params, &$smarty) 840 { 841 $context = Context::getContext(); 842 $locale = static::getContextLocale($context); 843 if (array_key_exists('currency', $params)) { 844 $currency = Currency::getCurrencyInstance((int) $params['currency']); 845 if (Validate::isLoadedObject($currency)) { 846 return $locale->formatPrice($params['price'], $currency->iso_code); 847 } 848 } 849 850 return $locale->formatPrice($params['price'], $context->currency->iso_code); 851 } 852 853 /** 854 * Return price converted. 855 * 856 * @deprecated since 1.7.4 use convertPriceToCurrency() 857 * 858 * @param float|null $price Product price 859 * @param object|array $currency Current currency object 860 * @param bool $to_currency convert to currency or from currency to default currency 861 * @param Context $context 862 * 863 * @return float|null Price 864 */ 865 public static function convertPrice($price, $currency = null, $to_currency = true, Context $context = null) 866 { 867 $default_currency = (int) Configuration::get('PS_CURRENCY_DEFAULT'); 868 869 if (!$context) { 870 $context = Context::getContext(); 871 } 872 if ($currency === null) { 873 $currency = $context->currency; 874 } elseif (is_numeric($currency)) { 875 $currency = Currency::getCurrencyInstance($currency); 876 } 877 878 $c_id = (is_array($currency) ? $currency['id_currency'] : $currency->id); 879 $c_rate = (is_array($currency) ? $currency['conversion_rate'] : $currency->conversion_rate); 880 881 if ($c_id != $default_currency) { 882 if ($to_currency) { 883 $price *= $c_rate; 884 } else { 885 $price /= $c_rate; 886 } 887 } 888 889 return $price; 890 } 891 892 /** 893 * Implement array_replace for PHP <= 5.2. 894 * 895 * @return array|mixed|null 896 * 897 * @deprecated since version 1.7.4.0, to be removed. 898 */ 899 public static function array_replace() 900 { 901 Tools::displayAsDeprecated('Use PHP\'s array_replace() instead'); 902 if (!function_exists('array_replace')) { 903 $args = func_get_args(); 904 $num_args = func_num_args(); 905 $res = []; 906 for ($i = 0; $i < $num_args; ++$i) { 907 if (is_array($args[$i])) { 908 foreach ($args[$i] as $key => $val) { 909 $res[$key] = $val; 910 } 911 } else { 912 trigger_error(__FUNCTION__ . '(): Argument #' . ($i + 1) . ' is not an array', E_USER_WARNING); 913 914 return null; 915 } 916 } 917 918 return $res; 919 } else { 920 return call_user_func_array('array_replace', func_get_args()); 921 } 922 } 923 924 /** 925 * Convert amount from a currency to an other currency automatically. 926 * 927 * @param float $amount 928 * @param Currency $currency_from if null we used the default currency 929 * @param Currency $currency_to if null we used the default currency 930 */ 931 public static function convertPriceFull($amount, Currency $currency_from = null, Currency $currency_to = null) 932 { 933 if ($currency_from == $currency_to) { 934 return $amount; 935 } 936 937 if ($currency_from === null) { 938 $currency_from = new Currency(Configuration::get('PS_CURRENCY_DEFAULT')); 939 } 940 941 if ($currency_to === null) { 942 $currency_to = new Currency(Configuration::get('PS_CURRENCY_DEFAULT')); 943 } 944 945 if ($currency_from->id == Configuration::get('PS_CURRENCY_DEFAULT')) { 946 $amount *= $currency_to->conversion_rate; 947 } else { 948 $conversion_rate = ($currency_from->conversion_rate == 0 ? 1 : $currency_from->conversion_rate); 949 // Convert amount to default currency (using the old currency rate) 950 $amount = $amount / $conversion_rate; 951 // Convert to new currency 952 $amount *= $currency_to->conversion_rate; 953 } 954 955 return Tools::ps_round($amount, Context::getContext()->getComputingPrecision()); 956 } 957 958 /** 959 * Display date regarding to language preferences. 960 * 961 * @param array $params Date, format... 962 * @param object $smarty Smarty object for language preferences 963 * 964 * @return string Date 965 */ 966 public static function dateFormat($params, &$smarty) 967 { 968 return Tools::displayDate($params['date'], null, (isset($params['full']) ? $params['full'] : false)); 969 } 970 971 /** 972 * Display date regarding to language preferences. 973 * 974 * @param string $date Date to display format UNIX 975 * @param int $id_lang Language id DEPRECATED 976 * @param bool $full With time or not (optional) 977 * @param string $separator DEPRECATED 978 * 979 * @return string Date 980 */ 981 public static function displayDate($date, $id_lang = null, $full = false, $separator = null) 982 { 983 if ($id_lang !== null) { 984 Tools::displayParameterAsDeprecated('id_lang'); 985 } 986 if ($separator !== null) { 987 Tools::displayParameterAsDeprecated('separator'); 988 } 989 990 if (!$date || !($time = strtotime($date))) { 991 return $date; 992 } 993 994 if ($date == '0000-00-00 00:00:00' || $date == '0000-00-00') { 995 return ''; 996 } 997 998 if (!Validate::isDate($date) || !Validate::isBool($full)) { 999 throw new PrestaShopException('Invalid date'); 1000 } 1001 1002 $context = Context::getContext(); 1003 $date_format = ($full ? $context->language->date_format_full : $context->language->date_format_lite); 1004 1005 return date($date_format, $time); 1006 } 1007 1008 /** 1009 * Get localized date format. 1010 * 1011 * @return string Date format 1012 */ 1013 public static function getDateFormat() 1014 { 1015 $format = Context::getContext()->language->date_format_lite; 1016 $search = ['d', 'm', 'Y']; 1017 $replace = ['DD', 'MM', 'YYYY']; 1018 $format = str_replace($search, $replace, $format); 1019 1020 return $format; 1021 } 1022 1023 /** 1024 * Get formatted date. 1025 * 1026 * @param string $date_str Date string 1027 * @param bool $full With time or not (optional) 1028 * 1029 * @return string Formatted date 1030 */ 1031 public static function formatDateStr($date_str, $full = false) 1032 { 1033 $time = strtotime($date_str); 1034 $context = Context::getContext(); 1035 $date_format = ($full ? $context->language->date_format_full : $context->language->date_format_lite); 1036 $date = date($date_format, $time); 1037 1038 return $date; 1039 } 1040 1041 /** 1042 * Sanitize a string. 1043 * 1044 * @param string $string String to sanitize 1045 * @param bool $full String contains HTML or not (optional) 1046 * 1047 * @return string Sanitized string 1048 */ 1049 public static function safeOutput($string, $html = false) 1050 { 1051 if (!$html) { 1052 $string = strip_tags($string); 1053 } 1054 1055 return @Tools::htmlentitiesUTF8($string, ENT_QUOTES); 1056 } 1057 1058 public static function htmlentitiesUTF8($string, $type = ENT_QUOTES) 1059 { 1060 if (is_array($string)) { 1061 return array_map(['Tools', 'htmlentitiesUTF8'], $string); 1062 } 1063 1064 return htmlentities((string) $string, $type, 'utf-8'); 1065 } 1066 1067 public static function htmlentitiesDecodeUTF8($string) 1068 { 1069 if (is_array($string)) { 1070 $string = array_map(['Tools', 'htmlentitiesDecodeUTF8'], $string); 1071 1072 return (string) array_shift($string); 1073 } 1074 1075 return html_entity_decode((string) $string, ENT_QUOTES, 'utf-8'); 1076 } 1077 1078 public static function safePostVars() 1079 { 1080 if (!isset($_POST) || !is_array($_POST)) { 1081 $_POST = []; 1082 } else { 1083 $_POST = array_map(['Tools', 'htmlentitiesUTF8'], $_POST); 1084 } 1085 } 1086 1087 /** 1088 * Delete directory and subdirectories. 1089 * 1090 * @param string $dirname Directory name 1091 */ 1092 public static function deleteDirectory($dirname, $delete_self = true) 1093 { 1094 $dirname = rtrim($dirname, '/') . '/'; 1095 if (file_exists($dirname)) { 1096 if ($files = scandir($dirname, SCANDIR_SORT_NONE)) { 1097 foreach ($files as $file) { 1098 if ($file != '.' && $file != '..' && $file != '.svn') { 1099 if (is_dir($dirname . $file)) { 1100 Tools::deleteDirectory($dirname . $file); 1101 } elseif (file_exists($dirname . $file)) { 1102 unlink($dirname . $file); 1103 } 1104 } 1105 } 1106 1107 if ($delete_self && file_exists($dirname)) { 1108 if (!rmdir($dirname)) { 1109 return false; 1110 } 1111 } 1112 1113 return true; 1114 } 1115 } 1116 1117 return false; 1118 } 1119 1120 /** 1121 * Delete file. 1122 * 1123 * @param string $file File path 1124 * @param array $exclude_files Excluded files 1125 * 1126 * @return bool 1127 */ 1128 public static function deleteFile($file, $exclude_files = []) 1129 { 1130 if (isset($exclude_files) && !is_array($exclude_files)) { 1131 $exclude_files = [$exclude_files]; 1132 } 1133 1134 if (file_exists($file) && is_file($file) && array_search(basename($file), $exclude_files) === false) { 1135 return unlink($file); 1136 } 1137 1138 return false; 1139 } 1140 1141 /** 1142 * Clear XML cache folder. 1143 */ 1144 public static function clearXMLCache() 1145 { 1146 foreach (scandir(_PS_ROOT_DIR_ . '/config/xml', SCANDIR_SORT_NONE) as $file) { 1147 $path_info = pathinfo($file, PATHINFO_EXTENSION); 1148 if (($path_info == 'xml') && ($file != 'default.xml')) { 1149 self::deleteFile(_PS_ROOT_DIR_ . '/config/xml/' . $file); 1150 } 1151 } 1152 } 1153 1154 /** 1155 * Depending on _PS_MODE_DEV_ throws an exception or returns a error message. 1156 * 1157 * @param string|null $errorMessage Error message (defaults to "Fatal error") 1158 * @param bool $htmlentities DEPRECATED since 1.7.4.0 1159 * @param Context|null $context DEPRECATED since 1.7.4.0 1160 * 1161 * @return string 1162 * 1163 * @throws PrestaShopException If _PS_MODE_DEV_ is enabled 1164 */ 1165 public static function displayError($errorMessage = null, $htmlentities = null, Context $context = null) 1166 { 1167 header('HTTP/1.1 500 Internal Server Error', true, 500); 1168 if (null !== $htmlentities) { 1169 self::displayParameterAsDeprecated('htmlentities'); 1170 } 1171 if (null !== $context) { 1172 self::displayParameterAsDeprecated('context'); 1173 } 1174 1175 if (null === $errorMessage) { 1176 $errorMessage = Context::getContext() 1177 ->getTranslator() 1178 ->trans('Fatal error', [], 'Admin.Notifications.Error'); 1179 } 1180 1181 if (_PS_MODE_DEV_) { 1182 throw new PrestaShopException($errorMessage); 1183 } 1184 1185 return $errorMessage; 1186 } 1187 1188 /** 1189 * Display an error with detailed object. 1190 * 1191 * @param mixed $object 1192 * @param bool $kill 1193 * 1194 * @return $object if $kill = false; 1195 */ 1196 public static function dieObject($object, $kill = true) 1197 { 1198 dump($object); 1199 1200 if ($kill) { 1201 die('END'); 1202 } 1203 1204 return $object; 1205 } 1206 1207 public static function debug_backtrace($start = 0, $limit = null) 1208 { 1209 $backtrace = debug_backtrace(); 1210 array_shift($backtrace); 1211 for ($i = 0; $i < $start; ++$i) { 1212 array_shift($backtrace); 1213 } 1214 1215 echo ' 1216 <div style="margin:10px;padding:10px;border:1px solid #666666"> 1217 <ul>'; 1218 $i = 0; 1219 foreach ($backtrace as $id => $trace) { 1220 if ((int) $limit && (++$i > $limit)) { 1221 break; 1222 } 1223 $relative_file = (isset($trace['file'])) ? 'in /' . ltrim(str_replace([_PS_ROOT_DIR_, '\\'], ['', '/'], $trace['file']), '/') : ''; 1224 $current_line = (isset($trace['line'])) ? ':' . $trace['line'] : ''; 1225 1226 echo '<li> 1227 <b>' . ((isset($trace['class'])) ? $trace['class'] : '') . ((isset($trace['type'])) ? $trace['type'] : '') . $trace['function'] . '</b> 1228 ' . $relative_file . $current_line . ' 1229 </li>'; 1230 } 1231 echo '</ul> 1232 </div>'; 1233 } 1234 1235 /** 1236 * Prints object information into error log. 1237 * 1238 * @see error_log() 1239 * 1240 * @param mixed $object 1241 * @param int|null $message_type 1242 * @param string|null $destination 1243 * @param string|null $extra_headers 1244 * 1245 * @return bool 1246 */ 1247 public static function error_log($object, $message_type = null, $destination = null, $extra_headers = null) 1248 { 1249 return error_log(print_r($object, true), $message_type, $destination, $extra_headers); 1250 } 1251 1252 /** 1253 * Check if submit has been posted. 1254 * 1255 * @param string $submit submit name 1256 */ 1257 public static function isSubmit($submit) 1258 { 1259 return 1260 isset($_POST[$submit]) || isset($_POST[$submit . '_x']) || isset($_POST[$submit . '_y']) 1261 || isset($_GET[$submit]) || isset($_GET[$submit . '_x']) || isset($_GET[$submit . '_y']); 1262 } 1263 1264 /** 1265 * Hash password. 1266 * 1267 * @param string $passwd String to hash 1268 * 1269 * @return string Hashed password 1270 * 1271 * @deprecated 1.7.0 1272 */ 1273 public static function encrypt($passwd) 1274 { 1275 return self::hash($passwd); 1276 } 1277 1278 /** 1279 * Hash password. 1280 * 1281 * @param string $passwd String to has 1282 * 1283 * @return string Hashed password 1284 * 1285 * @since 1.7.0 1286 */ 1287 public static function hash($passwd) 1288 { 1289 return md5(_COOKIE_KEY_ . $passwd); 1290 } 1291 1292 /** 1293 * Hash data string. 1294 * 1295 * @param string $data String to encrypt 1296 * 1297 * @return string Hashed IV 1298 * 1299 * @deprecated 1.7.0 1300 */ 1301 public static function encryptIV($data) 1302 { 1303 return self::hashIV($data); 1304 } 1305 1306 /** 1307 * Hash data string. 1308 * 1309 * @param string $data String to encrypt 1310 * 1311 * @return string Hashed IV 1312 * 1313 * @since 1.7.0 1314 */ 1315 public static function hashIV($data) 1316 { 1317 return md5(_COOKIE_IV_ . $data); 1318 } 1319 1320 /** 1321 * Get token to prevent CSRF. 1322 * 1323 * @param string $token token to encrypt 1324 * 1325 * @return string 1326 */ 1327 public static function getToken($page = true, Context $context = null) 1328 { 1329 if (!$context) { 1330 $context = Context::getContext(); 1331 } 1332 if ($page === true) { 1333 return Tools::hash($context->customer->id . $context->customer->passwd . $_SERVER['SCRIPT_NAME']); 1334 } else { 1335 return Tools::hash($context->customer->id . $context->customer->passwd . $page); 1336 } 1337 } 1338 1339 /** 1340 * Tokenize a string. 1341 * 1342 * @param string $string String to encrypt 1343 * 1344 * @return string|bool false if given string is empty 1345 */ 1346 public static function getAdminToken($string) 1347 { 1348 return !empty($string) ? Tools::hash($string) : false; 1349 } 1350 1351 /** 1352 * @param string $tab 1353 * @param Context $context 1354 * 1355 * @return bool|string 1356 */ 1357 public static function getAdminTokenLite($tab, Context $context = null) 1358 { 1359 if (!$context) { 1360 $context = Context::getContext(); 1361 } 1362 1363 return Tools::getAdminToken($tab . (int) Tab::getIdFromClassName($tab) . (int) $context->employee->id); 1364 } 1365 1366 /** 1367 * @param array $params 1368 * @param $smarty unused parameter, please ignore (@todo: remove in next major) 1369 * 1370 * @return bool|string 1371 */ 1372 public static function getAdminTokenLiteSmarty($params, &$smarty = null) 1373 { 1374 $context = Context::getContext(); 1375 1376 return Tools::getAdminToken($params['tab'] . (int) Tab::getIdFromClassName($params['tab']) . (int) $context->employee->id); 1377 } 1378 1379 /** 1380 * Get a valid URL to use from BackOffice. 1381 * 1382 * @param string $url An URL to use in BackOffice 1383 * @param bool $entities Set to true to use htmlentities function on URL param 1384 * 1385 * @return string 1386 */ 1387 public static function getAdminUrl($url = null, $entities = false) 1388 { 1389 $link = Tools::getHttpHost(true) . __PS_BASE_URI__; 1390 1391 if (isset($url)) { 1392 $link .= ($entities ? Tools::htmlentitiesUTF8($url) : $url); 1393 } 1394 1395 return $link; 1396 } 1397 1398 /** 1399 * Get a valid image URL to use from BackOffice. 1400 * 1401 * @param string $image Image name 1402 * @param bool $entities Set to true to use htmlentities function on image param 1403 * 1404 * @return string 1405 */ 1406 public static function getAdminImageUrl($image = null, $entities = false) 1407 { 1408 return Tools::getAdminUrl(basename(_PS_IMG_DIR_) . '/' . $image, $entities); 1409 } 1410 1411 /** 1412 * Return the friendly url from the provided string. 1413 * 1414 * @param string $str 1415 * @param bool $utf8_decode (deprecated) 1416 * 1417 * @return string 1418 */ 1419 public static function link_rewrite($str, $utf8_decode = null) 1420 { 1421 if ($utf8_decode !== null) { 1422 Tools::displayParameterAsDeprecated('utf8_decode'); 1423 } 1424 1425 return Tools::str2url($str); 1426 } 1427 1428 /** 1429 * Return a friendly url made from the provided string 1430 * If the mbstring library is available, the output is the same as the js function of the same name. 1431 * 1432 * @param string $str 1433 * 1434 * @return string|bool 1435 */ 1436 public static function str2url($str) 1437 { 1438 static $array_str = []; 1439 static $allow_accented_chars = null; 1440 static $has_mb_strtolower = null; 1441 1442 if ($has_mb_strtolower === null) { 1443 $has_mb_strtolower = function_exists('mb_strtolower'); 1444 } 1445 1446 if (!is_string($str)) { 1447 return false; 1448 } 1449 1450 if (isset($array_str[$str])) { 1451 return $array_str[$str]; 1452 } 1453 1454 if ($str == '') { 1455 return ''; 1456 } 1457 1458 if ($allow_accented_chars === null) { 1459 $allow_accented_chars = Configuration::get('PS_ALLOW_ACCENTED_CHARS_URL'); 1460 } 1461 1462 $return_str = trim($str); 1463 1464 if ($has_mb_strtolower) { 1465 $return_str = mb_strtolower($return_str, 'utf-8'); 1466 } 1467 if (!$allow_accented_chars) { 1468 $return_str = Tools::replaceAccentedChars($return_str); 1469 } 1470 1471 // Remove all non-whitelist chars. 1472 if ($allow_accented_chars) { 1473 $return_str = preg_replace('/[^a-zA-Z0-9\s\'\:\/\[\]\-\p{L}]/u', '', $return_str); 1474 } else { 1475 $return_str = preg_replace('/[^a-zA-Z0-9\s\'\:\/\[\]\-]/', '', $return_str); 1476 } 1477 1478 $return_str = preg_replace('/[\s\'\:\/\[\]\-]+/', ' ', $return_str); 1479 $return_str = str_replace([' ', '/'], '-', $return_str); 1480 1481 // If it was not possible to lowercase the string with mb_strtolower, we do it after the transformations. 1482 // This way we lose fewer special chars. 1483 if (!$has_mb_strtolower) { 1484 $return_str = Tools::strtolower($return_str); 1485 } 1486 1487 $array_str[$str] = $return_str; 1488 1489 return $return_str; 1490 } 1491 1492 /** 1493 * Replace all accented chars by their equivalent non accented chars. 1494 * 1495 * @param string $str 1496 * 1497 * @return string 1498 */ 1499 public static function replaceAccentedChars($str) 1500 { 1501 /* One source among others: 1502 http://www.tachyonsoft.com/uc0000.htm 1503 http://www.tachyonsoft.com/uc0001.htm 1504 http://www.tachyonsoft.com/uc0004.htm 1505 */ 1506 $patterns = [ 1507 /* Lowercase */ 1508 /* a */ '/[\x{00E0}\x{00E1}\x{00E2}\x{00E3}\x{00E4}\x{00E5}\x{0101}\x{0103}\x{0105}\x{0430}\x{00C0}-\x{00C3}\x{1EA0}-\x{1EB7}]/u', 1509 /* b */ '/[\x{0431}]/u', 1510 /* c */ '/[\x{00E7}\x{0107}\x{0109}\x{010D}\x{0446}]/u', 1511 /* d */ '/[\x{010F}\x{0111}\x{0434}\x{0110}\x{00F0}]/u', 1512 /* e */ '/[\x{00E8}\x{00E9}\x{00EA}\x{00EB}\x{0113}\x{0115}\x{0117}\x{0119}\x{011B}\x{0435}\x{044D}\x{00C8}-\x{00CA}\x{1EB8}-\x{1EC7}]/u', 1513 /* f */ '/[\x{0444}]/u', 1514 /* g */ '/[\x{011F}\x{0121}\x{0123}\x{0433}\x{0491}]/u', 1515 /* h */ '/[\x{0125}\x{0127}]/u', 1516 /* i */ '/[\x{00EC}\x{00ED}\x{00EE}\x{00EF}\x{0129}\x{012B}\x{012D}\x{012F}\x{0131}\x{0438}\x{0456}\x{00CC}\x{00CD}\x{1EC8}-\x{1ECB}\x{0128}]/u', 1517 /* j */ '/[\x{0135}\x{0439}]/u', 1518 /* k */ '/[\x{0137}\x{0138}\x{043A}]/u', 1519 /* l */ '/[\x{013A}\x{013C}\x{013E}\x{0140}\x{0142}\x{043B}]/u', 1520 /* m */ '/[\x{043C}]/u', 1521 /* n */ '/[\x{00F1}\x{0144}\x{0146}\x{0148}\x{0149}\x{014B}\x{043D}]/u', 1522 /* o */ '/[\x{00F2}\x{00F3}\x{00F4}\x{00F5}\x{00F6}\x{00F8}\x{014D}\x{014F}\x{0151}\x{043E}\x{00D2}-\x{00D5}\x{01A0}\x{01A1}\x{1ECC}-\x{1EE3}]/u', 1523 /* p */ '/[\x{043F}]/u', 1524 /* r */ '/[\x{0155}\x{0157}\x{0159}\x{0440}]/u', 1525 /* s */ '/[\x{015B}\x{015D}\x{015F}\x{0161}\x{0441}]/u', 1526 /* ss */ '/[\x{00DF}]/u', 1527 /* t */ '/[\x{0163}\x{0165}\x{0167}\x{0442}]/u', 1528 /* u */ '/[\x{00F9}\x{00FA}\x{00FB}\x{00FC}\x{0169}\x{016B}\x{016D}\x{016F}\x{0171}\x{0173}\x{0443}\x{00D9}-\x{00DA}\x{0168}\x{01AF}\x{01B0}\x{1EE4}-\x{1EF1}]/u', 1529 /* v */ '/[\x{0432}]/u', 1530 /* w */ '/[\x{0175}]/u', 1531 /* y */ '/[\x{00FF}\x{0177}\x{00FD}\x{044B}\x{1EF2}-\x{1EF9}\x{00DD}]/u', 1532 /* z */ '/[\x{017A}\x{017C}\x{017E}\x{0437}]/u', 1533 /* ae */ '/[\x{00E6}]/u', 1534 /* ch */ '/[\x{0447}]/u', 1535 /* kh */ '/[\x{0445}]/u', 1536 /* oe */ '/[\x{0153}]/u', 1537 /* sh */ '/[\x{0448}]/u', 1538 /* shh*/ '/[\x{0449}]/u', 1539 /* ya */ '/[\x{044F}]/u', 1540 /* ye */ '/[\x{0454}]/u', 1541 /* yi */ '/[\x{0457}]/u', 1542 /* yo */ '/[\x{0451}]/u', 1543 /* yu */ '/[\x{044E}]/u', 1544 /* zh */ '/[\x{0436}]/u', 1545 1546 /* Uppercase */ 1547 /* A */ '/[\x{0100}\x{0102}\x{0104}\x{00C0}\x{00C1}\x{00C2}\x{00C3}\x{00C4}\x{00C5}\x{0410}]/u', 1548 /* B */ '/[\x{0411}]/u', 1549 /* C */ '/[\x{00C7}\x{0106}\x{0108}\x{010A}\x{010C}\x{0426}]/u', 1550 /* D */ '/[\x{010E}\x{0110}\x{0414}\x{00D0}]/u', 1551 /* E */ '/[\x{00C8}\x{00C9}\x{00CA}\x{00CB}\x{0112}\x{0114}\x{0116}\x{0118}\x{011A}\x{0415}\x{042D}]/u', 1552 /* F */ '/[\x{0424}]/u', 1553 /* G */ '/[\x{011C}\x{011E}\x{0120}\x{0122}\x{0413}\x{0490}]/u', 1554 /* H */ '/[\x{0124}\x{0126}]/u', 1555 /* I */ '/[\x{0128}\x{012A}\x{012C}\x{012E}\x{0130}\x{0418}\x{0406}]/u', 1556 /* J */ '/[\x{0134}\x{0419}]/u', 1557 /* K */ '/[\x{0136}\x{041A}]/u', 1558 /* L */ '/[\x{0139}\x{013B}\x{013D}\x{0139}\x{0141}\x{041B}]/u', 1559 /* M */ '/[\x{041C}]/u', 1560 /* N */ '/[\x{00D1}\x{0143}\x{0145}\x{0147}\x{014A}\x{041D}]/u', 1561 /* O */ '/[\x{00D3}\x{014C}\x{014E}\x{0150}\x{041E}]/u', 1562 /* P */ '/[\x{041F}]/u', 1563 /* R */ '/[\x{0154}\x{0156}\x{0158}\x{0420}]/u', 1564 /* S */ '/[\x{015A}\x{015C}\x{015E}\x{0160}\x{0421}]/u', 1565 /* T */ '/[\x{0162}\x{0164}\x{0166}\x{0422}]/u', 1566 /* U */ '/[\x{00D9}\x{00DA}\x{00DB}\x{00DC}\x{0168}\x{016A}\x{016C}\x{016E}\x{0170}\x{0172}\x{0423}]/u', 1567 /* V */ '/[\x{0412}]/u', 1568 /* W */ '/[\x{0174}]/u', 1569 /* Y */ '/[\x{0176}\x{042B}]/u', 1570 /* Z */ '/[\x{0179}\x{017B}\x{017D}\x{0417}]/u', 1571 /* AE */ '/[\x{00C6}]/u', 1572 /* CH */ '/[\x{0427}]/u', 1573 /* KH */ '/[\x{0425}]/u', 1574 /* OE */ '/[\x{0152}]/u', 1575 /* SH */ '/[\x{0428}]/u', 1576 /* SHH*/ '/[\x{0429}]/u', 1577 /* YA */ '/[\x{042F}]/u', 1578 /* YE */ '/[\x{0404}]/u', 1579 /* YI */ '/[\x{0407}]/u', 1580 /* YO */ '/[\x{0401}]/u', 1581 /* YU */ '/[\x{042E}]/u', 1582 /* ZH */ '/[\x{0416}]/u', 1583 ]; 1584 1585 // ö to oe 1586 // å to aa 1587 // ä to ae 1588 1589 $replacements = [ 1590 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 'ss', 't', 'u', 'v', 'w', 'y', 'z', 'ae', 'ch', 'kh', 'oe', 'sh', 'shh', 'ya', 'ye', 'yi', 'yo', 'yu', 'zh', 1591 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'Y', 'Z', 'AE', 'CH', 'KH', 'OE', 'SH', 'SHH', 'YA', 'YE', 'YI', 'YO', 'YU', 'ZH', 1592 ]; 1593 1594 return preg_replace($patterns, $replacements, $str); 1595 } 1596 1597 /** 1598 * Truncate strings. 1599 * 1600 * @param string $str 1601 * @param int $max_length Max length 1602 * @param string $suffix Suffix optional 1603 * 1604 * @return string $str truncated 1605 */ 1606 /* CAUTION : Use it only on module hookEvents. 1607 ** For other purposes use the smarty function instead */ 1608 public static function truncate($str, $max_length, $suffix = '...') 1609 { 1610 if (Tools::strlen($str) <= $max_length) { 1611 return $str; 1612 } 1613 $str = utf8_decode($str); 1614 1615 return utf8_encode(substr($str, 0, $max_length - Tools::strlen($suffix)) . $suffix); 1616 } 1617 1618 /*Copied from CakePHP String utility file*/ 1619 public static function truncateString($text, $length = 120, $options = []) 1620 { 1621 $default = [ 1622 'ellipsis' => '...', 'exact' => true, 'html' => true, 1623 ]; 1624 1625 $options = array_merge($default, $options); 1626 extract($options); 1627 /** 1628 * @var string 1629 * @var bool $exact 1630 * @var bool $html 1631 */ 1632 if ($html) { 1633 if (Tools::strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { 1634 return $text; 1635 } 1636 1637 $total_length = Tools::strlen(strip_tags($ellipsis)); 1638 $open_tags = []; 1639 $truncate = ''; 1640 preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER); 1641 1642 foreach ($tags as $tag) { 1643 if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) { 1644 if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) { 1645 array_unshift($open_tags, $tag[2]); 1646 } elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $close_tag)) { 1647 $pos = array_search($close_tag[1], $open_tags); 1648 if ($pos !== false) { 1649 array_splice($open_tags, $pos, 1); 1650 } 1651 } 1652 } 1653 $truncate .= $tag[1]; 1654 $content_length = Tools::strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3])); 1655 1656 if ($content_length + $total_length > $length) { 1657 $left = $length - $total_length; 1658 $entities_length = 0; 1659 1660 if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) { 1661 foreach ($entities[0] as $entity) { 1662 if ($entity[1] + 1 - $entities_length <= $left) { 1663 --$left; 1664 $entities_length += Tools::strlen($entity[0]); 1665 } else { 1666 break; 1667 } 1668 } 1669 } 1670 1671 $truncate .= Tools::substr($tag[3], 0, $left + $entities_length); 1672 1673 break; 1674 } else { 1675 $truncate .= $tag[3]; 1676 $total_length += $content_length; 1677 } 1678 1679 if ($total_length >= $length) { 1680 break; 1681 } 1682 } 1683 } else { 1684 if (Tools::strlen($text) <= $length) { 1685 return $text; 1686 } 1687 1688 $truncate = Tools::substr($text, 0, $length - Tools::strlen($ellipsis)); 1689 } 1690 1691 if (!$exact) { 1692 $spacepos = Tools::strrpos($truncate, ' '); 1693 if ($html) { 1694 $truncate_check = Tools::substr($truncate, 0, $spacepos); 1695 $last_open_tag = Tools::strrpos($truncate_check, '<'); 1696 $last_close_tag = Tools::strrpos($truncate_check, '>'); 1697 1698 if ($last_open_tag > $last_close_tag) { 1699 preg_match_all('/<[\w]+[^>]*>/s', $truncate, $last_tag_matches); 1700 $last_tag = array_pop($last_tag_matches[0]); 1701 $spacepos = Tools::strrpos($truncate, $last_tag) + Tools::strlen($last_tag); 1702 } 1703 1704 $bits = Tools::substr($truncate, $spacepos); 1705 preg_match_all('/<\/([a-z]+)>/', $bits, $dropped_tags, PREG_SET_ORDER); 1706 1707 if (!empty($dropped_tags)) { 1708 if (!empty($open_tags)) { 1709 foreach ($dropped_tags as $closing_tag) { 1710 if (!in_array($closing_tag[1], $open_tags)) { 1711 array_unshift($open_tags, $closing_tag[1]); 1712 } 1713 } 1714 } else { 1715 foreach ($dropped_tags as $closing_tag) { 1716 $open_tags[] = $closing_tag[1]; 1717 } 1718 } 1719 } 1720 } 1721 1722 $truncate = Tools::substr($truncate, 0, $spacepos); 1723 } 1724 1725 $truncate .= $ellipsis; 1726 1727 if ($html) { 1728 foreach ($open_tags as $tag) { 1729 $truncate .= '</' . $tag . '>'; 1730 } 1731 } 1732 1733 return $truncate; 1734 } 1735 1736 public static function normalizeDirectory($directory) 1737 { 1738 return rtrim($directory, '/\\') . DIRECTORY_SEPARATOR; 1739 } 1740 1741 /** 1742 * Generate date form. 1743 * 1744 * @param int $year Year to select 1745 * @param int $month Month to select 1746 * @param int $day Day to select 1747 * 1748 * @return array $tab html data with 3 cells :['days'], ['months'], ['years'] 1749 */ 1750 public static function dateYears() 1751 { 1752 $tab = []; 1753 for ($i = date('Y'); $i >= 1900; --$i) { 1754 $tab[] = $i; 1755 } 1756 1757 return $tab; 1758 } 1759 1760 public static function dateDays() 1761 { 1762 $tab = []; 1763 for ($i = 1; $i != 32; ++$i) { 1764 $tab[] = $i; 1765 } 1766 1767 return $tab; 1768 } 1769 1770 public static function dateMonths() 1771 { 1772 $tab = []; 1773 for ($i = 1; $i != 13; ++$i) { 1774 $tab[$i] = date('F', mktime(0, 0, 0, $i, date('m'), date('Y'))); 1775 } 1776 1777 return $tab; 1778 } 1779 1780 public static function hourGenerate($hours, $minutes, $seconds) 1781 { 1782 return implode(':', [$hours, $minutes, $seconds]); 1783 } 1784 1785 public static function dateFrom($date) 1786 { 1787 $tab = explode(' ', $date); 1788 if (!isset($tab[1])) { 1789 $date .= ' ' . Tools::hourGenerate(0, 0, 0); 1790 } 1791 1792 return $date; 1793 } 1794 1795 public static function dateTo($date) 1796 { 1797 $tab = explode(' ', $date); 1798 if (!isset($tab[1])) { 1799 $date .= ' ' . Tools::hourGenerate(23, 59, 59); 1800 } 1801 1802 return $date; 1803 } 1804 1805 public static function strtolower($str) 1806 { 1807 if (is_array($str)) { 1808 return false; 1809 } 1810 if (function_exists('mb_strtolower')) { 1811 return mb_strtolower($str, 'utf-8'); 1812 } 1813 1814 return strtolower($str); 1815 } 1816 1817 public static function strlen($str, $encoding = 'UTF-8') 1818 { 1819 if (is_array($str)) { 1820 return false; 1821 } 1822 $str = html_entity_decode($str, ENT_COMPAT, 'UTF-8'); 1823 if (function_exists('mb_strlen')) { 1824 return mb_strlen($str, $encoding); 1825 } 1826 1827 return strlen($str); 1828 } 1829 1830 public static function stripslashes($string) 1831 { 1832 return $string; 1833 } 1834 1835 public static function strtoupper($str) 1836 { 1837 if (is_array($str)) { 1838 return false; 1839 } 1840 if (function_exists('mb_strtoupper')) { 1841 return mb_strtoupper($str, 'utf-8'); 1842 } 1843 1844 return strtoupper($str); 1845 } 1846 1847 public static function substr($str, $start, $length = false, $encoding = 'utf-8') 1848 { 1849 if (is_array($str)) { 1850 return false; 1851 } 1852 if (function_exists('mb_substr')) { 1853 return mb_substr($str, (int) $start, ($length === false ? Tools::strlen($str) : (int) $length), $encoding); 1854 } 1855 1856 return substr($str, $start, ($length === false ? Tools::strlen($str) : (int) $length)); 1857 } 1858 1859 public static function strpos($str, $find, $offset = 0, $encoding = 'UTF-8') 1860 { 1861 if (function_exists('mb_strpos')) { 1862 return mb_strpos($str, $find, $offset, $encoding); 1863 } 1864 1865 return strpos($str, $find, $offset); 1866 } 1867 1868 public static function strrpos($str, $find, $offset = 0, $encoding = 'utf-8') 1869 { 1870 if (function_exists('mb_strrpos')) { 1871 return mb_strrpos($str, $find, $offset, $encoding); 1872 } 1873 1874 return strrpos($str, $find, $offset); 1875 } 1876 1877 public static function ucfirst($str) 1878 { 1879 return Tools::strtoupper(Tools::substr($str, 0, 1)) . Tools::substr($str, 1); 1880 } 1881 1882 public static function ucwords($str) 1883 { 1884 if (function_exists('mb_convert_case')) { 1885 return mb_convert_case($str, MB_CASE_TITLE); 1886 } 1887 1888 return ucwords(Tools::strtolower($str)); 1889 } 1890 1891 public static function orderbyPrice(&$array, $order_way) 1892 { 1893 foreach ($array as &$row) { 1894 $row['price_tmp'] = Product::getPriceStatic($row['id_product'], true, ((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int) $row['id_product_attribute'] : null), 2); 1895 } 1896 unset($row); 1897 1898 if (Tools::strtolower($order_way) == 'desc') { 1899 uasort($array, 'cmpPriceDesc'); 1900 } else { 1901 uasort($array, 'cmpPriceAsc'); 1902 } 1903 foreach ($array as &$row) { 1904 unset($row['price_tmp']); 1905 } 1906 } 1907 1908 public static function iconv($from, $to, $string) 1909 { 1910 if (function_exists('iconv')) { 1911 return iconv($from, $to . '//TRANSLIT', str_replace('¥', '¥', str_replace('£', '£', str_replace('€', '€', $string)))); 1912 } 1913 1914 return html_entity_decode(htmlentities($string, ENT_NOQUOTES, $from), ENT_NOQUOTES, $to); 1915 } 1916 1917 public static function isEmpty($field) 1918 { 1919 return $field === '' || $field === null; 1920 } 1921 1922 /** 1923 * returns the rounded value of $value to specified precision, according to your configuration;. 1924 * 1925 * @note : PHP 5.3.0 introduce a 3rd parameter mode in round function 1926 * 1927 * @param float $value 1928 * @param int $precision 1929 * 1930 * @return float 1931 */ 1932 public static function ps_round($value, $precision = 0, $round_mode = null) 1933 { 1934 if ($round_mode === null) { 1935 if (Tools::$round_mode == null) { 1936 Tools::$round_mode = (int) Configuration::get('PS_PRICE_ROUND_MODE'); 1937 } 1938 1939 $round_mode = Tools::$round_mode; 1940 } 1941 1942 switch ($round_mode) { 1943 case PS_ROUND_UP: 1944 return Tools::ceilf($value, $precision); 1945 case PS_ROUND_DOWN: 1946 return Tools::floorf($value, $precision); 1947 case PS_ROUND_HALF_DOWN: 1948 case PS_ROUND_HALF_EVEN: 1949 case PS_ROUND_HALF_ODD: 1950 return Tools::math_round($value, $precision, $round_mode); 1951 case PS_ROUND_HALF_UP: 1952 default: 1953 return Tools::math_round($value, $precision, PS_ROUND_HALF_UP); 1954 } 1955 } 1956 1957 /** 1958 * @param $value 1959 * @param $places 1960 * @param int $mode 1961 * 1962 * @return false|float 1963 */ 1964 public static function math_round($value, $places, $mode = PS_ROUND_HALF_UP) 1965 { 1966 //If PHP_ROUND_HALF_UP exist (PHP 5.3) use it and pass correct mode value (PrestaShop define - 1) 1967 if (defined('PHP_ROUND_HALF_UP')) { 1968 return round($value, $places, $mode - 1); 1969 } 1970 1971 $precision_places = 14 - floor(log10(abs($value))); 1972 $f1 = 10.0 ** (float) abs($places); 1973 1974 /* If the decimal precision guaranteed by FP arithmetic is higher than 1975 * the requested places BUT is small enough to make sure a non-zero value 1976 * is returned, pre-round the result to the precision */ 1977 if ($precision_places > $places && $precision_places - $places < 15) { 1978 $f2 = 10.0 ** (float) abs($precision_places); 1979 1980 if ($precision_places >= 0) { 1981 $tmp_value = $value * $f2; 1982 } else { 1983 $tmp_value = $value / $f2; 1984 } 1985 1986 /* preround the result (tmp_value will always be something * 1e14, 1987 * thus never larger than 1e15 here) */ 1988 $tmp_value = Tools::round_helper($tmp_value, $mode); 1989 /* now correctly move the decimal point */ 1990 $f2 = 10.0 ** (float) abs($places - $precision_places); 1991 /* because places < precision_places */ 1992 $tmp_value = $tmp_value / $f2; 1993 } else { 1994 /* adjust the value */ 1995 if ($places >= 0) { 1996 $tmp_value = $value * $f1; 1997 } else { 1998 $tmp_value = $value / $f1; 1999 } 2000 2001 /* This value is beyond our precision, so rounding it is pointless */ 2002 if (abs($tmp_value) >= 1e15) { 2003 return $value; 2004 } 2005 } 2006 2007 /* round the temp value */ 2008 $tmp_value = Tools::round_helper($tmp_value, $mode); 2009 2010 /* see if it makes sense to use simple division to round the value */ 2011 if (abs($places) < 23) { 2012 if ($places > 0) { 2013 $tmp_value /= $f1; 2014 } else { 2015 $tmp_value *= $f1; 2016 } 2017 } 2018 2019 return $tmp_value; 2020 } 2021 2022 /** 2023 * @param $value 2024 * @param $mode 2025 * 2026 * @return float 2027 */ 2028 public static function round_helper($value, $mode) 2029 { 2030 if ($value >= 0.0) { 2031 $tmp_value = floor($value + 0.5); 2032 2033 if (($mode == PS_ROUND_HALF_DOWN && $value == (-0.5 + $tmp_value)) || 2034 ($mode == PS_ROUND_HALF_EVEN && $value == (0.5 + 2 * floor($tmp_value / 2.0))) || 2035 ($mode == PS_ROUND_HALF_ODD && $value == (0.5 + 2 * floor($tmp_value / 2.0) - 1.0))) { 2036 $tmp_value = $tmp_value - 1.0; 2037 } 2038 } else { 2039 $tmp_value = ceil($value - 0.5); 2040 2041 if (($mode == PS_ROUND_HALF_DOWN && $value == (0.5 + $tmp_value)) || 2042 ($mode == PS_ROUND_HALF_EVEN && $value == (-0.5 + 2 * ceil($tmp_value / 2.0))) || 2043 ($mode == PS_ROUND_HALF_ODD && $value == (-0.5 + 2 * ceil($tmp_value / 2.0) + 1.0))) { 2044 $tmp_value = $tmp_value + 1.0; 2045 } 2046 } 2047 2048 return $tmp_value; 2049 } 2050 2051 /** 2052 * Returns the rounded value up of $value to specified precision. 2053 * 2054 * @param float $value 2055 * @param int $precision 2056 * 2057 * @return float 2058 */ 2059 public static function ceilf($value, $precision = 0) 2060 { 2061 $precision_factor = $precision == 0 ? 1 : 10 ** $precision; 2062 $tmp = $value * $precision_factor; 2063 $tmp2 = (string) $tmp; 2064 // If the current value has already the desired precision 2065 if (strpos($tmp2, '.') === false) { 2066 return $value; 2067 } 2068 if ($tmp2[strlen($tmp2) - 1] == 0) { 2069 return $value; 2070 } 2071 2072 return ceil($tmp) / $precision_factor; 2073 } 2074 2075 /** 2076 * Returns the rounded value down of $value to specified precision. 2077 * 2078 * @param float $value 2079 * @param int $precision 2080 * 2081 * @return float 2082 */ 2083 public static function floorf($value, $precision = 0) 2084 { 2085 $precision_factor = $precision == 0 ? 1 : 10 ** $precision; 2086 $tmp = $value * $precision_factor; 2087 $tmp2 = (string) $tmp; 2088 // If the current value has already the desired precision 2089 if (strpos($tmp2, '.') === false) { 2090 return $value; 2091 } 2092 if ($tmp2[strlen($tmp2) - 1] == 0) { 2093 return $value; 2094 } 2095 2096 return floor($tmp) / $precision_factor; 2097 } 2098 2099 /** 2100 * file_exists() wrapper with cache to speedup performance. 2101 * 2102 * @param string $filename File name 2103 * 2104 * @return bool Cached result of file_exists($filename) 2105 */ 2106 public static function file_exists_cache($filename) 2107 { 2108 if (!isset(self::$file_exists_cache[$filename])) { 2109 self::$file_exists_cache[$filename] = file_exists($filename); 2110 } 2111 2112 return self::$file_exists_cache[$filename]; 2113 } 2114 2115 /** 2116 * file_exists() wrapper with a call to clearstatcache prior. 2117 * 2118 * @param string $filename File name 2119 * 2120 * @return bool Cached result of file_exists($filename) 2121 */ 2122 public static function file_exists_no_cache($filename) 2123 { 2124 clearstatcache(); 2125 2126 return file_exists($filename); 2127 } 2128 2129 /** 2130 * Refresh local CACert file. 2131 */ 2132 public static function refreshCACertFile() 2133 { 2134 if ((time() - @filemtime(_PS_CACHE_CA_CERT_FILE_) > 1296000)) { 2135 $stream_context = @stream_context_create( 2136 [ 2137 'http' => ['timeout' => 3], 2138 'ssl' => [ 2139 'cafile' => CaBundle::getBundledCaBundlePath(), 2140 ], 2141 ] 2142 ); 2143 2144 $ca_cert_content = @file_get_contents(Tools::CACERT_LOCATION, false, $stream_context); 2145 if (empty($ca_cert_content)) { 2146 $ca_cert_content = @file_get_contents(CaBundle::getBundledCaBundlePath()); 2147 } 2148 2149 if ( 2150 preg_match('/(.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----){50}$/Uims', $ca_cert_content) && 2151 substr(rtrim($ca_cert_content), -1) == '-' 2152 ) { 2153 file_put_contents(_PS_CACHE_CA_CERT_FILE_, $ca_cert_content); 2154 } 2155 } 2156 } 2157 2158 /** 2159 * @param string $url 2160 * @param int $curl_timeout 2161 * @param array $opts 2162 * 2163 * @return bool|string 2164 * 2165 * @throws Exception 2166 */ 2167 private static function file_get_contents_curl( 2168 $url, 2169 $curl_timeout, 2170 $opts 2171 ) { 2172 $content = false; 2173 2174 if (function_exists('curl_init')) { 2175 Tools::refreshCACertFile(); 2176 $curl = curl_init(); 2177 2178 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 2179 curl_setopt($curl, CURLOPT_URL, $url); 2180 curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5); 2181 curl_setopt($curl, CURLOPT_TIMEOUT, $curl_timeout); 2182 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); 2183 curl_setopt($curl, CURLOPT_CAINFO, _PS_CACHE_CA_CERT_FILE_); 2184 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); 2185 curl_setopt($curl, CURLOPT_MAXREDIRS, 5); 2186 2187 if ($opts != null) { 2188 if (isset($opts['http']['method']) && Tools::strtolower($opts['http']['method']) == 'post') { 2189 curl_setopt($curl, CURLOPT_POST, true); 2190 if (isset($opts['http']['content'])) { 2191 parse_str($opts['http']['content'], $post_data); 2192 curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); 2193 } 2194 } 2195 } 2196 2197 $content = curl_exec($curl); 2198 2199 if (false === $content && _PS_MODE_DEV_) { 2200 $errorMessage = sprintf('file_get_contents_curl failed to download %s : (error code %d) %s', 2201 $url, 2202 curl_errno($curl), 2203 curl_error($curl) 2204 ); 2205 2206 throw new \Exception($errorMessage); 2207 } 2208 2209 curl_close($curl); 2210 } 2211 2212 return $content; 2213 } 2214 2215 private static function file_get_contents_fopen( 2216 $url, 2217 $use_include_path, 2218 $stream_context 2219 ) { 2220 $content = false; 2221 2222 if (in_array(ini_get('allow_url_fopen'), ['On', 'on', '1'])) { 2223 $content = @file_get_contents($url, $use_include_path, $stream_context); 2224 } 2225 2226 return $content; 2227 } 2228 2229 /** 2230 * This method allows to get the content from either a URL or a local file. 2231 * 2232 * @param string $url the url to get the content from 2233 * @param bool $use_include_path second parameter of http://php.net/manual/en/function.file-get-contents.php 2234 * @param resource $stream_context third parameter of http://php.net/manual/en/function.file-get-contents.php 2235 * @param int $curl_timeout 2236 * @param bool $fallback whether or not to use the fallback if the main solution fails 2237 * 2238 * @return bool|string false or the string content 2239 */ 2240 public static function file_get_contents( 2241 $url, 2242 $use_include_path = false, 2243 $stream_context = null, 2244 $curl_timeout = 5, 2245 $fallback = false 2246 ) { 2247 $is_local_file = !preg_match('/^https?:\/\//', $url); 2248 $require_fopen = false; 2249 $opts = null; 2250 2251 if ($stream_context) { 2252 $opts = stream_context_get_options($stream_context); 2253 if (isset($opts['http'])) { 2254 $require_fopen = true; 2255 $opts_layer = array_diff_key($opts, ['http' => null]); 2256 $http_layer = array_diff_key($opts['http'], ['method' => null, 'content' => null]); 2257 if (empty($opts_layer) && empty($http_layer)) { 2258 $require_fopen = false; 2259 } 2260 } 2261 } elseif (!$is_local_file) { 2262 $stream_context = @stream_context_create( 2263 [ 2264 'http' => ['timeout' => $curl_timeout], 2265 'ssl' => [ 2266 'verify_peer' => true, 2267 'cafile' => CaBundle::getBundledCaBundlePath(), 2268 ], 2269 ] 2270 ); 2271 } 2272 2273 if ($is_local_file) { 2274 $content = @file_get_contents($url, $use_include_path, $stream_context); 2275 } else { 2276 if ($require_fopen) { 2277 $content = Tools::file_get_contents_fopen($url, $use_include_path, $stream_context); 2278 } else { 2279 $content = Tools::file_get_contents_curl($url, $curl_timeout, $opts); 2280 if (empty($content) && $fallback) { 2281 $content = Tools::file_get_contents_fopen($url, $use_include_path, $stream_context); 2282 } 2283 } 2284 } 2285 2286 return $content; 2287 } 2288 2289 /** 2290 * Create a local file from url 2291 * required because ZipArchive is unable to extract from remote files. 2292 * 2293 * @param string $url the remote location 2294 * 2295 * @return bool|string false if failure, else the local filename 2296 */ 2297 public static function createFileFromUrl($url) 2298 { 2299 $remoteFile = fopen($url, 'rb'); 2300 if (!$remoteFile) { 2301 return false; 2302 } 2303 $localFile = fopen(basename($url), 'wb'); 2304 if (!$localFile) { 2305 return false; 2306 } 2307 2308 while (!feof($remoteFile)) { 2309 $data = fread($remoteFile, 1024); 2310 fwrite($localFile, $data, 1024); 2311 } 2312 2313 fclose($remoteFile); 2314 fclose($localFile); 2315 2316 return basename($url); 2317 } 2318 2319 public static function simplexml_load_file($url, $class_name = null) 2320 { 2321 $cache_id = 'Tools::simplexml_load_file' . $url; 2322 if (!Cache::isStored($cache_id)) { 2323 $result = @simplexml_load_string(Tools::file_get_contents($url), $class_name); 2324 Cache::store($cache_id, $result); 2325 2326 return $result; 2327 } 2328 2329 return Cache::retrieve($cache_id); 2330 } 2331 2332 public static function copy($source, $destination, $stream_context = null) 2333 { 2334 if (null === $stream_context && !preg_match('/^https?:\/\//', $source)) { 2335 return @copy($source, $destination); 2336 } 2337 2338 return @file_put_contents($destination, Tools::file_get_contents($source, false, $stream_context)); 2339 } 2340 2341 /** 2342 * Translates a string with underscores into camel case (e.g. first_name -> firstName). 2343 * 2344 * @prototype string public static function toCamelCase(string $str[, bool $capitalise_first_char = false]) 2345 * 2346 * @param string $str Source string to convert in camel case 2347 * @param bool $capitaliseFirstChar Optionnal parameters to transform the first letter in upper case 2348 * 2349 * @return string The string in camel case 2350 */ 2351 public static function toCamelCase($str, $capitaliseFirstChar = false) 2352 { 2353 $str = Tools::strtolower($str); 2354 $str = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $str))); 2355 if (!$capitaliseFirstChar) { 2356 $str = lcfirst($str); 2357 } 2358 2359 return $str; 2360 } 2361 2362 /** 2363 * Transform a CamelCase string to underscore_case string. 2364 * 2365 * 'CMSCategories' => 'cms_categories' 2366 * 'RangePrice' => 'range_price' 2367 * 2368 * @param string $string 2369 * 2370 * @return string 2371 */ 2372 public static function toUnderscoreCase($string) 2373 { 2374 return Tools::strtolower(trim(preg_replace('/([A-Z][a-z])/', '_$1', $string), '_')); 2375 } 2376 2377 /** 2378 * Converts SomethingLikeThis to something-like-this 2379 * 2380 * @param string $string 2381 * 2382 * @return string 2383 */ 2384 public static function camelCaseToKebabCase($string) 2385 { 2386 return Tools::strtolower( 2387 preg_replace('/([a-z])([A-Z])/', '$1-$2', $string) 2388 ); 2389 } 2390 2391 /** 2392 * @param string $hex 2393 * 2394 * @return float|int|string 2395 */ 2396 public static function getBrightness($hex) 2397 { 2398 if (Tools::strtolower($hex) == 'transparent') { 2399 return '129'; 2400 } 2401 2402 $hex = str_replace('#', '', $hex); 2403 2404 if (Tools::strlen($hex) == 3) { 2405 $hex .= $hex; 2406 } 2407 2408 $r = hexdec(substr($hex, 0, 2)); 2409 $g = hexdec(substr($hex, 2, 2)); 2410 $b = hexdec(substr($hex, 4, 2)); 2411 2412 return (($r * 299) + ($g * 587) + ($b * 114)) / 1000; 2413 } 2414 2415 public static function isBright($hex) 2416 { 2417 if (null === self::$colorBrightnessCalculator) { 2418 self::$colorBrightnessCalculator = new ColorBrightnessCalculator(); 2419 } 2420 2421 return self::$colorBrightnessCalculator->isBright($hex); 2422 } 2423 2424 public static function parserSQL($sql) 2425 { 2426 if (strlen($sql) > 0) { 2427 $parser = new PHPSQLParser($sql); 2428 2429 return $parser->parsed; 2430 } 2431 2432 return false; 2433 } 2434 2435 public static function replaceByAbsoluteURL($matches) 2436 { 2437 Tools::displayAsDeprecated('Use Media::replaceByAbsoluteURL($matches) instead'); 2438 2439 return Media::replaceByAbsoluteURL($matches); 2440 } 2441 2442 protected static $_cache_nb_media_servers = null; 2443 2444 /** 2445 * @return bool 2446 */ 2447 public static function hasMediaServer(): bool 2448 { 2449 if (self::$_cache_nb_media_servers === null && defined('_MEDIA_SERVER_1_') && defined('_MEDIA_SERVER_2_') && defined('_MEDIA_SERVER_3_')) { 2450 if (_MEDIA_SERVER_1_ == '') { 2451 self::$_cache_nb_media_servers = 0; 2452 } elseif (_MEDIA_SERVER_2_ == '') { 2453 self::$_cache_nb_media_servers = 1; 2454 } elseif (_MEDIA_SERVER_3_ == '') { 2455 self::$_cache_nb_media_servers = 2; 2456 } else { 2457 self::$_cache_nb_media_servers = 3; 2458 } 2459 } 2460 2461 return self::$_cache_nb_media_servers > 0; 2462 } 2463 2464 /** 2465 * @param string $filename 2466 * 2467 * @return string 2468 */ 2469 public static function getMediaServer(string $filename): string 2470 { 2471 if (self::hasMediaServer() && ($id_media_server = (abs(crc32($filename)) % self::$_cache_nb_media_servers + 1))) { 2472 return constant('_MEDIA_SERVER_' . $id_media_server . '_'); 2473 } 2474 2475 return Tools::usingSecureMode() ? Tools::getShopDomainSsl() : Tools::getShopDomain(); 2476 } 2477 2478 /** 2479 * Get domains information with physical and virtual paths 2480 * 2481 * e.g: [ 2482 * prestashop.localhost => [ 2483 * physical => "/", 2484 * virtual => "", 2485 * id_shop => "1", 2486 * ] 2487 * ] 2488 * 2489 * @return array 2490 */ 2491 public static function getDomains() 2492 { 2493 $domains = []; 2494 foreach (ShopUrl::getShopUrls() as $shop_url) { 2495 /** @var ShopUrl $shop_url */ 2496 if (!isset($domains[$shop_url->domain])) { 2497 $domains[$shop_url->domain] = []; 2498 } 2499 2500 $domains[$shop_url->domain][] = [ 2501 'physical' => $shop_url->physical_uri, 2502 'virtual' => $shop_url->virtual_uri, 2503 'id_shop' => $shop_url->id_shop, 2504 ]; 2505 2506 if ($shop_url->domain == $shop_url->domain_ssl) { 2507 continue; 2508 } 2509 2510 if (!isset($domains[$shop_url->domain_ssl])) { 2511 $domains[$shop_url->domain_ssl] = []; 2512 } 2513 2514 $domains[$shop_url->domain_ssl][] = [ 2515 'physical' => $shop_url->physical_uri, 2516 'virtual' => $shop_url->virtual_uri, 2517 'id_shop' => $shop_url->id_shop, 2518 ]; 2519 } 2520 2521 return $domains; 2522 } 2523 2524 public static function generateHtaccess($path = null, $rewrite_settings = null, $cache_control = null, $specific = '', $disable_multiviews = null, $medias = false, $disable_modsec = null) 2525 { 2526 if (defined('_PS_IN_TEST_') 2527 || (defined('PS_INSTALLATION_IN_PROGRESS') && $rewrite_settings === null) 2528 ) { 2529 return true; 2530 } 2531 2532 // Default values for parameters 2533 if (null === $path) { 2534 $path = _PS_ROOT_DIR_ . '/.htaccess'; 2535 } 2536 2537 if (null === $cache_control) { 2538 $cache_control = (int) Configuration::get('PS_HTACCESS_CACHE_CONTROL'); 2539 } 2540 if (null === $disable_multiviews) { 2541 $disable_multiviews = (bool) Configuration::get('PS_HTACCESS_DISABLE_MULTIVIEWS'); 2542 } 2543 2544 if ($disable_modsec === null) { 2545 $disable_modsec = (int) Configuration::get('PS_HTACCESS_DISABLE_MODSEC'); 2546 } 2547 2548 // Check current content of .htaccess and save all code outside of prestashop comments 2549 $specific_before = $specific_after = ''; 2550 if (file_exists($path)) { 2551 $content = file_get_contents($path); 2552 if (preg_match('#^(.*)\# ~~start~~.*\# ~~end~~[^\n]*(.*)$#s', $content, $m)) { 2553 $specific_before = $m[1]; 2554 $specific_after = $m[2]; 2555 } else { 2556 // For retrocompatibility 2557 if (preg_match('#\# http://www\.prestashop\.com - http://www\.prestashop\.com/forums\s*(.*)<IfModule mod_rewrite\.c>#si', $content, $m)) { 2558 $specific_before = $m[1]; 2559 } else { 2560 $specific_before = $content; 2561 } 2562 } 2563 } 2564 2565 // Write .htaccess data 2566 if (!$write_fd = @fopen($path, 'wb')) { 2567 return false; 2568 } 2569 if ($specific_before) { 2570 fwrite($write_fd, trim($specific_before) . "\n\n"); 2571 } 2572 2573 $domains = self::getDomains(); 2574 2575 // Write data in .htaccess file 2576 fwrite($write_fd, "# ~~start~~ Do not remove this comment, Prestashop will keep automatically the code outside this comment when .htaccess will be generated again\n"); 2577 fwrite($write_fd, "# .htaccess automaticaly generated by PrestaShop e-commerce open-source solution\n"); 2578 fwrite($write_fd, "# https://www.prestashop.com - https://www.prestashop.com/forums\n\n"); 2579 2580 if ($disable_modsec) { 2581 fwrite($write_fd, "<IfModule mod_security.c>\nSecFilterEngine Off\nSecFilterScanPOST Off\n</IfModule>\n\n"); 2582 } 2583 2584 // RewriteEngine 2585 fwrite($write_fd, "<IfModule mod_rewrite.c>\n"); 2586 2587 // Ensure HTTP_MOD_REWRITE variable is set in environment 2588 fwrite($write_fd, "<IfModule mod_env.c>\n"); 2589 fwrite($write_fd, "SetEnv HTTP_MOD_REWRITE On\n"); 2590 fwrite($write_fd, "</IfModule>\n\n"); 2591 2592 // Disable multiviews ? 2593 if ($disable_multiviews) { 2594 fwrite($write_fd, "\n# Disable Multiviews\nOptions -Multiviews\n\n"); 2595 } 2596 2597 fwrite($write_fd, "RewriteEngine on\n"); 2598 2599 if (!$medias && Configuration::getMultiShopValues('PS_MEDIA_SERVER_1') 2600 && Configuration::getMultiShopValues('PS_MEDIA_SERVER_2') 2601 && Configuration::getMultiShopValues('PS_MEDIA_SERVER_3') 2602 ) { 2603 $medias = [ 2604 Configuration::getMultiShopValues('PS_MEDIA_SERVER_1'), 2605 Configuration::getMultiShopValues('PS_MEDIA_SERVER_2'), 2606 Configuration::getMultiShopValues('PS_MEDIA_SERVER_3'), 2607 ]; 2608 } 2609 2610 $media_domains = ''; 2611 foreach ($medias as $media) { 2612 foreach ($media as $media_url) { 2613 if ($media_url) { 2614 $media_domains .= 'RewriteCond %{HTTP_HOST} ^' . $media_url . '$ [OR]' . PHP_EOL; 2615 } 2616 } 2617 } 2618 2619 if (Configuration::get('PS_WEBSERVICE_CGI_HOST')) { 2620 fwrite($write_fd, "RewriteCond %{HTTP:Authorization} ^(.*)\nRewriteRule . - [E=HTTP_AUTHORIZATION:%1]\n\n"); 2621 } 2622 2623 foreach ($domains as $domain => $list_uri) { 2624 // As we use regex in the htaccess, ipv6 surrounded by brackets must be escaped 2625 $domain = str_replace(['[', ']'], ['\[', '\]'], $domain); 2626 2627 foreach ($list_uri as $uri) { 2628 fwrite($write_fd, PHP_EOL . PHP_EOL . '#Domain: ' . $domain . PHP_EOL); 2629 if (Shop::isFeatureActive()) { 2630 fwrite($write_fd, 'RewriteCond %{HTTP_HOST} ^' . $domain . '$' . PHP_EOL); 2631 } 2632 fwrite($write_fd, 'RewriteRule . - [E=REWRITEBASE:' . $uri['physical'] . ']' . PHP_EOL); 2633 2634 // Webservice 2635 fwrite($write_fd, 'RewriteRule ^api(?:/(.*))?$ %{ENV:REWRITEBASE}webservice/dispatcher.php?url=$1 [QSA,L]' . "\n\n"); 2636 2637 if (!$rewrite_settings) { 2638 $rewrite_settings = (int) Configuration::get('PS_REWRITING_SETTINGS', null, null, (int) $uri['id_shop']); 2639 } 2640 2641 $domain_rewrite_cond = 'RewriteCond %{HTTP_HOST} ^' . $domain . '$' . PHP_EOL; 2642 // Rewrite virtual multishop uri 2643 if ($uri['virtual']) { 2644 if (!$rewrite_settings) { 2645 fwrite($write_fd, $media_domains); 2646 fwrite($write_fd, $domain_rewrite_cond); 2647 fwrite($write_fd, 'RewriteRule ^' . trim($uri['virtual'], '/') . '/?$ ' . $uri['physical'] . $uri['virtual'] . "index.php [L,R]\n"); 2648 } else { 2649 fwrite($write_fd, $media_domains); 2650 fwrite($write_fd, $domain_rewrite_cond); 2651 fwrite($write_fd, 'RewriteRule ^' . trim($uri['virtual'], '/') . '$ ' . $uri['physical'] . $uri['virtual'] . " [L,R]\n"); 2652 } 2653 fwrite($write_fd, $media_domains); 2654 fwrite($write_fd, $domain_rewrite_cond); 2655 fwrite($write_fd, 'RewriteRule ^' . ltrim($uri['virtual'], '/') . '(.*) ' . $uri['physical'] . "$1 [L]\n\n"); 2656 } 2657 2658 if ($rewrite_settings) { 2659 // Compatibility with the old image filesystem 2660 fwrite($write_fd, "# Images\n"); 2661 if (Configuration::get('PS_LEGACY_IMAGES')) { 2662 fwrite($write_fd, $media_domains); 2663 fwrite($write_fd, $domain_rewrite_cond); 2664 fwrite($write_fd, 'RewriteRule ^([a-z0-9]+)\-([a-z0-9]+)(\-[_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1-$2$3$4.jpg [L]' . PHP_EOL); 2665 fwrite($write_fd, $media_domains); 2666 fwrite($write_fd, $domain_rewrite_cond); 2667 fwrite($write_fd, 'RewriteRule ^([0-9]+)\-([0-9]+)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1-$2$3.jpg [L]' . PHP_EOL); 2668 } 2669 2670 // Rewrite product images < 10 millions 2671 for ($i = 1; $i <= 7; ++$i) { 2672 $img_path = $img_name = ''; 2673 for ($j = 1; $j <= $i; ++$j) { 2674 $img_path .= '$' . $j . '/'; 2675 $img_name .= '$' . $j; 2676 } 2677 $img_name .= '$' . $j; 2678 fwrite($write_fd, $media_domains); 2679 fwrite($write_fd, $domain_rewrite_cond); 2680 fwrite($write_fd, 'RewriteRule ^' . str_repeat('([0-9])', $i) . '(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/' . $img_path . $img_name . '$' . ($j + 1) . ".jpg [L]\n"); 2681 } 2682 fwrite($write_fd, $media_domains); 2683 fwrite($write_fd, $domain_rewrite_cond); 2684 fwrite($write_fd, 'RewriteRule ^c/([0-9]+)(\-[\.*_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/c/$1$2$3.jpg [L]' . PHP_EOL); 2685 fwrite($write_fd, $media_domains); 2686 fwrite($write_fd, $domain_rewrite_cond); 2687 fwrite($write_fd, 'RewriteRule ^c/([a-zA-Z_-]+)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/c/$1$2.jpg [L]' . PHP_EOL); 2688 } 2689 2690 fwrite($write_fd, "# AlphaImageLoader for IE and fancybox\n"); 2691 if (Shop::isFeatureActive()) { 2692 fwrite($write_fd, $domain_rewrite_cond); 2693 } 2694 fwrite($write_fd, 'RewriteRule ^images_ie/?([^/]+)\.(jpe?g|png|gif)$ js/jquery/plugins/fancybox/images/$1.$2 [L]' . PHP_EOL); 2695 } 2696 // Redirections to dispatcher 2697 if ($rewrite_settings) { 2698 fwrite($write_fd, "\n# Dispatcher\n"); 2699 fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -s [OR]\n"); 2700 fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -l [OR]\n"); 2701 fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -d\n"); 2702 if (Shop::isFeatureActive()) { 2703 fwrite($write_fd, $domain_rewrite_cond); 2704 } 2705 fwrite($write_fd, "RewriteRule ^.*$ - [NC,L]\n"); 2706 if (Shop::isFeatureActive()) { 2707 fwrite($write_fd, $domain_rewrite_cond); 2708 } 2709 fwrite($write_fd, "RewriteRule ^.*\$ %{ENV:REWRITEBASE}index.php [NC,L]\n"); 2710 } 2711 } 2712 2713 fwrite($write_fd, "</IfModule>\n\n"); 2714 2715 fwrite($write_fd, "AddType application/vnd.ms-fontobject .eot\n"); 2716 fwrite($write_fd, "AddType font/ttf .ttf\n"); 2717 fwrite($write_fd, "AddType font/otf .otf\n"); 2718 fwrite($write_fd, "AddType application/font-woff .woff\n"); 2719 fwrite($write_fd, "AddType font/woff2 .woff2\n"); 2720 fwrite($write_fd, "<IfModule mod_headers.c> 2721 <FilesMatch \"\.(ttf|ttc|otf|eot|woff|woff2|svg)$\"> 2722 Header set Access-Control-Allow-Origin \"*\" 2723 </FilesMatch> 2724 2725 <FilesMatch \"\.pdf$\"> 2726 Header set Content-Disposition \"Attachment\" 2727 Header set X-Content-Type-Options \"nosniff\" 2728 </FilesMatch> 2729</IfModule>\n\n"); 2730 fwrite($write_fd, '<Files composer.lock> 2731 # Apache 2.2 2732 <IfModule !mod_authz_core.c> 2733 Order deny,allow 2734 Deny from all 2735 </IfModule> 2736 2737 # Apache 2.4 2738 <IfModule mod_authz_core.c> 2739 Require all denied 2740 </IfModule> 2741</Files> 2742'); 2743 // Cache control 2744 if ($cache_control) { 2745 $cache_control = "<IfModule mod_expires.c> 2746 ExpiresActive On 2747 ExpiresByType image/gif \"access plus 1 month\" 2748 ExpiresByType image/jpeg \"access plus 1 month\" 2749 ExpiresByType image/png \"access plus 1 month\" 2750 ExpiresByType text/css \"access plus 1 week\" 2751 ExpiresByType text/javascript \"access plus 1 week\" 2752 ExpiresByType application/javascript \"access plus 1 week\" 2753 ExpiresByType application/x-javascript \"access plus 1 week\" 2754 ExpiresByType image/x-icon \"access plus 1 year\" 2755 ExpiresByType image/svg+xml \"access plus 1 year\" 2756 ExpiresByType image/vnd.microsoft.icon \"access plus 1 year\" 2757 ExpiresByType application/font-woff \"access plus 1 year\" 2758 ExpiresByType application/x-font-woff \"access plus 1 year\" 2759 ExpiresByType font/woff2 \"access plus 1 year\" 2760 ExpiresByType application/vnd.ms-fontobject \"access plus 1 year\" 2761 ExpiresByType font/opentype \"access plus 1 year\" 2762 ExpiresByType font/ttf \"access plus 1 year\" 2763 ExpiresByType font/otf \"access plus 1 year\" 2764 ExpiresByType application/x-font-ttf \"access plus 1 year\" 2765 ExpiresByType application/x-font-otf \"access plus 1 year\" 2766</IfModule> 2767 2768<IfModule mod_headers.c> 2769 Header unset Etag 2770</IfModule> 2771FileETag none 2772<IfModule mod_deflate.c> 2773 <IfModule mod_filter.c> 2774 AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/x-javascript font/ttf application/x-font-ttf font/otf application/x-font-otf font/opentype image/svg+xml 2775 </IfModule> 2776</IfModule>\n\n"; 2777 fwrite($write_fd, $cache_control); 2778 } 2779 2780 // In case the user hasn't rewrite mod enabled 2781 fwrite($write_fd, "#If rewrite mod isn't enabled\n"); 2782 2783 // Do not remove ($domains is already iterated upper) 2784 reset($domains); 2785 $domain = current($domains); 2786 fwrite($write_fd, 'ErrorDocument 404 ' . $domain[0]['physical'] . "index.php?controller=404\n\n"); 2787 2788 fwrite($write_fd, '# ~~end~~ Do not remove this comment, Prestashop will keep automatically the code outside this comment when .htaccess will be generated again'); 2789 if ($specific_after) { 2790 fwrite($write_fd, "\n\n" . trim($specific_after)); 2791 } 2792 fclose($write_fd); 2793 2794 if (!defined('PS_INSTALLATION_IN_PROGRESS')) { 2795 Hook::exec('actionHtaccessCreate'); 2796 } 2797 2798 return true; 2799 } 2800 2801 /** 2802 * @param bool $executeHook 2803 * 2804 * @return bool 2805 */ 2806 public static function generateRobotsFile($executeHook = false) 2807 { 2808 $robots_file = _PS_ROOT_DIR_ . '/robots.txt'; 2809 2810 if (!$write_fd = @fopen($robots_file, 'wb')) { 2811 return false; 2812 } 2813 2814 $robots_content = static::getRobotsContent(); 2815 $languagesIsoIds = Language::getIsoIds(); 2816 2817 if (true === $executeHook) { 2818 Hook::exec('actionAdminMetaBeforeWriteRobotsFile', [ 2819 'rb_data' => &$robots_content, 2820 ]); 2821 } 2822 2823 // PS Comments 2824 fwrite($write_fd, "# robots.txt automatically generated by PrestaShop e-commerce open-source solution\n"); 2825 fwrite($write_fd, "# https://www.prestashop.com - https://www.prestashop.com/forums\n"); 2826 fwrite($write_fd, "# This file is to prevent the crawling and indexing of certain parts\n"); 2827 fwrite($write_fd, "# of your site by web crawlers and spiders run by sites like Yahoo!\n"); 2828 fwrite($write_fd, "# and Google. By telling these \"robots\" where not to go on your site,\n"); 2829 fwrite($write_fd, "# you save bandwidth and server resources.\n"); 2830 fwrite($write_fd, "# For more information about the robots.txt standard, see:\n"); 2831 fwrite($write_fd, "# https://www.robotstxt.org/robotstxt.html\n"); 2832 2833 // User-Agent 2834 fwrite($write_fd, "User-agent: *\n"); 2835 2836 // Allow Directives 2837 if (count($robots_content['Allow'])) { 2838 fwrite($write_fd, "# Allow Directives\n"); 2839 foreach ($robots_content['Allow'] as $allow) { 2840 fwrite($write_fd, 'Allow: ' . $allow . PHP_EOL); 2841 } 2842 } 2843 2844 // Private pages 2845 if (count($robots_content['GB'])) { 2846 fwrite($write_fd, "# Private pages\n"); 2847 foreach ($robots_content['GB'] as $gb) { 2848 fwrite($write_fd, 'Disallow: /*' . $gb . PHP_EOL); 2849 } 2850 } 2851 2852 // Directories 2853 if (count($robots_content['Directories'])) { 2854 foreach (self::getDomains() as $domain => $uriList) { 2855 fwrite( 2856 $write_fd, 2857 sprintf( 2858 '# Directories for %s%s', 2859 $domain, 2860 PHP_EOL 2861 ) 2862 ); 2863 // Disallow multishop directories 2864 foreach ($uriList as $uri) { 2865 foreach ($robots_content['Directories'] as $dir) { 2866 fwrite($write_fd, 'Disallow: ' . $uri['physical'] . $dir . PHP_EOL); 2867 } 2868 } 2869 2870 // Disallow multilang directories 2871 if (!empty($languagesIsoIds)) { 2872 foreach ($languagesIsoIds as $language) { 2873 foreach ($robots_content['Directories'] as $dir) { 2874 fwrite( 2875 $write_fd, 2876 sprintf( 2877 'Disallow: /%s/%s%s', 2878 $language['iso_code'], 2879 $dir, 2880 PHP_EOL 2881 ) 2882 ); 2883 } 2884 } 2885 } 2886 } 2887 } 2888 2889 // Files 2890 if (count($robots_content['Files'])) { 2891 fwrite($write_fd, "# Files\n"); 2892 foreach ($robots_content['Files'] as $iso_code => $files) { 2893 foreach ($files as $file) { 2894 if (!empty($languagesIsoIds)) { 2895 fwrite($write_fd, 'Disallow: /*' . $iso_code . '/' . $file . PHP_EOL); 2896 } else { 2897 fwrite($write_fd, 'Disallow: /' . $file . PHP_EOL); 2898 } 2899 } 2900 } 2901 } 2902 2903 if (null === Context::getContext()) { 2904 $sitemap_file = _PS_ROOT_DIR_ . DIRECTORY_SEPARATOR . 'index_sitemap.xml'; 2905 } else { 2906 $sitemap_file = _PS_ROOT_DIR_ . DIRECTORY_SEPARATOR . Context::getContext()->shop->id . '_index_sitemap.xml'; 2907 } 2908 2909 // Sitemap 2910 if (file_exists($sitemap_file) && filesize($sitemap_file)) { 2911 fwrite($write_fd, "# Sitemap\n"); 2912 $sitemap_filename = basename($sitemap_file); 2913 fwrite($write_fd, 'Sitemap: ' . (Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://') . $_SERVER['SERVER_NAME'] 2914 . __PS_BASE_URI__ . $sitemap_filename . PHP_EOL); 2915 } 2916 2917 if (true === $executeHook) { 2918 Hook::exec('actionAdminMetaAfterWriteRobotsFile', [ 2919 'rb_data' => $robots_content, 2920 'write_fd' => &$write_fd, 2921 ]); 2922 } 2923 2924 fclose($write_fd); 2925 2926 return true; 2927 } 2928 2929 /** 2930 * @return array 2931 */ 2932 public static function getRobotsContent() 2933 { 2934 $tab = []; 2935 2936 // Special allow directives 2937 $tab['Allow'] = [ 2938 '*/modules/*.css', 2939 '*/modules/*.js', 2940 '*/modules/*.png', 2941 '*/modules/*.jpg', 2942 '/js/jquery/*', 2943 ]; 2944 2945 // Directories 2946 $tab['Directories'] = [ 2947 'app/', 'cache/', 'classes/', 'config/', 'controllers/', 2948 'download/', 'js/', 'localization/', 'log/', 'mails/', 'modules/', 'override/', 2949 'pdf/', 'src/', 'tools/', 'translations/', 'upload/', 'var/', 'vendor/', 'webservice/', 2950 ]; 2951 2952 // Files 2953 $disallow_controllers = [ 2954 'addresses', 'address', 'authentication', 'cart', 'discount', 'footer', 2955 'get-file', 'header', 'history', 'identity', 'images.inc', 'init', 'my-account', 'order', 2956 'order-slip', 'order-detail', 'order-follow', 'order-return', 'order-confirmation', 'pagination', 'password', 2957 'pdf-invoice', 'pdf-order-return', 'pdf-order-slip', 'product-sort', 'search', 'statistics', 'attachment', 'guest-tracking', 2958 ]; 2959 2960 // Rewrite files 2961 $tab['Files'] = []; 2962 if (Configuration::get('PS_REWRITING_SETTINGS')) { 2963 $sql = 'SELECT DISTINCT ml.url_rewrite, l.iso_code 2964 FROM ' . _DB_PREFIX_ . 'meta m 2965 INNER JOIN ' . _DB_PREFIX_ . 'meta_lang ml ON ml.id_meta = m.id_meta 2966 INNER JOIN ' . _DB_PREFIX_ . 'lang l ON l.id_lang = ml.id_lang 2967 WHERE l.active = 1 AND m.page IN (\'' . implode('\', \'', $disallow_controllers) . '\')'; 2968 if ($results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql)) { 2969 foreach ($results as $row) { 2970 $tab['Files'][$row['iso_code']][] = $row['url_rewrite']; 2971 } 2972 } 2973 } 2974 2975 $tab['GB'] = [ 2976 '?order=', '?tag=', '?id_currency=', '?search_query=', '?back=', '?n=', 2977 '&order=', '&tag=', '&id_currency=', '&search_query=', '&back=', '&n=', 2978 ]; 2979 2980 foreach ($disallow_controllers as $controller) { 2981 $tab['GB'][] = 'controller=' . $controller; 2982 } 2983 2984 return $tab; 2985 } 2986 2987 public static function generateIndex() 2988 { 2989 PrestaShopAutoload::getInstance()->generateIndex(); 2990 } 2991 2992 /** 2993 * @return string php file to be run 2994 */ 2995 public static function getDefaultIndexContent() 2996 { 2997 return '<?php 2998/** 2999 * Copyright since 2007 PrestaShop SA and Contributors 3000 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA 3001 * 3002 * NOTICE OF LICENSE 3003 * 3004 * This source file is subject to the Open Software License (OSL 3.0) 3005 * that is bundled with this package in the file LICENSE.md. 3006 * It is also available through the world-wide-web at this URL: 3007 * https://opensource.org/licenses/OSL-3.0 3008 * If you did not receive a copy of the license and are unable to 3009 * obtain it through the world-wide-web, please send an email 3010 * to license@prestashop.com so we can send you a copy immediately. 3011 * 3012 * DISCLAIMER 3013 * 3014 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 3015 * versions in the future. If you wish to customize PrestaShop for your 3016 * needs please refer to https://devdocs.prestashop.com/ for more information. 3017 * 3018 * @author PrestaShop SA and Contributors <contact@prestashop.com> 3019 * @copyright Since 2007 PrestaShop SA and Contributors 3020 * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) 3021 */ 3022 3023header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 3024header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); 3025 3026header("Cache-Control: no-store, no-cache, must-revalidate"); 3027header("Cache-Control: post-check=0, pre-check=0", false); 3028header("Pragma: no-cache"); 3029 3030header("Location: ../"); 3031exit; 3032'; 3033 } 3034 3035 /** 3036 * Return the directory list from the given $path. 3037 * 3038 * @param string $path 3039 * 3040 * @return array 3041 */ 3042 public static function getDirectories($path) 3043 { 3044 if (function_exists('glob')) { 3045 return self::getDirectoriesWithGlob($path); 3046 } 3047 3048 return self::getDirectoriesWithReaddir($path); 3049 } 3050 3051 /** 3052 * Return the directory list from the given $path using php glob function. 3053 * 3054 * @param string $path 3055 * 3056 * @return array 3057 */ 3058 public static function getDirectoriesWithGlob($path) 3059 { 3060 $directoryList = glob($path . '/*', GLOB_ONLYDIR | GLOB_NOSORT); 3061 array_walk( 3062 $directoryList, 3063 function (&$absolutePath, $key) { 3064 $absolutePath = substr($absolutePath, strrpos($absolutePath, '/') + 1); 3065 } 3066 ); 3067 3068 return $directoryList; 3069 } 3070 3071 /** 3072 * Return the directory list from the given $path using php readdir function. 3073 * 3074 * @param string $path 3075 * 3076 * @return array 3077 */ 3078 public static function getDirectoriesWithReaddir($path) 3079 { 3080 $directoryList = []; 3081 $dh = @opendir($path); 3082 if ($dh) { 3083 while (($file = @readdir($dh)) !== false) { 3084 if (is_dir($path . DIRECTORY_SEPARATOR . $file) && $file[0] != '.') { 3085 $directoryList[] = $file; 3086 } 3087 } 3088 @closedir($dh); 3089 } 3090 3091 return $directoryList; 3092 } 3093 3094 /** 3095 * @deprecated Deprecated since 1.7.0 3096 * Use json_decode instead 3097 * jsonDecode convert json string to php array / object 3098 * 3099 * @param string $data 3100 * @param bool $assoc (since 1.4.2.4) if true, convert to associative array 3101 * @param int $depth 3102 * @param int $options 3103 * 3104 * @return array 3105 */ 3106 public static function jsonDecode($data, $assoc = false, $depth = 512, $options = 0) 3107 { 3108 return json_decode($data, $assoc, $depth, $options); 3109 } 3110 3111 /** 3112 * @deprecated Deprecated since 1.7.0 3113 * Use json_encode instead 3114 * Convert an array to json string 3115 * 3116 * @param array $data 3117 * @param int $depth 3118 * @param int $options 3119 * 3120 * @return string json 3121 */ 3122 public static function jsonEncode($data, $options = 0, $depth = 512) 3123 { 3124 return json_encode($data, $options, $depth); 3125 } 3126 3127 /** 3128 * Display a warning message indicating that the method is deprecated. 3129 * 3130 * @param string $message 3131 */ 3132 public static function displayAsDeprecated($message = null) 3133 { 3134 $backtrace = debug_backtrace(); 3135 $callee = next($backtrace); 3136 $class = isset($callee['class']) ? $callee['class'] : null; 3137 3138 if ($message === null) { 3139 $message = 'The function ' . $callee['function'] . ' (Line ' . $callee['line'] . ') is deprecated and will be removed in the next major version.'; 3140 } 3141 3142 $error = 'Function <b>' . $callee['function'] . '()</b> is deprecated in <b>' . $callee['file'] . '</b> on line <b>' . $callee['line'] . '</b><br />'; 3143 3144 Tools::throwDeprecated($error, $message, $class); 3145 } 3146 3147 /** 3148 * Display a warning message indicating that the parameter is deprecated. 3149 */ 3150 public static function displayParameterAsDeprecated($parameter) 3151 { 3152 $backtrace = debug_backtrace(); 3153 $callee = next($backtrace); 3154 $error = 'Parameter <b>' . $parameter . '</b> in function <b>' . (isset($callee['function']) ? $callee['function'] : '') . '()</b> is deprecated in <b>' . $callee['file'] . '</b> on line <b>' . (isset($callee['line']) ? $callee['line'] : '(undefined)') . '</b><br />'; 3155 $message = 'The parameter ' . $parameter . ' in function ' . $callee['function'] . ' (Line ' . (isset($callee['line']) ? $callee['line'] : 'undefined') . ') is deprecated and will be removed in the next major version.'; 3156 $class = isset($callee['class']) ? $callee['class'] : null; 3157 3158 Tools::throwDeprecated($error, $message, $class); 3159 } 3160 3161 public static function displayFileAsDeprecated() 3162 { 3163 $backtrace = debug_backtrace(); 3164 $callee = current($backtrace); 3165 $error = 'File <b>' . $callee['file'] . '</b> is deprecated<br />'; 3166 $message = 'The file ' . $callee['file'] . ' is deprecated and will be removed in the next major version.'; 3167 $class = isset($callee['class']) ? $callee['class'] : null; 3168 3169 Tools::throwDeprecated($error, $message, $class); 3170 } 3171 3172 protected static function throwDeprecated($error, $message, $class) 3173 { 3174 if (_PS_DISPLAY_COMPATIBILITY_WARNING_) { 3175 @trigger_error($error, E_USER_DEPRECATED); 3176 PrestaShopLogger::addLog($message, 3, $class); 3177 } 3178 } 3179 3180 public static function enableCache($level = 1, Context $context = null) 3181 { 3182 if (!$context) { 3183 $context = Context::getContext(); 3184 } 3185 $smarty = $context->smarty; 3186 if (!Configuration::get('PS_SMARTY_CACHE')) { 3187 return; 3188 } 3189 if ($smarty->force_compile == 0 && $smarty->caching == $level) { 3190 return; 3191 } 3192 self::$_forceCompile = (int) $smarty->force_compile; 3193 self::$_caching = (int) $smarty->caching; 3194 $smarty->force_compile = 0; 3195 $smarty->caching = (int) $level; 3196 $smarty->cache_lifetime = 31536000; // 1 Year 3197 } 3198 3199 public static function restoreCacheSettings(Context $context = null) 3200 { 3201 if (!$context) { 3202 $context = Context::getContext(); 3203 } 3204 3205 if (isset(self::$_forceCompile)) { 3206 $context->smarty->force_compile = (int) self::$_forceCompile; 3207 } 3208 if (isset(self::$_caching)) { 3209 $context->smarty->caching = (int) self::$_caching; 3210 } 3211 } 3212 3213 public static function isCallable($function) 3214 { 3215 $disabled = explode(',', ini_get('disable_functions')); 3216 3217 return !in_array($function, $disabled) && is_callable($function); 3218 } 3219 3220 public static function pRegexp($s, $delim) 3221 { 3222 $s = str_replace($delim, '\\' . $delim, $s); 3223 foreach (['?', '[', ']', '(', ')', '{', '}', '-', '.', '+', '*', '^', '$', '`', '"', '%'] as $char) { 3224 $s = str_replace($char, '\\' . $char, $s); 3225 } 3226 3227 return $s; 3228 } 3229 3230 public static function str_replace_once($needle, $replace, $haystack) 3231 { 3232 $pos = false; 3233 if ($needle) { 3234 $pos = strpos($haystack, $needle); 3235 } 3236 if ($pos === false) { 3237 return $haystack; 3238 } 3239 3240 return substr_replace($haystack, $replace, $pos, strlen($needle)); 3241 } 3242 3243 /** 3244 * Identify the version of php 3245 * 3246 * @return string 3247 */ 3248 public static function checkPhpVersion() 3249 { 3250 $version = null; 3251 3252 if (defined('PHP_VERSION')) { 3253 $version = PHP_VERSION; 3254 } else { 3255 $version = phpversion(''); 3256 } 3257 3258 // Specific ubuntu usecase: php version returns 5.2.4-2ubuntu5.2 3259 if (strpos($version, '-') !== false) { 3260 $version = substr($version, 0, strpos($version, '-')); 3261 } 3262 3263 return $version; 3264 } 3265 3266 /** 3267 * Try to open a zip file in order to check if it's valid 3268 * 3269 * @param string $from_file 3270 * 3271 * @return bool success 3272 */ 3273 public static function ZipTest($from_file) 3274 { 3275 $zip = new ZipArchive(); 3276 3277 return $zip->open($from_file, ZipArchive::CHECKCONS) === true; 3278 } 3279 3280 /** 3281 * @deprecated Deprecated since 1.7.0 3282 * 3283 * @return bool 3284 */ 3285 public static function getSafeModeStatus() 3286 { 3287 return false; 3288 } 3289 3290 /** 3291 * Extract a zip file to the given directory 3292 * 3293 * @param string $from_file 3294 * @param string $to_dir 3295 * 3296 * @return bool 3297 */ 3298 public static function ZipExtract($from_file, $to_dir) 3299 { 3300 if (!file_exists($to_dir)) { 3301 mkdir($to_dir, PsFileSystem::DEFAULT_MODE_FOLDER); 3302 } 3303 3304 $zip = new ZipArchive(); 3305 if ($zip->open($from_file) === true && $zip->extractTo($to_dir) && $zip->close()) { 3306 return true; 3307 } 3308 3309 return false; 3310 } 3311 3312 /** 3313 * @param string $path 3314 * @param int $filemode 3315 * 3316 * @return bool 3317 */ 3318 public static function chmodr($path, $filemode) 3319 { 3320 if (!is_dir($path)) { 3321 return @chmod($path, $filemode); 3322 } 3323 $dh = opendir($path); 3324 while (($file = readdir($dh)) !== false) { 3325 if ($file != '.' && $file != '..') { 3326 $fullpath = $path . '/' . $file; 3327 if (is_link($fullpath)) { 3328 return false; 3329 } elseif (!is_dir($fullpath) && !@chmod($fullpath, $filemode)) { 3330 return false; 3331 } elseif (!Tools::chmodr($fullpath, $filemode)) { 3332 return false; 3333 } 3334 } 3335 } 3336 closedir($dh); 3337 if (@chmod($path, $filemode)) { 3338 return true; 3339 } else { 3340 return false; 3341 } 3342 } 3343 3344 /** 3345 * Get products order field name for queries. 3346 * 3347 * @param string $type by|way 3348 * @param string $value If no index given, use default order from admin -> pref -> products 3349 * @param bool|\bool(false)|string $prefix 3350 * 3351 * @return string Order by sql clause 3352 */ 3353 public static function getProductsOrder($type, $value = null, $prefix = false) 3354 { 3355 switch ($type) { 3356 case 'by': 3357 $list = [0 => 'name', 1 => 'price', 2 => 'date_add', 3 => 'date_upd', 4 => 'position', 5 => 'manufacturer_name', 6 => 'quantity', 7 => 'reference']; 3358 $value = (null === $value || $value === false || $value === '') ? (int) Configuration::get('PS_PRODUCTS_ORDER_BY') : $value; 3359 $value = (isset($list[$value])) ? $list[$value] : ((in_array($value, $list)) ? $value : 'position'); 3360 $order_by_prefix = ''; 3361 if ($prefix) { 3362 if ($value == 'id_product' || $value == 'date_add' || $value == 'date_upd' || $value == 'price') { 3363 $order_by_prefix = 'p.'; 3364 } elseif ($value == 'name') { 3365 $order_by_prefix = 'pl.'; 3366 } elseif ($value == 'manufacturer_name' && $prefix) { 3367 $order_by_prefix = 'm.'; 3368 $value = 'name'; 3369 } elseif ($value == 'position' || empty($value)) { 3370 $order_by_prefix = 'cp.'; 3371 } 3372 } 3373 3374 return $order_by_prefix . $value; 3375 3376 break; 3377 3378 case 'way': 3379 $value = (null === $value || $value === false || $value === '') ? (int) Configuration::get('PS_PRODUCTS_ORDER_WAY') : $value; 3380 $list = [0 => 'asc', 1 => 'desc']; 3381 3382 return (isset($list[$value])) ? $list[$value] : ((in_array($value, $list)) ? $value : 'asc'); 3383 3384 break; 3385 } 3386 } 3387 3388 /** 3389 * Convert a shorthand byte value from a PHP configuration directive to an integer value. 3390 * 3391 * @param string $value value to convert 3392 * 3393 * @return int 3394 */ 3395 public static function convertBytes($value) 3396 { 3397 if (is_numeric($value)) { 3398 return $value; 3399 } else { 3400 $value_length = strlen($value); 3401 $qty = (int) substr($value, 0, $value_length - 1); 3402 $unit = Tools::strtolower(substr($value, $value_length - 1)); 3403 switch ($unit) { 3404 case 'k': 3405 $qty *= 1024; 3406 3407 break; 3408 case 'm': 3409 $qty *= 1048576; 3410 3411 break; 3412 case 'g': 3413 $qty *= 1073741824; 3414 3415 break; 3416 } 3417 3418 return $qty; 3419 } 3420 } 3421 3422 /** 3423 * @deprecated as of 1.5 use Controller::getController('PageNotFoundController')->run(); 3424 */ 3425 public static function display404Error() 3426 { 3427 header('HTTP/1.1 404 Not Found'); 3428 header('Status: 404 Not Found'); 3429 include __DIR__ . '/../404.php'; 3430 die; 3431 } 3432 3433 /** 3434 * Concat $begin and $end, add ? or & between strings. 3435 * 3436 * @since 1.5.0 3437 * 3438 * @param string $begin 3439 * @param string $end 3440 * 3441 * @return string 3442 */ 3443 public static function url($begin, $end) 3444 { 3445 return $begin . ((strpos($begin, '?') !== false) ? '&' : '?') . $end; 3446 } 3447 3448 /** 3449 * Display error and dies or silently log the error. 3450 * 3451 * @param string $msg 3452 * @param bool $die 3453 * 3454 * @return bool success of logging 3455 */ 3456 public static function dieOrLog($msg, $die = true) 3457 { 3458 if ($die || (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_)) { 3459 header('HTTP/1.1 500 Internal Server Error', true, 500); 3460 die($msg); 3461 } 3462 3463 return PrestaShopLogger::addLog($msg); 3464 } 3465 3466 /** 3467 * Convert \n and \r\n and \r to <br />. 3468 * 3469 * @param string $str String to transform 3470 * 3471 * @return string New string 3472 */ 3473 public static function nl2br($str) 3474 { 3475 return str_replace(["\r\n", "\r", "\n", AddressFormat::FORMAT_NEW_LINE, PHP_EOL], '<br />', $str); 3476 } 3477 3478 /** 3479 * Clear cache for Smarty. 3480 * 3481 * @param Smarty $smarty 3482 * @param bool $tpl 3483 * @param string $cache_id 3484 * @param string $compile_id 3485 * 3486 * @return int|null number of cache files deleted 3487 */ 3488 public static function clearCache($smarty = null, $tpl = false, $cache_id = null, $compile_id = null) 3489 { 3490 if ($smarty === null) { 3491 $smarty = Context::getContext()->smarty; 3492 } 3493 3494 if ($smarty === null) { 3495 return null; 3496 } 3497 3498 if (!$tpl && $cache_id === null && $compile_id === null) { 3499 return $smarty->clearAllCache(); 3500 } 3501 3502 $ret = $smarty->clearCache($tpl, $cache_id, $compile_id); 3503 3504 Hook::exec('actionClearCache'); 3505 3506 return $ret; 3507 } 3508 3509 /** 3510 * Clear compile for Smarty. 3511 * 3512 * @param Smarty $smarty 3513 * 3514 * @return int|null number of template files deleted 3515 */ 3516 public static function clearCompile($smarty = null) 3517 { 3518 if ($smarty === null) { 3519 $smarty = Context::getContext()->smarty; 3520 } 3521 3522 if ($smarty === null) { 3523 return null; 3524 } 3525 3526 $ret = $smarty->clearCompiledTemplate(); 3527 3528 Hook::exec('actionClearCompileCache'); 3529 3530 return $ret; 3531 } 3532 3533 /** 3534 * Clear Smarty cache and compile folders. 3535 */ 3536 public static function clearSmartyCache() 3537 { 3538 $smarty = Context::getContext()->smarty; 3539 Tools::clearCache($smarty); 3540 Tools::clearCompile($smarty); 3541 } 3542 3543 /** 3544 * Clear Symfony cache. 3545 * 3546 * @param string $env 3547 */ 3548 public static function clearSf2Cache($env = null) 3549 { 3550 if (null === $env) { 3551 $env = _PS_ENV_; 3552 } 3553 3554 $dir = _PS_ROOT_DIR_ . '/var/cache/' . $env . '/'; 3555 3556 register_shutdown_function(function () use ($dir) { 3557 $fs = new Filesystem(); 3558 $fs->remove($dir); 3559 Hook::exec('actionClearSf2Cache'); 3560 }); 3561 } 3562 3563 /** 3564 * Clear both Smarty and Symfony cache. 3565 */ 3566 public static function clearAllCache() 3567 { 3568 Tools::clearSmartyCache(); 3569 Tools::clearSf2Cache(); 3570 } 3571 3572 /** 3573 * @param int|bool $id_product 3574 */ 3575 public static function clearColorListCache($id_product = false) 3576 { 3577 // Change template dir if called from the BackOffice 3578 $current_template_dir = Context::getContext()->smarty->getTemplateDir(); 3579 Context::getContext()->smarty->setTemplateDir(_PS_THEME_DIR_); 3580 Tools::clearCache(null, _PS_THEME_DIR_ . 'product-list-colors.tpl', Product::getColorsListCacheId((int) $id_product, false)); 3581 Context::getContext()->smarty->setTemplateDir($current_template_dir); 3582 } 3583 3584 /** 3585 * Allow to get the memory limit in octets. 3586 * 3587 * @since 1.4.5.0 3588 * 3589 * @return int the memory limit value in octet 3590 */ 3591 public static function getMemoryLimit() 3592 { 3593 $memory_limit = @ini_get('memory_limit'); 3594 3595 return Tools::getOctets($memory_limit); 3596 } 3597 3598 /** 3599 * Gets the value of a configuration option in octets. 3600 * 3601 * @since 1.5.0 3602 * 3603 * @return int the value of a configuration option in octets 3604 */ 3605 public static function getOctets($option) 3606 { 3607 if (preg_match('/[0-9]+k/i', $option)) { 3608 return 1024 * (int) $option; 3609 } 3610 3611 if (preg_match('/[0-9]+m/i', $option)) { 3612 return 1024 * 1024 * (int) $option; 3613 } 3614 3615 if (preg_match('/[0-9]+g/i', $option)) { 3616 return 1024 * 1024 * 1024 * (int) $option; 3617 } 3618 3619 return $option; 3620 } 3621 3622 /** 3623 * @return bool true if the server use 64bit arch 3624 */ 3625 public static function isX86_64arch() 3626 { 3627 return PHP_INT_MAX == '9223372036854775807'; 3628 } 3629 3630 /** 3631 * @return bool true if php-cli is used 3632 */ 3633 public static function isPHPCLI() 3634 { 3635 return defined('STDIN') || (Tools::strtolower(PHP_SAPI) == 'cli' && (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR']))); 3636 } 3637 3638 public static function argvToGET($argc, $argv) 3639 { 3640 if ($argc <= 1) { 3641 return; 3642 } 3643 3644 // get the first argument and parse it like a query string 3645 parse_str($argv[1], $args); 3646 if (!is_array($args) || !count($args)) { 3647 return; 3648 } 3649 $_GET = array_merge($args, $_GET); 3650 $_SERVER['QUERY_STRING'] = $argv[1]; 3651 } 3652 3653 /** 3654 * Get max file upload size considering server settings and optional max value. 3655 * 3656 * @param int $max_size optional max file size 3657 * 3658 * @return int max file size in bytes 3659 */ 3660 public static function getMaxUploadSize($max_size = 0) 3661 { 3662 $values = [Tools::convertBytes(ini_get('upload_max_filesize'))]; 3663 3664 if ($max_size > 0) { 3665 $values[] = $max_size; 3666 } 3667 3668 $post_max_size = Tools::convertBytes(ini_get('post_max_size')); 3669 if ($post_max_size > 0) { 3670 $values[] = $post_max_size; 3671 } 3672 3673 return min($values); 3674 } 3675 3676 /** 3677 * apacheModExists return true if the apache module $name is loaded. 3678 * 3679 * @TODO move this method in class Information (when it will exist) 3680 * 3681 * Notes: This method requires either apache_get_modules or phpinfo() 3682 * to be available. With CGI mod, we cannot get php modules 3683 * 3684 * @param string $name module name 3685 * 3686 * @return bool true if exists 3687 * 3688 * @since 1.4.5.0 3689 */ 3690 public static function apacheModExists($name) 3691 { 3692 if (function_exists('apache_get_modules')) { 3693 static $apache_module_list = null; 3694 3695 if (!is_array($apache_module_list)) { 3696 $apache_module_list = apache_get_modules(); 3697 } 3698 3699 // we need strpos (example, evasive can be evasive20) 3700 foreach ($apache_module_list as $module) { 3701 if (strpos($module, $name) !== false) { 3702 return true; 3703 } 3704 } 3705 } 3706 3707 return false; 3708 } 3709 3710 /** 3711 * Fix native uasort see: http://php.net/manual/en/function.uasort.php#114535. 3712 * 3713 * @param $array 3714 * @param $cmp_function 3715 */ 3716 public static function uasort(&$array, $cmp_function) 3717 { 3718 if (count($array) < 2) { 3719 return; 3720 } 3721 $halfway = count($array) / 2; 3722 $array1 = array_slice($array, 0, $halfway, true); 3723 $array2 = array_slice($array, $halfway, null, true); 3724 3725 self::uasort($array1, $cmp_function); 3726 self::uasort($array2, $cmp_function); 3727 if (call_user_func($cmp_function, end($array1), reset($array2)) < 1) { 3728 $array = $array1 + $array2; 3729 3730 return; 3731 } 3732 $array = []; 3733 reset($array1); 3734 reset($array2); 3735 while (current($array1) && current($array2)) { 3736 if (call_user_func($cmp_function, current($array1), current($array2)) < 1) { 3737 $array[key($array1)] = current($array1); 3738 next($array1); 3739 } else { 3740 $array[key($array2)] = current($array2); 3741 next($array2); 3742 } 3743 } 3744 while (current($array1)) { 3745 $array[key($array1)] = current($array1); 3746 next($array1); 3747 } 3748 while (current($array2)) { 3749 $array[key($array2)] = current($array2); 3750 next($array2); 3751 } 3752 } 3753 3754 /** 3755 * Copy the folder $src into $dst, $dst is created if it do not exist. 3756 * 3757 * @param $src 3758 * @param $dst 3759 * @param bool $del if true, delete the file after copy 3760 */ 3761 public static function recurseCopy($src, $dst, $del = false) 3762 { 3763 if (!Tools::file_exists_cache($src)) { 3764 return false; 3765 } 3766 $dir = opendir($src); 3767 3768 if (!Tools::file_exists_cache($dst)) { 3769 mkdir($dst); 3770 } 3771 while (false !== ($file = readdir($dir))) { 3772 if (($file != '.') && ($file != '..')) { 3773 if (is_dir($src . DIRECTORY_SEPARATOR . $file)) { 3774 self::recurseCopy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file, $del); 3775 } else { 3776 copy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file); 3777 if ($del && is_writable($src . DIRECTORY_SEPARATOR . $file)) { 3778 unlink($src . DIRECTORY_SEPARATOR . $file); 3779 } 3780 } 3781 } 3782 } 3783 closedir($dir); 3784 if ($del && is_writable($src)) { 3785 rmdir($src); 3786 } 3787 } 3788 3789 /** 3790 * @param string $path Path to scan 3791 * @param string $ext Extention to filter files 3792 * @param string $dir Add this to prefix output for example /path/dir/* 3793 * 3794 * @return array List of file found 3795 * 3796 * @since 1.5.0 3797 */ 3798 public static function scandir($path, $ext = 'php', $dir = '', $recursive = false) 3799 { 3800 $path = rtrim(rtrim($path, '\\'), '/') . '/'; 3801 $real_path = rtrim(rtrim($path . $dir, '\\'), '/') . '/'; 3802 $files = scandir($real_path, SCANDIR_SORT_NONE); 3803 if (!$files) { 3804 return []; 3805 } 3806 3807 $filtered_files = []; 3808 3809 $real_ext = false; 3810 if (!empty($ext)) { 3811 $real_ext = '.' . $ext; 3812 } 3813 $real_ext_length = strlen($real_ext); 3814 3815 $subdir = ($dir) ? $dir . '/' : ''; 3816 foreach ($files as $file) { 3817 if (!$real_ext || (strpos($file, $real_ext) && strpos($file, $real_ext) == (strlen($file) - $real_ext_length))) { 3818 $filtered_files[] = $subdir . $file; 3819 } 3820 3821 if ($recursive && $file[0] != '.' && is_dir($real_path . $file)) { 3822 foreach (Tools::scandir($path, $ext, $subdir . $file, $recursive) as $subfile) { 3823 $filtered_files[] = $subfile; 3824 } 3825 } 3826 } 3827 3828 return $filtered_files; 3829 } 3830 3831 /** 3832 * Align version sent and use internal function. 3833 * 3834 * @param $v1 3835 * @param $v2 3836 * @param string $operator 3837 * 3838 * @return mixed 3839 */ 3840 public static function version_compare($v1, $v2, $operator = '<') 3841 { 3842 Tools::alignVersionNumber($v1, $v2); 3843 3844 return version_compare($v1, $v2, $operator); 3845 } 3846 3847 /** 3848 * Align 2 version with the same number of sub version 3849 * version_compare will work better for its comparison :) 3850 * (Means: '1.8' to '1.9.3' will change '1.8' to '1.8.0'). 3851 * 3852 * @param $v1 3853 * @param $v2 3854 */ 3855 public static function alignVersionNumber(&$v1, &$v2) 3856 { 3857 $len1 = count(explode('.', trim($v1, '.'))); 3858 $len2 = count(explode('.', trim($v2, '.'))); 3859 $len = 0; 3860 $str = ''; 3861 3862 if ($len1 > $len2) { 3863 $len = $len1 - $len2; 3864 $str = &$v2; 3865 } elseif ($len2 > $len1) { 3866 $len = $len2 - $len1; 3867 $str = &$v1; 3868 } 3869 3870 for ($len; $len > 0; --$len) { 3871 $str .= '.0'; 3872 } 3873 } 3874 3875 public static function modRewriteActive() 3876 { 3877 if (Tools::apacheModExists('mod_rewrite')) { 3878 return true; 3879 } 3880 if ((isset($_SERVER['HTTP_MOD_REWRITE']) && Tools::strtolower($_SERVER['HTTP_MOD_REWRITE']) == 'on') || Tools::strtolower(getenv('HTTP_MOD_REWRITE')) == 'on') { 3881 return true; 3882 } 3883 3884 return false; 3885 } 3886 3887 public static function unSerialize($serialized, $object = false) 3888 { 3889 if (is_string($serialized) && (strpos($serialized, 'O:') === false || !preg_match('/(^|;|{|})O:[0-9]+:"/', $serialized)) && !$object || $object) { 3890 return @unserialize($serialized); 3891 } 3892 3893 return false; 3894 } 3895 3896 /** 3897 * Reproduce array_unique working before php version 5.2.9. 3898 * 3899 * @param array $array 3900 * 3901 * @return array 3902 */ 3903 public static function arrayUnique($array) 3904 { 3905 return array_unique($array, SORT_REGULAR); 3906 } 3907 3908 /** 3909 * Delete unicode class from regular expression patterns. 3910 * 3911 * @deprecated Use PrestaShop\PrestaShop\Core\String\CharacterCleaner::cleanNonUnicodeSupport() instead 3912 * 3913 * @param string $pattern 3914 * 3915 * @return string pattern 3916 * 3917 * @throws Exception 3918 */ 3919 public static function cleanNonUnicodeSupport($pattern) 3920 { 3921 $context = Context::getContext(); 3922 $containerFinder = new ContainerFinder($context); 3923 try { 3924 $container = $containerFinder->getContainer(); 3925 $characterCleaner = $container->get('prestashop.core.string.character_cleaner'); 3926 } catch (ContainerNotFoundException $e) { 3927 // Used when the container is not generated 3928 $characterCleaner = new CharacterCleaner(); 3929 } 3930 3931 return $characterCleaner->cleanNonUnicodeSupport($pattern); 3932 } 3933 3934 protected static $is_addons_up = true; 3935 3936 public static function addonsRequest($request, $params = []) 3937 { 3938 if (!self::$is_addons_up) { 3939 return false; 3940 } 3941 3942 $post_query_data = [ 3943 'version' => isset($params['version']) ? $params['version'] : _PS_VERSION_, 3944 'iso_lang' => Tools::strtolower(isset($params['iso_lang']) ? $params['iso_lang'] : Context::getContext()->language->iso_code), 3945 'iso_code' => Tools::strtolower(isset($params['iso_country']) ? $params['iso_country'] : Country::getIsoById(Configuration::get('PS_COUNTRY_DEFAULT'))), 3946 'shop_url' => isset($params['shop_url']) ? $params['shop_url'] : Tools::getShopDomain(), 3947 'mail' => isset($params['email']) ? $params['email'] : Configuration::get('PS_SHOP_EMAIL'), 3948 'format' => isset($params['format']) ? $params['format'] : 'xml', 3949 ]; 3950 if (isset($params['source'])) { 3951 $post_query_data['source'] = $params['source']; 3952 } 3953 3954 $post_data = http_build_query($post_query_data); 3955 3956 $end_point = 'api.addons.prestashop.com'; 3957 3958 switch ($request) { 3959 case 'native': 3960 $post_data .= '&method=listing&action=native'; 3961 3962 break; 3963 case 'partner': 3964 $post_data .= '&method=listing&action=partner'; 3965 3966 break; 3967 case 'service': 3968 $post_data .= '&method=listing&action=service'; 3969 3970 break; 3971 case 'native_all': 3972 $post_data .= '&method=listing&action=native&iso_code=all'; 3973 3974 break; 3975 case 'must-have': 3976 $post_data .= '&method=listing&action=must-have'; 3977 3978 break; 3979 case 'must-have-themes': 3980 $post_data .= '&method=listing&action=must-have-themes'; 3981 3982 break; 3983 case 'customer': 3984 $post_data .= '&method=listing&action=customer&username=' . urlencode(trim(Context::getContext()->cookie->username_addons)) 3985 . '&password=' . urlencode(trim(Context::getContext()->cookie->password_addons)); 3986 3987 break; 3988 case 'customer_themes': 3989 $post_data .= '&method=listing&action=customer-themes&username=' . urlencode(trim(Context::getContext()->cookie->username_addons)) 3990 . '&password=' . urlencode(trim(Context::getContext()->cookie->password_addons)); 3991 3992 break; 3993 case 'check_customer': 3994 $post_data .= '&method=check_customer&username=' . urlencode($params['username_addons']) . '&password=' . urlencode($params['password_addons']); 3995 3996 break; 3997 case 'check_module': 3998 $post_data .= '&method=check&module_name=' . urlencode($params['module_name']) . '&module_key=' . urlencode($params['module_key']); 3999 4000 break; 4001 case 'module': 4002 $post_data .= '&method=module&id_module=' . urlencode($params['id_module']); 4003 if (isset($params['username_addons'], $params['password_addons'])) { 4004 $post_data .= '&username=' . urlencode($params['username_addons']) . '&password=' . urlencode($params['password_addons']); 4005 } 4006 4007 break; 4008 case 'hosted_module': 4009 $post_data .= '&method=module&id_module=' . urlencode((int) $params['id_module']) . '&username=' . urlencode($params['hosted_email']) 4010 . '&password=' . urlencode($params['password_addons']) 4011 . '&shop_url=' . urlencode(isset($params['shop_url']) ? $params['shop_url'] : Tools::getShopDomain()) 4012 . '&mail=' . urlencode(isset($params['email']) ? $params['email'] : Configuration::get('PS_SHOP_EMAIL')); 4013 4014 break; 4015 case 'install-modules': 4016 $post_data .= '&method=listing&action=install-modules'; 4017 $post_data .= defined('_PS_HOST_MODE_') ? '-od' : ''; 4018 4019 break; 4020 default: 4021 return false; 4022 } 4023 4024 $context = stream_context_create([ 4025 'http' => [ 4026 'method' => 'POST', 4027 'content' => $post_data, 4028 'header' => 'Content-type: application/x-www-form-urlencoded', 4029 'timeout' => 5, 4030 ], 4031 ]); 4032 4033 if ($content = Tools::file_get_contents('https://' . $end_point, false, $context)) { 4034 return $content; 4035 } 4036 4037 self::$is_addons_up = false; 4038 4039 return false; 4040 } 4041 4042 /** 4043 * Returns an array containing information about 4044 * HTTP file upload variable ($_FILES). 4045 * 4046 * @param string $input File upload field name 4047 * @param bool $return_content If true, returns uploaded file contents 4048 * 4049 * @return array|null 4050 */ 4051 public static function fileAttachment($input = 'fileUpload', $return_content = true) 4052 { 4053 $file_attachment = null; 4054 if (isset($_FILES[$input]['name']) && !empty($_FILES[$input]['name']) && !empty($_FILES[$input]['tmp_name'])) { 4055 $file_attachment['rename'] = uniqid() . Tools::strtolower(substr($_FILES[$input]['name'], -5)); 4056 if ($return_content) { 4057 $file_attachment['content'] = file_get_contents($_FILES[$input]['tmp_name']); 4058 } 4059 $file_attachment['tmp_name'] = $_FILES[$input]['tmp_name']; 4060 $file_attachment['name'] = $_FILES[$input]['name']; 4061 $file_attachment['mime'] = $_FILES[$input]['type']; 4062 $file_attachment['error'] = $_FILES[$input]['error']; 4063 $file_attachment['size'] = $_FILES[$input]['size']; 4064 } 4065 4066 return $file_attachment; 4067 } 4068 4069 public static function changeFileMTime($file_name) 4070 { 4071 @touch($file_name); 4072 } 4073 4074 public static function waitUntilFileIsModified($file_name, $timeout = 180) 4075 { 4076 @ini_set('max_execution_time', $timeout); 4077 if (($time_limit = ini_get('max_execution_time')) === null) { 4078 $time_limit = 30; 4079 } 4080 4081 $time_limit -= 5; 4082 $start_time = microtime(true); 4083 $last_modified = @filemtime($file_name); 4084 4085 while (true) { 4086 if (((microtime(true) - $start_time) > $time_limit) || @filemtime($file_name) > $last_modified) { 4087 break; 4088 } 4089 clearstatcache(); 4090 usleep(300); 4091 } 4092 } 4093 4094 /** 4095 * Delete a substring from another one starting from the right. 4096 * 4097 * @param string $str 4098 * @param string $str_search 4099 * 4100 * @return string 4101 */ 4102 public static function rtrimString($str, $str_search) 4103 { 4104 $length_str = strlen($str_search); 4105 if (strlen($str) >= $length_str && substr($str, -$length_str) == $str_search) { 4106 $str = substr($str, 0, -$length_str); 4107 } 4108 4109 return $str; 4110 } 4111 4112 /** 4113 * Format a number into a human readable format 4114 * e.g. 24962496 => 23.81M. 4115 * 4116 * @param $size 4117 * @param int $precision 4118 * 4119 * @return string 4120 */ 4121 public static function formatBytes($size, $precision = 2) 4122 { 4123 if (!$size) { 4124 return '0'; 4125 } 4126 $base = log($size) / log(1024); 4127 $suffixes = ['', 'KB', 'MB', 'GB', 'TB']; 4128 4129 return round(1024 ** ($base - floor($base)), $precision) . Context::getContext()->getTranslator()->trans($suffixes[floor($base)], [], 'Shop.Theme.Catalog'); 4130 } 4131 4132 public static function boolVal($value) 4133 { 4134 if (empty($value)) { 4135 $value = false; 4136 } 4137 4138 return (bool) $value; 4139 } 4140 4141 public static function getUserPlatform() 4142 { 4143 if (isset(self::$_user_plateform)) { 4144 return self::$_user_plateform; 4145 } 4146 4147 $user_agent = $_SERVER['HTTP_USER_AGENT']; 4148 self::$_user_plateform = 'unknown'; 4149 4150 if (preg_match('/linux/i', $user_agent)) { 4151 self::$_user_plateform = 'Linux'; 4152 } elseif (preg_match('/macintosh|mac os x/i', $user_agent)) { 4153 self::$_user_plateform = 'Mac'; 4154 } elseif (preg_match('/windows|win32/i', $user_agent)) { 4155 self::$_user_plateform = 'Windows'; 4156 } 4157 4158 return self::$_user_plateform; 4159 } 4160 4161 public static function getUserBrowser() 4162 { 4163 if (isset(self::$_user_browser)) { 4164 return self::$_user_browser; 4165 } 4166 4167 $user_agent = $_SERVER['HTTP_USER_AGENT']; 4168 self::$_user_browser = 'unknown'; 4169 4170 if (preg_match('/MSIE/i', $user_agent) && !preg_match('/Opera/i', $user_agent)) { 4171 self::$_user_browser = 'Internet Explorer'; 4172 } elseif (preg_match('/Firefox/i', $user_agent)) { 4173 self::$_user_browser = 'Mozilla Firefox'; 4174 } elseif (preg_match('/Chrome/i', $user_agent)) { 4175 self::$_user_browser = 'Google Chrome'; 4176 } elseif (preg_match('/Safari/i', $user_agent)) { 4177 self::$_user_browser = 'Apple Safari'; 4178 } elseif (preg_match('/Opera/i', $user_agent)) { 4179 self::$_user_browser = 'Opera'; 4180 } elseif (preg_match('/Netscape/i', $user_agent)) { 4181 self::$_user_browser = 'Netscape'; 4182 } 4183 4184 return self::$_user_browser; 4185 } 4186 4187 /** 4188 * Allows to display the category description without HTML tags and slashes. 4189 * 4190 * @return string 4191 */ 4192 public static function getDescriptionClean($description) 4193 { 4194 return strip_tags(stripslashes($description)); 4195 } 4196 4197 public static function purifyHTML($html, $uri_unescape = null, $allow_style = false) 4198 { 4199 static $use_html_purifier = null; 4200 static $purifier = null; 4201 4202 if (defined('PS_INSTALLATION_IN_PROGRESS') || !Configuration::configurationIsLoaded()) { 4203 return $html; 4204 } 4205 4206 if ($use_html_purifier === null) { 4207 $use_html_purifier = (bool) Configuration::get('PS_USE_HTMLPURIFIER'); 4208 } 4209 4210 if ($use_html_purifier) { 4211 if ($purifier === null) { 4212 $config = HTMLPurifier_Config::createDefault(); 4213 4214 $config->set('Attr.EnableID', true); 4215 $config->set('Attr.AllowedRel', ['nofollow']); 4216 $config->set('HTML.Trusted', true); 4217 $config->set('Cache.SerializerPath', _PS_CACHE_DIR_ . 'purifier'); 4218 $config->set('Attr.AllowedFrameTargets', ['_blank', '_self', '_parent', '_top']); 4219 if (is_array($uri_unescape)) { 4220 $config->set('URI.UnescapeCharacters', implode('', $uri_unescape)); 4221 } 4222 4223 if (Configuration::get('PS_ALLOW_HTML_IFRAME')) { 4224 $config->set('HTML.SafeIframe', true); 4225 $config->set('HTML.SafeObject', true); 4226 $config->set('URI.SafeIframeRegexp', '/.*/'); 4227 } 4228 4229 /** @var HTMLPurifier_HTMLDefinition|HTMLPurifier_HTMLModule $def */ 4230 // http://developers.whatwg.org/the-video-element.html#the-video-element 4231 if ($def = $config->getHTMLDefinition(true)) { 4232 $def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [ 4233 'src' => 'URI', 4234 'type' => 'Text', 4235 'width' => 'Length', 4236 'height' => 'Length', 4237 'poster' => 'URI', 4238 'preload' => 'Enum#auto,metadata,none', 4239 'controls' => 'Bool', 4240 ]); 4241 $def->addElement('source', 'Block', 'Flow', 'Common', [ 4242 'src' => 'URI', 4243 'type' => 'Text', 4244 ]); 4245 if ($allow_style) { 4246 $def->addElement('style', 'Block', 'Flow', 'Common', ['type' => 'Text']); 4247 } 4248 } 4249 4250 $purifier = new HTMLPurifier($config); 4251 } 4252 4253 $html = $purifier->purify($html); 4254 } 4255 4256 return $html; 4257 } 4258 4259 /** 4260 * Check if a constant was already defined. 4261 * 4262 * @param string $constant Constant name 4263 * @param mixed $value Default value to set if not defined 4264 */ 4265 public static function safeDefine($constant, $value) 4266 { 4267 if (!defined($constant)) { 4268 define($constant, $value); 4269 } 4270 } 4271 4272 /** 4273 * Spread an amount on lines, adjusting the $column field, 4274 * with the biggest adjustments going to the rows having the 4275 * highest $sort_column. 4276 * 4277 * E.g.: 4278 * 4279 * $rows = [['a' => 5.1], ['a' => 8.2]]; 4280 * 4281 * spreadAmount(0.3, 1, $rows, 'a'); 4282 * 4283 * => $rows is [['a' => 8.4], ['a' => 5.2]] 4284 * 4285 * @param $amount float The amount to spread across the rows 4286 * @param $precision int Rounding precision 4287 * e.g. if $amount is 1, $precision is 0 and $rows = [['a' => 2], ['a' => 1]] 4288 * then the resulting $rows will be [['a' => 3], ['a' => 1]] 4289 * But if $precision were 1, then the resulting $rows would be [['a' => 2.5], ['a' => 1.5]] 4290 * @param &$rows array An array, associative or not, containing arrays that have at least $column and $sort_column fields 4291 * @param $column string The column on which to perform adjustments 4292 */ 4293 public static function spreadAmount($amount, $precision, &$rows, $column) 4294 { 4295 if (!is_array($rows) || empty($rows)) { 4296 return; 4297 } 4298 4299 $sort_function = function ($a, $b) use ($column) { return $b[$column] > $a[$column] ? 1 : -1; }; 4300 4301 uasort($rows, $sort_function); 4302 4303 $unit = 10 ** $precision; 4304 4305 $int_amount = (int) round($unit * $amount); 4306 4307 $remainder = $int_amount % count($rows); 4308 $amount_to_spread = ($int_amount - $remainder) / count($rows) / $unit; 4309 4310 $sign = ($amount >= 0 ? 1 : -1); 4311 $position = 0; 4312 foreach ($rows as &$row) { 4313 $adjustment_factor = $amount_to_spread; 4314 4315 if ($position < abs($remainder)) { 4316 $adjustment_factor += $sign * 1 / $unit; 4317 } 4318 4319 $row[$column] += $adjustment_factor; 4320 4321 ++$position; 4322 } 4323 unset($row); 4324 } 4325 4326 /** 4327 * Replaces elements from passed arrays into the first array recursively. 4328 * 4329 * @param array $base the array in which elements are replaced 4330 * @param array $replacements the array from which elements will be extracted 4331 */ 4332 public static function arrayReplaceRecursive($base, $replacements) 4333 { 4334 if (function_exists('array_replace_recursive')) { 4335 return array_replace_recursive($base, $replacements); 4336 } 4337 4338 foreach (array_slice(func_get_args(), 1) as $replacements) { 4339 $bref_stack = [&$base]; 4340 $head_stack = [$replacements]; 4341 4342 do { 4343 end($bref_stack); 4344 4345 $bref = &$bref_stack[key($bref_stack)]; 4346 $head = array_pop($head_stack); 4347 unset($bref_stack[key($bref_stack)]); 4348 foreach (array_keys($head) as $key) { 4349 if (isset($key, $bref) && is_array($bref[$key]) && is_array($head[$key])) { 4350 $bref_stack[] = &$bref[$key]; 4351 $head_stack[] = $head[$key]; 4352 } else { 4353 $bref[$key] = $head[$key]; 4354 } 4355 } 4356 } while (count($head_stack)); 4357 } 4358 4359 return $base; 4360 } 4361 4362 /** 4363 * Return path to a Product or a CMS category. 4364 * 4365 * @param string $url_base Start URL 4366 * @param int $id_category Start category 4367 * @param string $path Current path 4368 * @param string $highlight String to highlight (in XHTML/CSS) 4369 * @param string $type Category type (products/cms) 4370 */ 4371 public static function getPath($url_base, $id_category, $path = '', $highlight = '', $category_type = 'catalog', $home = false) 4372 { 4373 $context = Context::getContext(); 4374 if ($category_type == 'catalog') { 4375 $category = Db::getInstance()->getRow(' 4376 SELECT id_category, level_depth, nleft, nright 4377 FROM ' . _DB_PREFIX_ . 'category 4378 WHERE id_category = ' . (int) $id_category); 4379 if (isset($category['id_category'])) { 4380 $sql = 'SELECT c.id_category, cl.name, cl.link_rewrite 4381 FROM ' . _DB_PREFIX_ . 'category c 4382 LEFT JOIN ' . _DB_PREFIX_ . 'category_lang cl ON (cl.id_category = c.id_category' . Shop::addSqlRestrictionOnLang('cl') . ') 4383 WHERE c.nleft <= ' . (int) $category['nleft'] . ' 4384 AND c.nright >= ' . (int) $category['nright'] . ' 4385 AND cl.id_lang = ' . (int) $context->language->id . 4386 ($home ? ' AND c.id_category=' . (int) $id_category : '') . ' 4387 AND c.id_category != ' . (int) Category::getTopCategory()->id . ' 4388 GROUP BY c.id_category 4389 ORDER BY c.level_depth ASC 4390 LIMIT ' . (!$home ? (int) $category['level_depth'] + 1 : 1); 4391 $categories = Db::getInstance()->executeS($sql); 4392 $full_path = ''; 4393 $n = 1; 4394 $n_categories = (int) count($categories); 4395 foreach ($categories as $category) { 4396 $action = (($category['id_category'] == (int) Configuration::get('PS_HOME_CATEGORY') || $home) ? 'index' : 'updatecategory'); 4397 $link_params = ['action' => $action, 'id_category' => (int) $category['id_category']]; 4398 $edit_link = Context::getContext()->link->getAdminLink('AdminCategories', true, $link_params); 4399 $link_params['action'] = 'index'; 4400 $index_link = Context::getContext()->link->getAdminLink('AdminCategories', true, $link_params); 4401 $edit = '<a href="' . Tools::safeOutput($edit_link) . '" title="' . ($category['id_category'] == Category::getRootCategory()->id_category ? 'Home' : 'Modify') . '"><i class="icon-' . (($category['id_category'] == Category::getRootCategory()->id_category || $home) ? 'home' : 'pencil') . '"></i></a> '; 4402 $full_path .= $edit . 4403 ($n < $n_categories ? '<a href="' . Tools::safeOutput($index_link) . '" title="' . htmlentities($category['name'], ENT_NOQUOTES, 'UTF-8') . '">' : '') . 4404 (!empty($highlight) ? str_ireplace($highlight, '<span class="highlight">' . htmlentities($highlight, ENT_NOQUOTES, 'UTF-8') . '</span>', $category['name']) : $category['name']) . 4405 ($n < $n_categories ? '</a>' : '') . 4406 (($n++ != $n_categories || !empty($path)) ? ' > ' : ''); 4407 } 4408 4409 return $full_path . $path; 4410 } 4411 } elseif ($category_type == 'cms') { 4412 $category = new CMSCategory($id_category, $context->language->id); 4413 if (!$category->id) { 4414 return $path; 4415 } 4416 $name = ($highlight != null) ? str_ireplace($highlight, '<span class="highlight">' . $highlight . '</span>', CMSCategory::hideCMSCategoryPosition($category->name)) : CMSCategory::hideCMSCategoryPosition($category->name); 4417 $edit = '<a href="' . Tools::safeOutput($url_base . '&id_cms_category=' . $category->id . '&updatecms_category&token=' . Tools::getAdminToken('AdminCmsContent' . (int) Tab::getIdFromClassName('AdminCmsContent') . (int) $context->employee->id)) . '"> 4418 <i class="icon-pencil"></i></a> '; 4419 if ($category->id == 1) { 4420 $edit = '<li><a href="' . Tools::safeOutput($url_base . '&id_cms_category=' . $category->id . '&viewcategory&token=' . Tools::getAdminToken('AdminCmsContent' . (int) Tab::getIdFromClassName('AdminCmsContent') . (int) $context->employee->id)) . '"> 4421 <i class="icon-home"></i></a></li> '; 4422 } 4423 $path = $edit . '<li><a href="' . Tools::safeOutput($url_base . '&id_cms_category=' . $category->id . '&viewcategory&token=' . Tools::getAdminToken('AdminCmsContent' . (int) Tab::getIdFromClassName('AdminCmsContent') . (int) $context->employee->id)) . '"> 4424 ' . $name . '</a></li> > ' . $path; 4425 if ($category->id == 1) { 4426 return substr($path, 0, strlen($path) - 3); 4427 } 4428 4429 return Tools::getPath($url_base, $category->id_parent, $path, '', 'cms'); 4430 } 4431 } 4432 4433 public static function redirectToInstall() 4434 { 4435 if (file_exists(__DIR__ . '/../install')) { 4436 if (defined('_PS_ADMIN_DIR_')) { 4437 header('Location: ../install/'); 4438 } else { 4439 header('Location: install/'); 4440 } 4441 } elseif (file_exists(__DIR__ . '/../install-dev')) { 4442 if (defined('_PS_ADMIN_DIR_')) { 4443 header('Location: ../install-dev/'); 4444 } else { 4445 header('Location: install-dev/'); 4446 } 4447 } else { 4448 die('Error: "install" directory is missing'); 4449 } 4450 exit; 4451 } 4452 4453 /** 4454 * @param array $fallbackParameters 4455 */ 4456 public static function setFallbackParameters(array $fallbackParameters): void 4457 { 4458 static::$fallbackParameters = $fallbackParameters; 4459 } 4460 4461 /** 4462 * @param string $file_to_refresh 4463 * @param string $external_file 4464 * 4465 * @return bool 4466 */ 4467 public static function refreshFile(string $file_to_refresh, string $external_file): bool 4468 { 4469 return (bool) static::copy($external_file, _PS_ROOT_DIR_ . $file_to_refresh); 4470 } 4471 4472 /** 4473 * @param string $file 4474 * @param int $timeout 4475 * 4476 * @return bool 4477 */ 4478 public static function isFileFresh(string $file, int $timeout = self::CACHE_LIFETIME_SECONDS): bool 4479 { 4480 if (($time = @filemtime(_PS_ROOT_DIR_ . $file)) && filesize(_PS_ROOT_DIR_ . $file) > 0) { 4481 return (time() - $time) < $timeout; 4482 } 4483 4484 return false; 4485 } 4486} 4487 4488/** 4489 * Compare 2 prices to sort products. 4490 * 4491 * @param float $a 4492 * @param float $b 4493 * 4494 * @return int 4495 */ 4496/* Externalized because of a bug in PHP 5.1.6 when inside an object */ 4497function cmpPriceAsc($a, $b) 4498{ 4499 if ((float) $a['price_tmp'] < (float) $b['price_tmp']) { 4500 return -1; 4501 } elseif ((float) $a['price_tmp'] > (float) $b['price_tmp']) { 4502 return 1; 4503 } 4504 4505 return 0; 4506} 4507 4508/** 4509 * @param array $a 4510 * @param array $b 4511 * 4512 * @return int 4513 */ 4514function cmpPriceDesc($a, $b) 4515{ 4516 if ((float) $a['price_tmp'] < (float) $b['price_tmp']) { 4517 return 1; 4518 } elseif ((float) $a['price_tmp'] > (float) $b['price_tmp']) { 4519 return -1; 4520 } 4521 4522 return 0; 4523} 4524