JEMBOT MAWOT Bypass Shell
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
use Composer\CaBundle\CaBundle;
use PHPSQLParser\PHPSQLParser;
use PrestaShop\PrestaShop\Adapter\ContainerFinder;
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use PrestaShop\PrestaShop\Core\Cache\Clearer\CacheClearerInterface;
use PrestaShop\PrestaShop\Core\Foundation\Filesystem\FileSystem as PsFileSystem;
use PrestaShop\PrestaShop\Core\Localization\Exception\LocalizationException;
use PrestaShop\PrestaShop\Core\Localization\Locale;
use PrestaShop\PrestaShop\Core\Localization\Locale\Repository as LocaleRepository;
use PrestaShop\PrestaShop\Core\Security\Hashing;
use PrestaShop\PrestaShop\Core\Security\OpenSsl\OpenSSL;
use PrestaShop\PrestaShop\Core\Security\PasswordGenerator;
use PrestaShop\PrestaShop\Core\Util\ColorBrightnessCalculator;
use PrestaShop\PrestaShop\Core\Util\String\StringModifier;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Request;
class ToolsCore
{
public const CACERT_LOCATION = 'https://curl.haxx.se/ca/cacert.pem';
public const SERVICE_LOCALE_REPOSITORY = 'prestashop.core.localization.locale.repository';
public const CACHE_LIFETIME_SECONDS = 604800;
public const PASSWORDGEN_FLAG_NUMERIC = PasswordGenerator::PASSWORDGEN_FLAG_NUMERIC;
public const PASSWORDGEN_FLAG_NO_NUMERIC = PasswordGenerator::PASSWORDGEN_FLAG_NO_NUMERIC;
public const PASSWORDGEN_FLAG_RANDOM = PasswordGenerator::PASSWORDGEN_FLAG_RANDOM;
public const PASSWORDGEN_FLAG_ALPHANUMERIC = PasswordGenerator::PASSWORDGEN_FLAG_ALPHANUMERIC;
public const LANGUAGE_EXTRACTOR_REGEXP = '#(?<=-)\w\w|\w\w(?!-)#';
protected static $file_exists_cache = [];
protected static $_forceCompile;
protected static $_caching;
protected static $_string_modifier;
protected static $_user_plateform;
protected static $_user_browser;
protected static $request;
protected static $cldr_cache = [];
protected static $colorBrightnessCalculator;
protected static $fallbackParameters = [];
public static $round_mode = null;
/**
* @param Request $request
*/
public function __construct(Request $request = null)
{
if ($request) {
self::$request = $request;
}
}
/**
* Properly clean static cache
*/
public static function resetStaticCache()
{
static::$cldr_cache = [];
}
/**
* Reset the request set during the first new Tools($request) call.
*/
public static function resetRequest()
{
self::$request = null;
}
/**
* Random password generator.
*
* @param int $length Desired length (optional)
* @param string $flag Output type (NUMERIC, ALPHANUMERIC, NO_NUMERIC, RANDOM)
*
* @return bool|string Password
*/
public static function passwdGen($length = 8, $flag = self::PASSWORDGEN_FLAG_ALPHANUMERIC)
{
try {
return (new PasswordGenerator(new OpenSSL()))->generatePassword($length, $flag);
} catch (InvalidArgumentException $exception) {
return false;
}
}
/**
* Random bytes generator.
*
* Limited to OpenSSL since 1.7.0.0
*
* @deprecated Since 8.1.0
*
* @param int $length Desired length of random bytes
*
* @return bool|string Random bytes
*/
public static function getBytes($length)
{
@trigger_error(
'Tools::getBytes() is deprecated since version 8.1.0.',
E_USER_DEPRECATED
);
try {
return (new OpenSSL())->getBytes($length);
} catch (\Exception $e) {
return false;
}
}
/**
* Replace text within a portion of a string.
*
* Replaces a string matching a search, (optionally) string from a certain position
*
* @param string $search The string to search in the input string
* @param string $replace The replacement string
* @param string $subject The input string
* @param int $cur Starting position cursor for the search
*
* @return string the result string is returned
*/
public static function strReplaceFirst($search, $replace, $subject, $cur = 0)
{
$strPos = strpos($subject, $search, $cur);
return $strPos !== false ? substr_replace($subject, $replace, (int) $strPos, strlen($search)) : $subject;
}
/**
* Redirect user to another page.
*
* Warning: uses exit
*
* @param string $url Desired URL
* @param string $base_uri Base URI (optional)
* @param Link|null $link
* @param string|array $headers A list of headers to send before redirection
*/
public static function redirect($url, $base_uri = __PS_BASE_URI__, Link $link = null, $headers = null)
{
if (!$link) {
$link = Context::getContext()->link;
}
if (!preg_match('@^https?://@i', $url) && $link) {
if (strpos($url, $base_uri) === 0) {
$url = substr($url, strlen($base_uri));
}
if (strpos($url, 'index.php?controller=') === 0) {
$url = substr($url, strlen('index.php?controller='));
if (Configuration::get('PS_REWRITING_SETTINGS')) {
$url = Tools::strReplaceFirst('&', '?', $url);
}
}
$explode = explode('?', $url);
$url = $link->getPageLink($explode[0]);
if (isset($explode[1])) {
$url .= '?' . $explode[1];
}
}
// Send additional headers
if ($headers) {
if (!is_array($headers)) {
$headers = [$headers];
}
foreach ($headers as $header) {
header($header);
}
}
header('Location: ' . $url);
exit;
}
/**
* Redirect URLs already containing PS_BASE_URI.
*
* Warning: uses exit
*
* @param string $url Desired URL
*
* @deprecated since PrestaShop 8.0.0
*/
public static function redirectLink($url)
{
Tools::displayAsDeprecated('Use Tools::redirect() instead');
static::redirect($url);
}
/**
* Redirect user to another page (using header Location)
*
* Warning: uses exit
*
* @param string $url Desired URL
*/
public static function redirectAdmin($url)
{
header('Location: ' . $url);
exit;
}
/**
* Returns the available protocol for the current shop in use
* SSL if Configuration is set on and available for the server.
*
* @return string
*/
public static function getShopProtocol()
{
$protocol = (Configuration::get('PS_SSL_ENABLED') || Tools::usingSecureMode()) ? 'https://' : 'http://';
return $protocol;
}
/**
* Returns the set protocol according to configuration (http[s]).
*
* @param bool $use_ssl true if require ssl
*
* @return string (http|https)
*/
public static function getProtocol($use_ssl = null)
{
return null !== $use_ssl && $use_ssl ? 'https://' : 'http://';
}
/**
* Returns the <b>current</b> host used, with the protocol (http or https) if $http is true
* This function should not be used to choose http or https domain name.
* Use Tools::getShopDomain() or Tools::getShopDomainSsl instead.
*
* @param bool $http
* @param bool $entities
* @param bool $ignore_port
*
* @return string host
*/
public static function getHttpHost($http = false, $entities = false, $ignore_port = false)
{
$httpHost = '';
if (array_key_exists('HTTP_HOST', $_SERVER)) {
$httpHost = $_SERVER['HTTP_HOST'];
}
$host = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $httpHost);
if ($ignore_port && $pos = strpos($host, ':')) {
$host = substr($host, 0, $pos);
}
if ($entities) {
$host = htmlspecialchars($host, ENT_COMPAT, 'UTF-8');
}
if ($http) {
$host = static::getProtocol((bool) Configuration::get('PS_SSL_ENABLED')) . $host;
}
return $host;
}
/**
* Returns domain name according to configuration and ignoring ssl.
*
* @param bool $http if true, return domain name with protocol
* @param bool $entities if true, convert special chars to HTML entities
*
* @return string domain
*/
public static function getShopDomain($http = false, $entities = false)
{
if (!$domain = ShopUrl::getMainShopDomain()) {
$domain = Tools::getHttpHost();
}
if ($entities) {
$domain = htmlspecialchars($domain, ENT_COMPAT, 'UTF-8');
}
if ($http) {
$domain = 'http://' . $domain;
}
return $domain;
}
/**
* Returns domain name according to configuration and depending on ssl activation.
*
* @param bool $http if true, return domain name with protocol
* @param bool $entities if true, convert special chars to HTML entities
*
* @return string domain
*/
public static function getShopDomainSsl($http = false, $entities = false)
{
if (!$domain = ShopUrl::getMainShopDomainSSL()) {
$domain = Tools::getHttpHost();
}
if ($entities) {
$domain = htmlspecialchars($domain, ENT_COMPAT, 'UTF-8');
}
if ($http) {
$domain = static::getProtocol((bool) Configuration::get('PS_SSL_ENABLED')) . $domain;
}
return $domain;
}
/**
* Get the server variable SERVER_NAME.
* Relies on $_SERVER
*
* @return string server name
*/
public static function getServerName()
{
if (isset($_SERVER['HTTP_X_FORWARDED_SERVER']) && $_SERVER['HTTP_X_FORWARDED_SERVER']) {
return $_SERVER['HTTP_X_FORWARDED_SERVER'];
}
return $_SERVER['SERVER_NAME'];
}
/**
* Get the server variable REMOTE_ADDR, or the first ip of HTTP_X_FORWARDED_FOR (when using proxy).
*
* @return string $remote_addr ip of client
*/
public static function getRemoteAddr()
{
if (function_exists('apache_request_headers')) {
$headers = apache_request_headers();
} else {
$headers = $_SERVER;
}
if (array_key_exists('X-Forwarded-For', $headers)) {
$_SERVER['HTTP_X_FORWARDED_FOR'] = $headers['X-Forwarded-For'];
}
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] && (!isset($_SERVER['REMOTE_ADDR'])
|| preg_match('/^127\..*/i', trim($_SERVER['REMOTE_ADDR'])) || preg_match('/^172\.(1[6-9]|2\d|30|31)\..*/i', trim($_SERVER['REMOTE_ADDR']))
|| preg_match('/^192\.168\.*/i', trim($_SERVER['REMOTE_ADDR'])) || preg_match('/^10\..*/i', trim($_SERVER['REMOTE_ADDR'])))) {
if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return $ips[0];
} else {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
/**
* Check if the current page use SSL connection on not.
* Relies on $_SERVER global being filled
*
* @return bool true if SSL is used
*/
public static function usingSecureMode()
{
if (isset($_SERVER['HTTPS'])) {
return in_array(Tools::strtolower($_SERVER['HTTPS']), [1, 'on']);
}
// $_SERVER['SSL'] exists only in some specific configuration
if (isset($_SERVER['SSL'])) {
return in_array(Tools::strtolower($_SERVER['SSL']), [1, 'on']);
}
// $_SERVER['REDIRECT_HTTPS'] exists only in some specific configuration
if (isset($_SERVER['REDIRECT_HTTPS'])) {
return in_array(Tools::strtolower($_SERVER['REDIRECT_HTTPS']), [1, 'on']);
}
if (isset($_SERVER['HTTP_SSL'])) {
return in_array(Tools::strtolower($_SERVER['HTTP_SSL']), [1, 'on']);
}
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
return Tools::strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https';
}
return false;
}
/**
* Get the current url prefix protocol (https/http).
*
* @return string protocol
*/
public static function getCurrentUrlProtocolPrefix()
{
if (Tools::usingSecureMode()) {
return 'https://';
} else {
return 'http://';
}
}
/**
* Get the current url
*
* @return string current url
*/
public static function getCurrentUrl(): string
{
return Tools::getCurrentUrlProtocolPrefix() . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
}
/**
* Returns a safe URL referrer.
*
* @param string $referrer URL referrer
*
* @return string secured referrer
*/
public static function secureReferrer($referrer)
{
if (static::urlBelongsToShop($referrer)) {
return $referrer;
}
return __PS_BASE_URI__;
}
/**
* Indicates if the provided URL belongs to this shop (relative urls count as belonging to the shop).
*
* @param string $url
*
* @return bool
*/
public static function urlBelongsToShop($url)
{
$urlHost = Tools::extractHost($url);
return empty($urlHost) || $urlHost === Tools::getServerName();
}
/**
* Safely extracts the host part from an URL.
*
* @param string $url
*
* @return string
*/
public static function extractHost($url)
{
$parsed = parse_url($url);
if (!is_array($parsed)) {
return $url;
}
if (empty($parsed['host']) || empty($parsed['scheme'])) {
return '';
}
return $parsed['host'];
}
/**
* Get a value from $_POST / $_GET
* if unavailable, take a default value.
*
* @param string $key Value key
* @param mixed $default_value (optional)
*
* @return mixed Value
*/
public static function getValue($key, $default_value = false)
{
if (empty($key) || !is_string($key)) {
return false;
}
if (getenv('kernel.environment') === 'test' && self::$request instanceof Request) {
$value = self::$request->request->get($key, self::$request->query->get($key, $default_value));
} elseif (isset($_POST[$key]) || isset($_GET[$key])) {
$value = isset($_POST[$key]) ? $_POST[$key] : $_GET[$key];
} elseif (isset(static::$fallbackParameters[$key])) {
$value = static::$fallbackParameters[$key];
}
if (!isset($value)) {
$value = $default_value;
}
if (is_string($value)) {
return urldecode(preg_replace('/((\%5C0+)|(\%00+))/i', '', urlencode($value)));
}
return $value;
}
/**
* Get all values from $_POST/$_GET.
*
* @return mixed
*/
public static function getAllValues()
{
return $_POST + $_GET;
}
/**
* Checks if a key exists either in $_POST or $_GET.
*
* @param string $key
*
* @return bool
*/
public static function getIsset($key)
{
if (!is_string($key)) {
return false;
}
return isset($_POST[$key]) || isset($_GET[$key]);
}
/**
* Change language in cookie while clicking on a flag.
*
* @return string iso code
*/
public static function setCookieLanguage($cookie = null)
{
if (!$cookie) {
$cookie = Context::getContext()->cookie;
}
/* If language does not exist or is disabled, erase it */
if ($cookie->id_lang) {
$lang = new Language((int) $cookie->id_lang);
if (!Validate::isLoadedObject($lang) || !$lang->active || !$lang->isAssociatedToShop()) {
$cookie->id_lang = null;
}
}
if (!Configuration::get('PS_DETECT_LANG')) {
unset($cookie->detect_language);
}
/* Automatically detect language if not already defined, detect_language is set in Cookie::update */
if (
!Tools::getValue('isolang') &&
!Tools::getValue('id_lang') &&
(!$cookie->id_lang || isset($cookie->detect_language))
&& isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])
) {
$array = explode(',', Tools::strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE']));
$string = $array[0];
if (Validate::isLanguageCode($string)) {
$lang = Language::getLanguageByIETFCode($string);
if (Validate::isLoadedObject($lang) && $lang->active && $lang->isAssociatedToShop()) {
Context::getContext()->language = $lang;
$cookie->id_lang = (int) $lang->id;
}
}
}
if (isset($cookie->detect_language)) {
unset($cookie->detect_language);
}
/* If language file not present, you must use default language file */
if (!$cookie->id_lang || !Validate::isUnsignedId($cookie->id_lang)) {
$cookie->id_lang = (int) Configuration::get('PS_LANG_DEFAULT');
}
$iso = Language::getIsoById((int) $cookie->id_lang);
@include_once _PS_THEME_DIR_ . 'lang/' . $iso . '.php';
return $iso;
}
/**
* If necessary change cookie language ID and context language.
*
* @param Context|null $context
*
* @throws PrestaShopDatabaseException
* @throws PrestaShopException
*/
public static function switchLanguage(Context $context = null)
{
if (null === $context) {
$context = Context::getContext();
}
// On PrestaShop installations Dispatcher::__construct() gets called (and so Tools::switchLanguage())
// Stop in this case by checking the cookie
if (!isset($context->cookie)) {
return;
}
if (
($iso = Tools::getValue('isolang')) &&
Validate::isLanguageIsoCode($iso) &&
($id_lang = (int) Language::getIdByIso($iso))
) {
$_GET['id_lang'] = $id_lang;
}
// Only switch if new ID is different from old ID
$newLanguageId = (int) Tools::getValue('id_lang');
if (
Validate::isUnsignedId($newLanguageId) &&
$newLanguageId !== 0 &&
$context->cookie->id_lang !== $newLanguageId
) {
$context->cookie->id_lang = $newLanguageId;
$language = new Language($newLanguageId);
if (Validate::isLoadedObject($language) && $language->active && $language->isAssociatedToShop()) {
$context->language = $language;
}
}
Tools::setCookieLanguage($context->cookie);
}
public static function getCountry($address = null)
{
$countryId = Tools::getValue('id_country');
if (
Validate::isInt($countryId)
&& (int) $countryId > 0
&& !empty(Country::getIsoById((int) $countryId))
) {
return (int) $countryId;
}
if (!empty($address->id_country) && (int) $address->id_country > 0) {
return (int) $address->id_country;
}
return (int) Configuration::get('PS_COUNTRY_DEFAULT');
}
/**
* Set cookie currency from POST or default currency.
*
* @return Currency|array
*/
public static function setCurrency($cookie)
{
/*
* If we received a request to change a currency and we have a valid currency ID, we will update it
* in the cookie. This is mostly done by the currency switcher in the header.
*/
if (Tools::isSubmit('SubmitCurrency') && ($id_currency = Tools::getValue('id_currency'))) {
/** @var Currency $currency */
$currency = Currency::getCurrencyInstance((int) $id_currency);
if (is_object($currency) && $currency->id && !$currency->deleted && $currency->isAssociatedToShop()) {
$cookie->id_currency = (int) $currency->id;
}
}
// First, let's try to load the currency we have in the cookie.
$currency = null;
if ((int) $cookie->id_currency) {
$currency = Currency::getCurrencyInstance((int) $cookie->id_currency);
}
// If it's not valid anymore, we will use a default currency set in our store.
if (!Validate::isLoadedObject($currency) || (bool) $currency->deleted || !(bool) $currency->active) {
$currency = Currency::getCurrencyInstance(Currency::getDefaultCurrencyId());
}
$cookie->id_currency = (int) $currency->id;
if ($currency->isAssociatedToShop()) {
return $currency;
} else {
// Get currency from context
$currency = Shop::getEntityIds('currency', Context::getContext()->shop->id, true, true);
if (isset($currency[0]) && $currency[0]['id_currency']) {
$cookie->id_currency = $currency[0]['id_currency'];
return Currency::getCurrencyInstance((int) $cookie->id_currency);
}
}
return $currency;
}
/**
* Return price with currency sign for a given product.
*
* @deprecated Since 1.7.6.0. Please use Locale::formatPrice() instead
* @see PrestaShop\PrestaShop\Core\Localization\Locale
*
* @param float $price Product price
* @param int|Currency|array|null $currency Current currency (object, id_currency, NULL => context currency)
* @param bool $no_utf8 Not used anymore
* @param Context|null $context
*
* @return string Price correctly formatted (sign, decimal separator...)
* if you modify this function, don't forget to modify the Javascript function formatCurrency (in tools.js)
*
* @throws LocalizationException
*/
public static function displayPrice($price, $currency = null, $no_utf8 = false, Context $context = null)
{
@trigger_error(
'Tools::displayPrice() is deprecated since version 1.7.6.0. Use ' . Locale::class . '::formatPrice() instead.',
E_USER_DEPRECATED
);
if (!is_numeric($price)) {
return $price;
}
$context = $context ?: Context::getContext();
$currency = $currency ?: $context->currency;
if (is_int($currency)) {
$currency = Currency::getCurrencyInstance($currency);
}
$locale = static::getContextLocale($context);
$currencyCode = is_array($currency) ? $currency['iso_code'] : $currency->iso_code;
return $locale->formatPrice($price, $currencyCode);
}
/**
* Return current locale
*
* @param Context $context
*
* @return Locale
*
* @throws Exception
*/
public static function getContextLocale(Context $context)
{
$locale = $context->getCurrentLocale();
if (null !== $locale) {
return $locale;
}
$containerFinder = new ContainerFinder($context);
$container = $containerFinder->getContainer();
if (null === $context->container) {
$context->container = $container;
}
/** @var LocaleRepository $localeRepository */
$localeRepository = $container->get(self::SERVICE_LOCALE_REPOSITORY);
$locale = $localeRepository->getLocale(
$context->language->getLocale()
);
return $locale;
}
/**
* Returns a well formatted number.
*
* @deprecated Since 1.7.6.0. Please use Locale::formatNumber() instead
* @see Locale
*
* @param int|float|string $number The number to format
* @param null $currency not used anymore
*
* @return string The formatted number
*
* @throws Exception
* @throws LocalizationException
*/
public static function displayNumber($number, $currency = null)
{
@trigger_error(
'Tools::displayNumber() is deprecated since version 1.7.5.0. Use ' . Locale::class . ' instead.',
E_USER_DEPRECATED
);
$context = Context::getContext();
$locale = static::getContextLocale($context);
return $locale->formatNumber($number);
}
public static function displayPriceSmarty($params, &$smarty)
{
$context = Context::getContext();
$locale = static::getContextLocale($context);
if (array_key_exists('currency', $params)) {
$currency = Currency::getCurrencyInstance((int) $params['currency']);
if (Validate::isLoadedObject($currency)) {
return $locale->formatPrice($params['price'], $currency->iso_code);
}
}
return $locale->formatPrice($params['price'], $context->currency->iso_code);
}
/**
* Return price converted.
*
* @param float|null $price Product price
* @param array|Currency|int|null $currency Current currency object
* @param bool $to_currency convert to currency or from currency to default currency
* @param Context|null $context
*
* @return float|null Price
*/
public static function convertPrice($price, $currency = null, $to_currency = true, Context $context = null)
{
$default_currency = Currency::getDefaultCurrencyId();
if (!$context) {
$context = Context::getContext();
}
if ($currency === null) {
$currency = $context->currency;
} elseif (is_numeric($currency)) {
$currency = Currency::getCurrencyInstance($currency);
}
$c_id = (is_array($currency) ? $currency['id_currency'] : $currency->id);
$c_rate = (is_array($currency) ? $currency['conversion_rate'] : $currency->conversion_rate);
if ($c_id != $default_currency) {
if ($to_currency) {
$price *= $c_rate;
} else {
$price /= $c_rate;
}
}
return $price;
}
/**
* Convert amount from a currency to an other currency automatically.
*
* @param float $amount
* @param Currency $currency_from if null we used the default currency
* @param Currency $currency_to if null we used the default currency
*/
public static function convertPriceFull($amount, Currency $currency_from = null, Currency $currency_to = null)
{
if ($currency_from == $currency_to) {
return $amount;
}
if ($currency_from === null) {
$currency_from = Currency::getDefaultCurrency();
}
if ($currency_to === null) {
$currency_to = Currency::getDefaultCurrency();
}
if ($currency_from->id == Currency::getDefaultCurrencyId()) {
$amount *= $currency_to->conversion_rate;
} else {
$conversion_rate = ($currency_from->conversion_rate == 0 ? 1 : $currency_from->conversion_rate);
// Convert amount to default currency (using the old currency rate)
$amount = $amount / $conversion_rate;
// Convert to new currency
$amount *= $currency_to->conversion_rate;
}
return Tools::ps_round($amount, Context::getContext()->getComputingPrecision());
}
/**
* Display date regarding to language preferences.
*
* @param array $params Date, format...
* @param object $smarty Smarty object for language preferences
*
* @return string Date
*/
public static function dateFormat($params, &$smarty)
{
return Tools::displayDate($params['date'], (isset($params['full']) ? $params['full'] : false));
}
/**
* Display date regarding to language preferences.
*
* @param string $date Date to display format UNIX
* @param bool $full With time or not (optional)
*
* @return string Date
*/
public static function displayDate($date, $full = false)
{
if (!$date || !($time = strtotime($date))) {
return $date;
}
if ($date == '0000-00-00 00:00:00' || $date == '0000-00-00') {
return '';
}
if (!Validate::isDate($date) || !Validate::isBool($full)) {
throw new PrestaShopException('Invalid date');
}
$context = Context::getContext();
$date_format = ($full ? $context->language->date_format_full : $context->language->date_format_lite);
return date($date_format, $time);
}
/**
* Get localized date format.
*
* @return string Date format
*/
public static function getDateFormat()
{
$format = Context::getContext()->language->date_format_lite;
$search = ['d', 'm', 'Y'];
$replace = [
Context::getContext()->getTranslator()->trans('DD', [], 'Shop.Forms.Help'),
Context::getContext()->getTranslator()->trans('MM', [], 'Shop.Forms.Help'),
Context::getContext()->getTranslator()->trans('YYYY', [], 'Shop.Forms.Help'),
];
$format = str_replace($search, $replace, $format);
return $format;
}
/**
* Get formatted date.
*
* @param string $date_str Date string
* @param bool $full With time or not (optional)
*
* @return string Formatted date
*/
public static function formatDateStr($date_str, $full = false)
{
$time = strtotime($date_str);
$context = Context::getContext();
$date_format = ($full ? $context->language->date_format_full : $context->language->date_format_lite);
$date = date($date_format, $time);
return $date;
}
/**
* Sanitize a string.
*
* @param string $string String to sanitize
* @param bool $html String contains HTML or not (optional)
*
* @return string Sanitized string
*/
public static function safeOutput($string, $html = false)
{
if (!$html) {
$string = strip_tags($string);
}
return @Tools::htmlentitiesUTF8($string, ENT_QUOTES);
}
public static function htmlentitiesUTF8($string, $type = ENT_QUOTES)
{
if (is_array($string)) {
return array_map(['Tools', 'htmlentitiesUTF8'], $string);
}
return htmlentities((string) $string, $type, 'utf-8');
}
public static function htmlentitiesDecodeUTF8($string)
{
if (is_array($string)) {
$string = array_map(['Tools', 'htmlentitiesDecodeUTF8'], $string);
return (string) array_shift($string);
}
return html_entity_decode((string) $string, ENT_QUOTES, 'utf-8');
}
/**
* @deprecated Since 8.0.0
*/
public static function safePostVars()
{
@trigger_error(
'Tools::safePostVars() is deprecated since version 8.0.0.',
E_USER_DEPRECATED
);
if (!is_array($_POST)) {
$_POST = [];
} else {
$_POST = array_map(['Tools', 'htmlentitiesUTF8'], $_POST);
}
}
/**
* Delete directory and subdirectories.
*
* @param string $dirname Directory name
*/
public static function deleteDirectory($dirname, $delete_self = true)
{
$dirname = rtrim($dirname, '/') . '/';
if (file_exists($dirname)) {
if ($files = scandir($dirname, SCANDIR_SORT_NONE)) {
foreach ($files as $file) {
if ($file != '.' && $file != '..' && $file != '.svn') {
if (is_dir($dirname . $file)) {
Tools::deleteDirectory($dirname . $file);
} elseif (file_exists($dirname . $file)) {
unlink($dirname . $file);
}
}
}
if ($delete_self && file_exists($dirname)) {
if (!rmdir($dirname)) {
return false;
}
}
return true;
}
}
return false;
}
/**
* Delete file.
*
* @param string $file File path
* @param array $exclude_files Excluded files
*
* @return bool
*/
public static function deleteFile($file, $exclude_files = [])
{
if (!is_array($exclude_files)) {
$exclude_files = [$exclude_files];
}
if (file_exists($file) && is_file($file) && array_search(basename($file), $exclude_files) === false) {
return unlink($file);
}
return false;
}
/**
* Clear XML cache folder.
*/
public static function clearXMLCache()
{
foreach (scandir(_PS_ROOT_DIR_ . '/config/xml', SCANDIR_SORT_NONE) as $file) {
$path_info = pathinfo($file, PATHINFO_EXTENSION);
if (($path_info == 'xml') && ($file != 'default.xml')) {
self::deleteFile(_PS_ROOT_DIR_ . '/config/xml/' . $file);
}
}
}
/**
* Depending on _PS_MODE_DEV_ throws an exception or returns a error message.
*
* @param string|null $errorMessage Error message (defaults to "Fatal error")
* @param bool $htmlentities DEPRECATED since 1.7.4.0
* @param Context|null $context DEPRECATED since 1.7.4.0
*
* @return string
*
* @throws PrestaShopException If _PS_MODE_DEV_ is enabled
*/
public static function displayError($errorMessage = null, $htmlentities = null, Context $context = null)
{
header('HTTP/1.1 500 Internal Server Error', true, 500);
if (null !== $htmlentities) {
self::displayParameterAsDeprecated('htmlentities');
}
if (null !== $context) {
self::displayParameterAsDeprecated('context');
}
if (null === $errorMessage) {
$errorMessage = Context::getContext()
->getTranslator()
->trans('Fatal error', [], 'Admin.Notifications.Error');
}
if (_PS_MODE_DEV_) {
throw new PrestaShopException($errorMessage);
}
/* @phpstan-ignore-next-line */
return $errorMessage;
}
/**
* Display an error with detailed object.
*
* @param mixed $object
* @param bool $kill
*
* @return mixed
*/
public static function dieObject($object, $kill = true)
{
dump($object);
if ($kill) {
die('END');
}
return $object;
}
public static function debug_backtrace($start = 0, $limit = null)
{
$backtrace = debug_backtrace();
array_shift($backtrace);
for ($i = 0; $i < $start; ++$i) {
array_shift($backtrace);
}
echo '
<div style="margin:10px;padding:10px;border:1px solid #666666">
<ul>';
$i = 0;
foreach ($backtrace as $id => $trace) {
if ((int) $limit && (++$i > $limit)) {
break;
}
$relative_file = (isset($trace['file'])) ? 'in /' . ltrim(str_replace([_PS_ROOT_DIR_, '\\'], ['', '/'], $trace['file']), '/') : '';
$current_line = (isset($trace['line'])) ? ':' . $trace['line'] : '';
echo '<li>
<b>' . ((isset($trace['class'])) ? $trace['class'] : '') . ((isset($trace['type'])) ? $trace['type'] : '') . $trace['function'] . '</b>
' . $relative_file . $current_line . '
</li>';
}
echo '</ul>
</div>';
}
/**
* Prints object information into error log.
*
* @see error_log()
*
* @param mixed $object
* @param int|null $message_type
* @param string|null $destination
* @param string|null $extra_headers
*
* @return bool
*/
public static function error_log($object, $message_type = null, $destination = null, $extra_headers = null)
{
return error_log(print_r($object, true), $message_type, $destination, $extra_headers);
}
/**
* Check if submit has been posted.
*
* @param string $submit submit name
*/
public static function isSubmit($submit)
{
return
isset($_POST[$submit]) || isset($_POST[$submit . '_x']) || isset($_POST[$submit . '_y'])
|| isset($_GET[$submit]) || isset($_GET[$submit . '_x']) || isset($_GET[$submit . '_y']);
}
/**
* Hash password.
*
* @param string $passwd String to hash
*
* @return string Hashed password
*
* @deprecated 1.7.0
*/
public static function encrypt($passwd)
{
return self::hash($passwd);
}
/**
* Hash password.
*
* @param string $passwd String to has
*
* @return string Hashed password
*
* @since 1.7.0
*/
public static function hash($passwd)
{
return (new Hashing())->hash($passwd, _COOKIE_KEY_);
}
/**
* Hash data string.
*
* @param string $data String to encrypt
*
* @return string Hashed IV
*
* @deprecated 1.7.0
*/
public static function encryptIV($data)
{
return self::hashIV($data);
}
/**
* Hash data string.
*
* @param string $data String to encrypt
*
* @return string Hashed IV
*
* @since 1.7.0
*/
public static function hashIV($data)
{
return (new Hashing())->hash($data, _COOKIE_IV_);
}
/**
* Get token to prevent CSRF.
*
* @param bool $page
* @param Context|null $context
*
* @return string
*/
public static function getToken($page = true, Context $context = null)
{
if (!$context) {
$context = Context::getContext();
}
if ($page === true) {
return Tools::hash($context->customer->id . $context->customer->passwd . $_SERVER['SCRIPT_NAME']);
} else {
return Tools::hash($context->customer->id . $context->customer->passwd . $page);
}
}
/**
* Tokenize a string.
*
* @param string $string String to encrypt
*
* @return string|bool false if given string is empty
*/
public static function getAdminToken($string)
{
return !empty($string) ? Tools::hash($string) : false;
}
/**
* @param string $tab
* @param Context $context
*
* @return bool|string
*/
public static function getAdminTokenLite($tab, Context $context = null)
{
if (!$context) {
$context = Context::getContext();
}
return Tools::getAdminToken($tab . (int) Tab::getIdFromClassName($tab) . (int) $context->employee->id);
}
/**
* @param array $params
* @param Smarty|null $smarty unused parameter, please ignore (@todo: remove in next major)
*
* @return bool|string
*/
public static function getAdminTokenLiteSmarty($params, &$smarty = null)
{
$context = Context::getContext();
return Tools::getAdminToken($params['tab'] . (int) Tab::getIdFromClassName($params['tab']) . (int) $context->employee->id);
}
/**
* Get a valid URL to use from BackOffice.
*
* @param string $url An URL to use in BackOffice
* @param bool $entities Set to true to use htmlentities function on URL param
*
* @return string
*/
public static function getAdminUrl($url = null, $entities = false)
{
$link = Tools::getHttpHost(true) . __PS_BASE_URI__;
if (isset($url)) {
$link .= ($entities ? Tools::htmlentitiesUTF8($url) : $url);
}
return $link;
}
/**
* Get a valid image URL to use from BackOffice.
*
* @param string $image Image name
* @param bool $entities Set to true to use htmlentities function on image param
*
* @return string
*/
public static function getAdminImageUrl($image = null, $entities = false)
{
return Tools::getAdminUrl(basename(_PS_IMG_DIR_) . '/' . $image, $entities);
}
/**
* Return the friendly url from the provided string.
*
* @deprecated since 8.1
*
* @param string $str
* @param bool $utf8_decode (deprecated)
*
* @return string
*/
public static function link_rewrite($str, $utf8_decode = null)
{
@trigger_error(
'This function is deprecated, use Tools::str2url($str) instead.',
E_USER_DEPRECATED
);
if ($utf8_decode !== null) {
Tools::displayParameterAsDeprecated('utf8_decode');
}
return Tools::str2url($str);
}
/**
* Return a friendly url made from the provided string
* If the mbstring library is available, the output is the same as the js function of the same name.
*
* @param string $str
*
* @return string|bool
*/
public static function str2url($str)
{
static $allow_accented_chars = null;
if ($allow_accented_chars === null) {
$allow_accented_chars = Configuration::get('PS_ALLOW_ACCENTED_CHARS_URL');
}
return (self::getStringModifier())->str2url((string) $str, $allow_accented_chars);
}
/**
* Replace all accented chars by their equivalent non accented chars.
*
* @param string $str
*
* @return string
*/
public static function replaceAccentedChars($str)
{
return (self::getStringModifier())->replaceAccentedChars($str);
}
/**
* Reuse the StringModifier for performance reasons.
*
* @return StringModifier
*/
private static function getStringModifier()
{
if (!isset(self::$_string_modifier)) {
self::$_string_modifier = new StringModifier();
}
return self::$_string_modifier;
}
/**
* Truncate strings.
*
* @param string $str
* @param int $max_length Max length
* @param string $suffix Suffix optional
*
* @return string $str truncated
*/
/* CAUTION : Use it only on module hookEvents.
** For other purposes use the smarty function instead */
public static function truncate($str, $max_length, $suffix = '...')
{
if (Tools::strlen($str) <= $max_length) {
return $str;
}
$str = utf8_decode($str);
return utf8_encode(substr($str, 0, $max_length - Tools::strlen($suffix)) . $suffix);
}
/*Copied from CakePHP String utility file*/
public static function truncateString($text, $length = 120, $options = [])
{
$default = [
'ellipsis' => '...', 'exact' => true, 'html' => true,
];
$options = array_merge($default, $options);
extract($options);
if (isset($html)) {
/* @var bool $exact */
/* @var bool $html */
if (Tools::strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
return $text;
}
$total_length = Tools::strlen(strip_tags($ellipsis ?? ''));
$open_tags = [];
$truncate = '';
preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
foreach ($tags as $tag) {
if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
array_unshift($open_tags, $tag[2]);
} elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $close_tag)) {
$pos = array_search($close_tag[1], $open_tags);
if ($pos !== false) {
array_splice($open_tags, $pos, 1);
}
}
}
$truncate .= $tag[1];
$content_length = Tools::strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3]));
if ($content_length + $total_length > $length) {
$left = $length - $total_length;
$entities_length = 0;
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)) {
foreach ($entities[0] as $entity) {
if ($entity[1] + 1 - $entities_length <= $left) {
--$left;
$entities_length += Tools::strlen($entity[0]);
} else {
break;
}
}
}
$truncate .= Tools::substr($tag[3], 0, $left + $entities_length);
break;
} else {
$truncate .= $tag[3];
$total_length += $content_length;
}
if ($total_length >= $length) {
break;
}
}
} else {
if (Tools::strlen($text) <= $length) {
return $text;
}
$truncate = Tools::substr($text, 0, $length - Tools::strlen($ellipsis ?? ''));
}
if (!isset($exact) || !$exact) {
$spacepos = Tools::strrpos($truncate ?? '', ' ');
if (isset($html)) {
$truncate_check = Tools::substr($truncate ?? '', 0, $spacepos);
$last_open_tag = Tools::strrpos($truncate_check, '<');
$last_close_tag = Tools::strrpos($truncate_check, '>');
if ($last_open_tag > $last_close_tag) {
preg_match_all('/<[\w]+[^>]*>/s', $truncate ?? '', $last_tag_matches);
$last_tag = array_pop($last_tag_matches[0]);
$spacepos = Tools::strrpos($truncate, $last_tag) + Tools::strlen($last_tag);
}
$bits = Tools::substr($truncate ?? '', $spacepos);
preg_match_all('/<\/([a-z]+)>/', $bits, $dropped_tags, PREG_SET_ORDER);
if (!empty($dropped_tags)) {
if (!empty($open_tags)) {
foreach ($dropped_tags as $closing_tag) {
if (!in_array($closing_tag[1], $open_tags)) {
array_unshift($open_tags, $closing_tag[1]);
}
}
} else {
foreach ($dropped_tags as $closing_tag) {
$open_tags[] = $closing_tag[1];
}
}
}
}
$truncate = Tools::substr($truncate, 0, $spacepos);
}
$truncate .= ($ellipsis ?? '');
if (isset($html)) {
$open_tags = $open_tags ?? [];
foreach ($open_tags as $tag) {
$truncate .= '</' . $tag . '>';
}
}
return $truncate;
}
public static function normalizeDirectory($directory)
{
return rtrim($directory, '/\\') . DIRECTORY_SEPARATOR;
}
/**
* Generate date form.
*
* @return array $tab html data with 3 cells :['days'], ['months'], ['years']
*/
public static function dateYears()
{
$tab = [];
for ($i = date('Y'); $i >= 1900; --$i) {
$tab[] = $i;
}
return $tab;
}
public static function dateDays()
{
$tab = [];
for ($i = 1; $i != 32; ++$i) {
$tab[] = $i;
}
return $tab;
}
public static function dateMonths()
{
$tab = [];
for ($i = 1; $i != 13; ++$i) {
$tab[$i] = date('F', mktime(0, 0, 0, $i, (int) date('m'), (int) date('Y')));
}
return $tab;
}
public static function hourGenerate($hours, $minutes, $seconds)
{
return implode(':', [$hours, $minutes, $seconds]);
}
public static function dateFrom($date)
{
$tab = explode(' ', $date);
if (!isset($tab[1])) {
$date .= ' ' . Tools::hourGenerate(0, 0, 0);
}
return $date;
}
public static function dateTo($date)
{
$tab = explode(' ', $date);
if (!isset($tab[1])) {
$date .= ' ' . Tools::hourGenerate(23, 59, 59);
}
return $date;
}
public static function strtolower($str)
{
if (null === $str || is_array($str)) {
return false;
}
return mb_strtolower($str, 'UTF-8');
}
public static function strlen($str, $encoding = 'UTF-8')
{
if (null === $str || is_array($str)) {
return false;
}
$str = html_entity_decode($str, ENT_COMPAT, 'UTF-8');
return mb_strlen($str, $encoding);
}
/**
* @deprecated Since 8.0.0
*/
public static function stripslashes($string)
{
@trigger_error(
'Tools::stripslashes() is deprecated since version 8.0.0. Use PHP\'s stripslashes instead.',
E_USER_DEPRECATED
);
return $string;
}
public static function strtoupper($str)
{
if (is_array($str)) {
return false;
}
return mb_strtoupper($str, 'utf-8');
}
public static function substr($str, $start, $length = false, $encoding = 'UTF-8')
{
if (is_array($str)) {
return false;
}
return mb_substr($str, (int) $start, ($length === false ? null : (int) $length), $encoding);
}
public static function strpos($str, $find, $offset = 0, $encoding = 'UTF-8')
{
return mb_strpos($str, $find, $offset, $encoding);
}
public static function strrpos($str, $find, $offset = 0, $encoding = 'UTF-8')
{
return mb_strrpos($str, $find, $offset, $encoding);
}
public static function ucfirst($str)
{
return Tools::strtoupper(Tools::substr($str, 0, 1)) . Tools::substr($str, 1);
}
public static function ucwords($str)
{
return mb_convert_case($str, MB_CASE_TITLE);
}
public static function orderbyPrice(&$array, $order_way)
{
foreach ($array as &$row) {
$row['price_tmp'] = (float) Product::getPriceStatic($row['id_product'], true, ((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int) $row['id_product_attribute'] : null), 2);
}
unset($row);
if (Tools::strtolower($order_way) == 'desc') {
uasort($array, 'cmpPriceDesc');
} else {
uasort($array, 'cmpPriceAsc');
}
foreach ($array as &$row) {
unset($row['price_tmp']);
}
}
public static function iconv($from, $to, $string)
{
if (function_exists('iconv')) {
return iconv($from, $to . '//TRANSLIT', str_replace('¥', '¥', str_replace('£', '£', str_replace('€', '€', $string))));
}
return html_entity_decode(htmlentities($string, ENT_NOQUOTES, $from), ENT_NOQUOTES, $to);
}
public static function isEmpty($field)
{
return $field === '' || $field === null;
}
/**
* returns the rounded value of $value to specified precision, according to your configuration;.
*
* @note : PHP 5.3.0 introduce a 3rd parameter mode in round function
*
* @param float $value
* @param int $precision
*
* @return float
*/
public static function ps_round($value, $precision = 0, $round_mode = null)
{
if ($round_mode === null) {
if (Tools::$round_mode == null) {
Tools::$round_mode = (int) Configuration::get('PS_PRICE_ROUND_MODE');
}
$round_mode = Tools::$round_mode;
}
switch ($round_mode) {
case PS_ROUND_UP:
return Tools::ceilf($value, $precision);
case PS_ROUND_DOWN:
return Tools::floorf($value, $precision);
case PS_ROUND_HALF_DOWN:
case PS_ROUND_HALF_EVEN:
case PS_ROUND_HALF_ODD:
return Tools::math_round($value, $precision, $round_mode);
case PS_ROUND_HALF_UP:
default:
return Tools::math_round($value, $precision, PS_ROUND_HALF_UP);
}
}
/**
* @param int|float $value
* @param int|float $places
* @param int<2,5> $mode (PS_ROUND_HALF_UP|PS_ROUND_HALF_DOWN|PS_ROUND_HALF_EVEN|PS_ROUND_HALF_ODD)
*
* @return false|float
*/
public static function math_round($value, $places, $mode = PS_ROUND_HALF_UP)
{
//If PHP_ROUND_HALF_UP exist (PHP 5.3) use it and pass correct mode value (PrestaShop define - 1)
if (defined('PHP_ROUND_HALF_UP')) {
return round($value, $places, $mode - 1);
}
$precision_places = 14 - floor(log10(abs($value)));
$f1 = 10.0 ** (float) abs($places);
/* If the decimal precision guaranteed by FP arithmetic is higher than
* the requested places BUT is small enough to make sure a non-zero value
* is returned, pre-round the result to the precision */
if ($precision_places > $places && $precision_places - $places < 15) {
$f2 = 10.0 ** (float) abs($precision_places);
if ($precision_places >= 0) {
$tmp_value = $value * $f2;
} else {
$tmp_value = $value / $f2;
}
/* preround the result (tmp_value will always be something * 1e14,
* thus never larger than 1e15 here) */
$tmp_value = Tools::round_helper($tmp_value, $mode);
/* now correctly move the decimal point */
$f2 = 10.0 ** (float) abs($places - $precision_places);
/* because places < precision_places */
$tmp_value = $tmp_value / $f2;
} else {
/* adjust the value */
if ($places >= 0) {
$tmp_value = $value * $f1;
} else {
$tmp_value = $value / $f1;
}
/* This value is beyond our precision, so rounding it is pointless */
if (abs($tmp_value) >= 1e15) {
return $value;
}
}
/* round the temp value */
$tmp_value = Tools::round_helper($tmp_value, $mode);
/* see if it makes sense to use simple division to round the value */
if (abs($places) < 23) {
if ($places > 0) {
$tmp_value /= $f1;
} else {
$tmp_value *= $f1;
}
}
return $tmp_value;
}
/**
* @param float $value
* @param int $mode
*
* @return float
*/
public static function round_helper($value, $mode)
{
if ($value >= 0.0) {
$tmp_value = floor($value + 0.5);
if (
($mode == PS_ROUND_HALF_DOWN && $value == (-0.5 + $tmp_value)) ||
($mode == PS_ROUND_HALF_EVEN && $value == (0.5 + 2 * floor($tmp_value / 2.0))) ||
($mode == PS_ROUND_HALF_ODD && $value == (0.5 + 2 * floor($tmp_value / 2.0) - 1.0))
) {
$tmp_value = $tmp_value - 1.0;
}
} else {
$tmp_value = ceil($value - 0.5);
if (
($mode == PS_ROUND_HALF_DOWN && $value == (0.5 + $tmp_value)) ||
($mode == PS_ROUND_HALF_EVEN && $value == (-0.5 + 2 * ceil($tmp_value / 2.0))) ||
($mode == PS_ROUND_HALF_ODD && $value == (-0.5 + 2 * ceil($tmp_value / 2.0) + 1.0))
) {
$tmp_value = $tmp_value + 1.0;
}
}
return $tmp_value;
}
/**
* Returns the rounded value up of $value to specified precision.
*
* @param float $value
* @param int $precision
*
* @return float
*/
public static function ceilf($value, $precision = 0)
{
$precision_factor = $precision == 0 ? 1 : 10 ** $precision;
$tmp = $value * $precision_factor;
$tmp2 = (string) $tmp;
// If the current value has already the desired precision
if (strpos($tmp2, '.') === false) {
return $value;
}
if ($tmp2[strlen($tmp2) - 1] == 0) {
return $value;
}
return ceil($tmp) / $precision_factor;
}
/**
* Returns the rounded value down of $value to specified precision.
*
* @param float $value
* @param int $precision
*
* @return float
*/
public static function floorf($value, $precision = 0)
{
$precision_factor = $precision == 0 ? 1 : 10 ** $precision;
$tmp = $value * $precision_factor;
$tmp2 = (string) $tmp;
// If the current value has already the desired precision
if (strpos($tmp2, '.') === false) {
return $value;
}
if ($tmp2[strlen($tmp2) - 1] == 0) {
return $value;
}
return floor($tmp) / $precision_factor;
}
/**
* file_exists() wrapper with cache to speedup performance.
*
* @param string $filename File name
*
* @return bool Cached result of file_exists($filename)
*/
public static function file_exists_cache($filename)
{
if (!isset(self::$file_exists_cache[$filename])) {
self::$file_exists_cache[$filename] = file_exists($filename);
}
return self::$file_exists_cache[$filename];
}
/**
* file_exists() wrapper with a call to clearstatcache prior.
*
* @param string $filename File name
*
* @return bool Cached result of file_exists($filename)
*/
public static function file_exists_no_cache($filename)
{
clearstatcache();
return file_exists($filename);
}
/**
* Refresh local CACert file.
*/
public static function refreshCACertFile()
{
if ((time() - @filemtime(_PS_CACHE_CA_CERT_FILE_) > 1296000)) {
$stream_context = @stream_context_create(
[
'http' => ['timeout' => 3],
'ssl' => [
'cafile' => CaBundle::getBundledCaBundlePath(),
],
]
);
$ca_cert_content = @file_get_contents(Tools::CACERT_LOCATION, false, $stream_context);
if (empty($ca_cert_content)) {
$ca_cert_content = @file_get_contents(CaBundle::getBundledCaBundlePath());
}
if (
preg_match('/(.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----){50}$/Uims', $ca_cert_content) &&
substr(rtrim($ca_cert_content), -1) == '-'
) {
file_put_contents(_PS_CACHE_CA_CERT_FILE_, $ca_cert_content);
}
}
}
/**
* @param string $url
* @param int $curl_timeout
* @param array $opts
*
* @return bool|string
*
* @throws Exception
*/
private static function file_get_contents_curl(
$url,
$curl_timeout,
$opts
) {
$content = false;
if (function_exists('curl_init')) {
Tools::refreshCACertFile();
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($curl, CURLOPT_TIMEOUT, $curl_timeout);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_CAINFO, _PS_CACHE_CA_CERT_FILE_);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_MAXREDIRS, 5);
if ($opts != null) {
if (isset($opts['http']['method']) && Tools::strtolower($opts['http']['method']) == 'post') {
curl_setopt($curl, CURLOPT_POST, true);
if (isset($opts['http']['content'])) {
parse_str($opts['http']['content'], $post_data);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
}
}
}
$content = curl_exec($curl);
if (false === $content && _PS_MODE_DEV_) {
$errorMessage = sprintf(
'file_get_contents_curl failed to download %s : (error code %d) %s',
$url,
curl_errno($curl),
curl_error($curl)
);
throw new \Exception($errorMessage);
}
curl_close($curl);
}
return $content;
}
private static function file_get_contents_fopen(
$url,
$use_include_path,
$stream_context
) {
$content = false;
if (in_array(ini_get('allow_url_fopen'), ['On', 'on', '1'])) {
$content = @file_get_contents($url, $use_include_path, $stream_context);
}
return $content;
}
/**
* This method allows to get the content from either a URL or a local file.
*
* @param string $url the url to get the content from
* @param bool $use_include_path second parameter of http://php.net/manual/en/function.file-get-contents.php
* @param resource $stream_context third parameter of http://php.net/manual/en/function.file-get-contents.php
* @param int $curl_timeout
* @param bool $fallback whether or not to use the fallback if the main solution fails
*
* @return bool|string false or the string content
*/
public static function file_get_contents(
$url,
$use_include_path = false,
$stream_context = null,
$curl_timeout = 5,
$fallback = false
) {
$is_local_file = !preg_match('/^https?:\/\//', $url);
$require_fopen = false;
$opts = null;
if ($stream_context) {
$opts = stream_context_get_options($stream_context);
if (isset($opts['http'])) {
$require_fopen = true;
$opts_layer = array_diff_key($opts, ['http' => null]);
$http_layer = array_diff_key($opts['http'], ['method' => null, 'content' => null]);
if (empty($opts_layer) && empty($http_layer)) {
$require_fopen = false;
}
}
} elseif (!$is_local_file) {
$stream_context = @stream_context_create(
[
'http' => ['timeout' => $curl_timeout],
'ssl' => [
'verify_peer' => true,
'cafile' => CaBundle::getBundledCaBundlePath(),
],
]
);
}
if ($is_local_file) {
$content = @file_get_contents($url, $use_include_path, $stream_context);
} else {
if ($require_fopen) {
$content = Tools::file_get_contents_fopen($url, $use_include_path, $stream_context);
} else {
$content = Tools::file_get_contents_curl($url, $curl_timeout, $opts);
if (empty($content) && $fallback) {
$content = Tools::file_get_contents_fopen($url, $use_include_path, $stream_context);
}
}
}
return $content;
}
/**
* Create a local file from url
* required because ZipArchive is unable to extract from remote files.
*
* @param string $url the remote location
*
* @return bool|string false if failure, else the local filename
*/
public static function createFileFromUrl($url)
{
$remoteFile = fopen($url, 'rb');
if (!$remoteFile) {
return false;
}
$localFile = fopen(basename($url), 'wb');
if (!$localFile) {
return false;
}
while (!feof($remoteFile)) {
$data = fread($remoteFile, 1024);
fwrite($localFile, $data, 1024);
}
fclose($remoteFile);
fclose($localFile);
return basename($url);
}
public static function simplexml_load_file($url, $class_name = null)
{
$cache_id = 'Tools::simplexml_load_file' . $url;
if (!Cache::isStored($cache_id)) {
$result = @simplexml_load_string(Tools::file_get_contents($url), $class_name);
Cache::store($cache_id, $result);
return $result;
}
return Cache::retrieve($cache_id);
}
public static function copy($source, $destination, $stream_context = null)
{
if (null === $stream_context && !preg_match('/^https?:\/\//', $source)) {
return @copy($source, $destination);
}
return @file_put_contents($destination, Tools::file_get_contents($source, false, $stream_context));
}
/**
* Translates a string with underscores into camel case (e.g. first_name -> firstName).
*
* @prototype string public static function toCamelCase(string $str[, bool $capitalise_first_char = false])
*
* @param string $str Source string to convert in camel case
* @param bool $capitaliseFirstChar Optionnal parameters to transform the first letter in upper case
*
* @return string The string in camel case
*/
public static function toCamelCase($str, $capitaliseFirstChar = false)
{
$str = Tools::strtolower($str);
$str = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $str)));
if (!$capitaliseFirstChar) {
$str = lcfirst($str);
}
return $str;
}
/**
* Transform a CamelCase string to underscore_case string.
*
* 'CMSCategories' => 'cms_categories'
* 'RangePrice' => 'range_price'
*
* @param string $string
*
* @return string
*/
public static function toUnderscoreCase($string)
{
return Tools::strtolower(trim(preg_replace('/([A-Z][a-z])/', '_$1', $string), '_'));
}
/**
* Converts SomethingLikeThis to something-like-this
*
* @param string $string
*
* @return string
*/
public static function camelCaseToKebabCase($string)
{
return Tools::strtolower(
preg_replace('/([a-z])([A-Z])/', '$1-$2', $string)
);
}
/**
* @deprecated since 8.1 use PrestaShop\PrestaShop\Core\Util\ColorBrightnessCalculator::isBright function instead
*
* @param string $hex
*
* @return float|int|string
*/
public static function getBrightness($hex)
{
@trigger_error(
sprintf(
'%s is deprecated since version 8.1.0. Use PrestaShop\PrestaShop\Core\Util\ColorBrightnessCalculator::isBright function instead.',
__METHOD__
),
E_USER_DEPRECATED
);
if (Tools::strtolower($hex) == 'transparent') {
return '129';
}
$hex = str_replace('#', '', $hex);
if (Tools::strlen($hex) == 3) {
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
}
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
return (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
}
/**
* @deprecated since 8.1 use PrestaShop\PrestaShop\Core\Util\ColorBrightnessCalculator::isBright function instead
*/
public static function isBright($hex)
{
@trigger_error(
sprintf(
'%s is deprecated since version 8.1.0. Use PrestaShop\PrestaShop\Core\Util\ColorBrightnessCalculator::isBright function instead.',
__METHOD__
),
E_USER_DEPRECATED
);
if (null === self::$colorBrightnessCalculator) {
self::$colorBrightnessCalculator = new ColorBrightnessCalculator();
}
return self::$colorBrightnessCalculator->isBright($hex);
}
public static function parserSQL($sql)
{
if (strlen($sql) > 0) {
$parser = new PHPSQLParser($sql);
return $parser->parsed;
}
return false;
}
public static function replaceByAbsoluteURL($matches)
{
Tools::displayAsDeprecated('Use Media::replaceByAbsoluteURL($matches) instead');
return Media::replaceByAbsoluteURL($matches);
}
protected static $_cache_nb_media_servers = null;
/**
* @return bool
*/
public static function hasMediaServer(): bool
{
if (self::$_cache_nb_media_servers === null && defined('_MEDIA_SERVER_1_') && defined('_MEDIA_SERVER_2_') && defined('_MEDIA_SERVER_3_')) {
if (_MEDIA_SERVER_1_ == '') {
self::$_cache_nb_media_servers = 0;
} elseif (_MEDIA_SERVER_2_ == '') {
self::$_cache_nb_media_servers = 1;
} elseif (_MEDIA_SERVER_3_ == '') {
self::$_cache_nb_media_servers = 2;
} else {
self::$_cache_nb_media_servers = 3;
}
}
return self::$_cache_nb_media_servers > 0;
}
/**
* @param string $filename
*
* @return string
*/
public static function getMediaServer(string $filename): string
{
if (self::hasMediaServer()) {
$id_media_server = abs(crc32($filename)) % self::$_cache_nb_media_servers + 1;
return constant('_MEDIA_SERVER_' . $id_media_server . '_');
}
return Tools::usingSecureMode() ? Tools::getShopDomainSsl() : Tools::getShopDomain();
}
/**
* Get domains information with physical and virtual paths
*
* e.g: [
* prestashop.localhost => [
* physical => "/",
* virtual => "",
* id_shop => "1",
* ]
* ]
*
* @return array
*/
public static function getDomains()
{
$domains = [];
foreach (ShopUrl::getShopUrls() as $shop_url) {
/** @var ShopUrl $shop_url */
if (!isset($domains[$shop_url->domain])) {
$domains[$shop_url->domain] = [];
}
$domains[$shop_url->domain][] = [
'physical' => $shop_url->physical_uri,
'virtual' => $shop_url->virtual_uri,
'id_shop' => $shop_url->id_shop,
];
if ($shop_url->domain == $shop_url->domain_ssl) {
continue;
}
if (!isset($domains[$shop_url->domain_ssl])) {
$domains[$shop_url->domain_ssl] = [];
}
$domains[$shop_url->domain_ssl][] = [
'physical' => $shop_url->physical_uri,
'virtual' => $shop_url->virtual_uri,
'id_shop' => $shop_url->id_shop,
];
}
return $domains;
}
public static function generateHtaccess($path = null, $rewrite_settings = null, $cache_control = null, $specific = '', $disable_multiviews = null, $medias = false, $disable_modsec = null)
{
if (
defined('_PS_IN_TEST_')
|| (defined('PS_INSTALLATION_IN_PROGRESS') && $rewrite_settings === null)
) {
return true;
}
// Default values for parameters
if (null === $path) {
$path = _PS_ROOT_DIR_ . '/.htaccess';
}
if (null === $cache_control) {
$cache_control = (int) Configuration::get('PS_HTACCESS_CACHE_CONTROL');
}
if (null === $disable_multiviews) {
$disable_multiviews = (bool) Configuration::get('PS_HTACCESS_DISABLE_MULTIVIEWS');
}
if ($disable_modsec === null) {
$disable_modsec = (int) Configuration::get('PS_HTACCESS_DISABLE_MODSEC');
}
// Check current content of .htaccess and save all code outside of prestashop comments
$specific_before = $specific_after = '';
if (file_exists($path)) {
$content = file_get_contents($path);
if (preg_match('#^(.*)\# ~~start~~.*\# ~~end~~[^\n]*(.*)$#s', $content, $m)) {
$specific_before = $m[1];
$specific_after = $m[2];
} else {
// For retrocompatibility
if (preg_match('#\# http://www\.prestashop\.com - http://www\.prestashop\.com/forums\s*(.*)<IfModule mod_rewrite\.c>#si', $content, $m)) {
$specific_before = $m[1];
} else {
$specific_before = $content;
}
}
}
// Write .htaccess data
if (!$write_fd = @fopen($path, 'wb')) {
return false;
}
if ($specific_before) {
fwrite($write_fd, trim($specific_before) . "\n\n");
}
$domains = self::getDomains();
// Write data in .htaccess file
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");
fwrite($write_fd, "# .htaccess automaticaly generated by PrestaShop e-commerce open-source solution\n");
fwrite($write_fd, "# https://www.prestashop.com - https://www.prestashop.com/forums\n\n");
if ($disable_modsec) {
fwrite($write_fd, "<IfModule mod_security.c>\nSecFilterEngine Off\nSecFilterScanPOST Off\n</IfModule>\n\n");
}
// RewriteEngine
fwrite($write_fd, "<IfModule mod_rewrite.c>\n");
// Ensure HTTP_MOD_REWRITE variable is set in environment
fwrite($write_fd, "<IfModule mod_env.c>\n");
fwrite($write_fd, "SetEnv HTTP_MOD_REWRITE On\n");
fwrite($write_fd, "</IfModule>\n\n");
// Disable multiviews ?
if ($disable_multiviews) {
fwrite($write_fd, "\n# Disable Multiviews\nOptions -Multiviews\n\n");
}
fwrite($write_fd, "RewriteEngine on\n");
if (
!$medias
&& Configuration::getMultiShopValues('PS_MEDIA_SERVER_1')
&& Configuration::getMultiShopValues('PS_MEDIA_SERVER_2')
&& Configuration::getMultiShopValues('PS_MEDIA_SERVER_3')
) {
$medias = [
Configuration::getMultiShopValues('PS_MEDIA_SERVER_1'),
Configuration::getMultiShopValues('PS_MEDIA_SERVER_2'),
Configuration::getMultiShopValues('PS_MEDIA_SERVER_3'),
];
}
$media_domains = '';
foreach ($medias as $media) {
foreach ($media as $media_url) {
if ($media_url) {
$media_domains .= 'RewriteCond %{HTTP_HOST} ^' . $media_url . '$ [OR]' . PHP_EOL;
}
}
}
if (Configuration::get('PS_WEBSERVICE_CGI_HOST')) {
fwrite($write_fd, "RewriteCond %{HTTP:Authorization} ^(.*)\nRewriteRule . - [E=HTTP_AUTHORIZATION:%1]\n\n");
}
foreach ($domains as $domain => $list_uri) {
// As we use regex in the htaccess, ipv6 surrounded by brackets must be escaped
$domain = str_replace(['[', ']'], ['\[', '\]'], $domain);
$domain_rewrite_cond = '';
foreach ($list_uri as $uri) {
fwrite($write_fd, PHP_EOL . PHP_EOL . '#Domain: ' . $domain . PHP_EOL);
if (Shop::isFeatureActive()) {
fwrite($write_fd, 'RewriteCond %{HTTP_HOST} ^' . $domain . '$' . PHP_EOL);
}
fwrite($write_fd, 'RewriteRule . - [E=REWRITEBASE:' . $uri['physical'] . ']' . PHP_EOL);
// Webservice
fwrite($write_fd, 'RewriteRule ^api(?:/(.*))?$ %{ENV:REWRITEBASE}webservice/dispatcher.php?url=$1 [QSA,L]' . PHP_EOL);
// upload folder
fwrite($write_fd, 'RewriteRule ^upload/.+$ %{ENV:REWRITEBASE}index.php [QSA,L]' . "\n\n");
if (!$rewrite_settings) {
$rewrite_settings = (int) Configuration::get('PS_REWRITING_SETTINGS', null, null, (int) $uri['id_shop']);
}
$domain_rewrite_cond = 'RewriteCond %{HTTP_HOST} ^' . $domain . '$' . PHP_EOL;
// Rewrite virtual multishop uri
if ($uri['virtual']) {
if (!$rewrite_settings) {
fwrite($write_fd, $media_domains);
fwrite($write_fd, $domain_rewrite_cond);
fwrite($write_fd, 'RewriteRule ^' . trim($uri['virtual'], '/') . '/?$ ' . $uri['physical'] . $uri['virtual'] . "index.php [L,R]\n");
} else {
fwrite($write_fd, $media_domains);
fwrite($write_fd, $domain_rewrite_cond);
fwrite($write_fd, 'RewriteRule ^' . trim($uri['virtual'], '/') . '$ ' . $uri['physical'] . $uri['virtual'] . " [L,R]\n");
}
fwrite($write_fd, $media_domains);
fwrite($write_fd, $domain_rewrite_cond);
fwrite($write_fd, 'RewriteRule ^' . ltrim($uri['virtual'], '/') . '(.*) ' . $uri['physical'] . "$1 [L]\n\n");
}
if ($rewrite_settings) {
// Compatibility with the old image filesystem
fwrite($write_fd, "# Images\n");
if (Configuration::get('PS_LEGACY_IMAGES')) {
fwrite($write_fd, $media_domains);
fwrite($write_fd, $domain_rewrite_cond);
fwrite($write_fd, 'RewriteRule ^([a-z0-9]+\-[a-z0-9]+\-[-\w]*)/.+(\.(?:jpe?g|webp|png|avif))$ %{ENV:REWRITEBASE}img/p/$1$2.jpg [L]' . PHP_EOL);
fwrite($write_fd, $media_domains);
fwrite($write_fd, $domain_rewrite_cond);
fwrite($write_fd, 'RewriteRule ^([\d]+(?:\-[\d]+){1,2})/.+(\.(?:jpe?g|webp|png|avif))$ %{ENV:REWRITEBASE}img/p/$1$2.jpg [L]' . PHP_EOL);
}
// Rewrite product images < 10 millions
$path_components = [];
for ($i = 1; $i <= 7; ++$i) {
$path_components[] = '$' . ($i + 1); // paths start on 2
$path = implode('/', $path_components);
fwrite($write_fd, $media_domains);
fwrite($write_fd, $domain_rewrite_cond);
fwrite($write_fd, 'RewriteRule ^(' . str_repeat('([\d])', $i) . '(?:\-[\w-]*)?)/.+(\.(?:jpe?g|webp|png|avif))$ %{ENV:REWRITEBASE}img/p/' . $path . '/$1$' . ($i + 2) . " [L]\n");
}
fwrite($write_fd, $media_domains);
fwrite($write_fd, $domain_rewrite_cond);
fwrite($write_fd, 'RewriteRule ^c/([\d]+)(\-[\.*\w-]*)/.+(\.(?:jpe?g|webp|png|avif))$ %{ENV:REWRITEBASE}img/c/$1$2$3 [L]' . PHP_EOL);
fwrite($write_fd, $media_domains);
fwrite($write_fd, $domain_rewrite_cond);
fwrite($write_fd, 'RewriteRule ^c/([a-zA-Z_-]+)(-[\d]+)?/.+(\.(?:jpe?g|webp|png|avif))$ %{ENV:REWRITEBASE}img/c/$1$2$3 [L]' . PHP_EOL);
}
fwrite($write_fd, "# AlphaImageLoader for IE and fancybox\n");
if (Shop::isFeatureActive()) {
fwrite($write_fd, $domain_rewrite_cond);
}
fwrite($write_fd, 'RewriteRule ^images_ie/?([^/]+)\.(jpe?g|png|gif)$ %{ENV:REWRITEBASE}js/jquery/plugins/fancybox/images/$1.$2 [L]' . PHP_EOL);
}
// Redirections to dispatcher
if ($rewrite_settings) {
fwrite($write_fd, "\n# Dispatcher\n");
fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -s [OR]\n");
fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -l [OR]\n");
fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -d\n");
if (Shop::isFeatureActive()) {
fwrite($write_fd, $domain_rewrite_cond);
}
fwrite($write_fd, "RewriteRule ^.*$ - [NC,L]\n");
if (Shop::isFeatureActive()) {
fwrite($write_fd, $domain_rewrite_cond);
}
fwrite($write_fd, "RewriteRule ^.*\$ %{ENV:REWRITEBASE}index.php [NC,L]\n");
}
}
fwrite($write_fd, "</IfModule>\n\n");
fwrite($write_fd, "AddType application/vnd.ms-fontobject .eot\n");
fwrite($write_fd, "AddType font/ttf .ttf\n");
fwrite($write_fd, "AddType font/otf .otf\n");
fwrite($write_fd, "AddType application/font-woff .woff\n");
fwrite($write_fd, "AddType font/woff2 .woff2\n");
fwrite($write_fd, "<IfModule mod_headers.c>
<FilesMatch \"\.(ttf|ttc|otf|eot|woff|woff2|svg)$\">
Header set Access-Control-Allow-Origin \"*\"
</FilesMatch>
</IfModule>\n\n");
fwrite($write_fd, '<Files composer.lock>
# Apache 2.2
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
# Apache 2.4
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
</Files>
');
// Cache control
if ($cache_control) {
$cache_control = "<IfModule mod_expires.c>
ExpiresActive On
AddType image/webp .webp
ExpiresByType image/webp \"access plus 1 month\"
ExpiresByType image/avif \"access plus 1 month\"
ExpiresByType image/gif \"access plus 1 month\"
ExpiresByType image/jpeg \"access plus 1 month\"
ExpiresByType image/png \"access plus 1 month\"
ExpiresByType text/css \"access plus 1 week\"
ExpiresByType text/javascript \"access plus 1 week\"
ExpiresByType application/javascript \"access plus 1 week\"
ExpiresByType application/x-javascript \"access plus 1 week\"
ExpiresByType image/x-icon \"access plus 1 year\"
ExpiresByType image/svg+xml \"access plus 1 year\"
ExpiresByType image/vnd.microsoft.icon \"access plus 1 year\"
ExpiresByType application/font-woff \"access plus 1 year\"
ExpiresByType application/x-font-woff \"access plus 1 year\"
ExpiresByType font/woff2 \"access plus 1 year\"
ExpiresByType application/vnd.ms-fontobject \"access plus 1 year\"
ExpiresByType font/opentype \"access plus 1 year\"
ExpiresByType font/ttf \"access plus 1 year\"
ExpiresByType font/otf \"access plus 1 year\"
ExpiresByType application/x-font-ttf \"access plus 1 year\"
ExpiresByType application/x-font-otf \"access plus 1 year\"
</IfModule>
<IfModule mod_headers.c>
Header unset Etag
</IfModule>
FileETag none
<IfModule mod_deflate.c>
<IfModule mod_filter.c>
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
</IfModule>
</IfModule>\n\n";
fwrite($write_fd, $cache_control);
}
// In case the user hasn't rewrite mod enabled
fwrite($write_fd, "#If rewrite mod isn't enabled\n");
// Do not remove ($domains is already iterated upper)
reset($domains);
$domain = current($domains);
fwrite($write_fd, 'ErrorDocument 404 ' . $domain[0]['physical'] . "index.php?controller=404\n\n");
fwrite($write_fd, '# ~~end~~ Do not remove this comment, Prestashop will keep automatically the code outside this comment when .htaccess will be generated again');
if ($specific_after) {
fwrite($write_fd, "\n\n" . trim($specific_after));
}
fclose($write_fd);
if (!defined('PS_INSTALLATION_IN_PROGRESS')) {
Hook::exec('actionHtaccessCreate');
}
return true;
}
/**
* @param bool $executeHook
*
* @return bool
*/
public static function generateRobotsFile($executeHook = false)
{
$robots_file = _PS_ROOT_DIR_ . '/robots.txt';
if (!$write_fd = @fopen($robots_file, 'wb')) {
return false;
}
$robots_content = static::getRobotsContent();
$languagesIsoIds = Language::getIsoIds();
if (true === $executeHook) {
Hook::exec('actionAdminMetaBeforeWriteRobotsFile', [
'rb_data' => &$robots_content,
]);
}
// PS Comments
fwrite($write_fd, "# robots.txt automatically generated by PrestaShop e-commerce open-source solution\n");
fwrite($write_fd, "# https://www.prestashop.com - https://www.prestashop.com/forums\n");
fwrite($write_fd, "# This file is to prevent the crawling and indexing of certain parts\n");
fwrite($write_fd, "# of your site by web crawlers and spiders run by sites like Yahoo!\n");
fwrite($write_fd, "# and Google. By telling these \"robots\" where not to go on your site,\n");
fwrite($write_fd, "# you save bandwidth and server resources.\n");
fwrite($write_fd, "# For more information about the robots.txt standard, see:\n");
fwrite($write_fd, "# https://www.robotstxt.org/robotstxt.html\n");
// User-Agent
fwrite($write_fd, "User-agent: *\n");
// Allow Directives
if (count($robots_content['Allow'])) {
fwrite($write_fd, "# Allow Directives\n");
foreach ($robots_content['Allow'] as $allow) {
fwrite($write_fd, 'Allow: ' . $allow . PHP_EOL);
}
}
// Private pages
if (count($robots_content['GB'])) {
fwrite($write_fd, "# Private pages\n");
foreach ($robots_content['GB'] as $gb) {
fwrite($write_fd, 'Disallow: /*' . $gb . PHP_EOL);
}
}
// Directories
if (count($robots_content['Directories'])) {
foreach (self::getDomains() as $domain => $uriList) {
fwrite(
$write_fd,
sprintf(
'# Directories for %s%s',
$domain,
PHP_EOL
)
);
// Disallow multishop directories
foreach ($uriList as $uri) {
foreach ($robots_content['Directories'] as $dir) {
fwrite($write_fd, 'Disallow: ' . $uri['physical'] . $dir . PHP_EOL);
}
// Disallow multilang directories
if (is_array($languagesIsoIds) && count($languagesIsoIds) > 1) {
foreach ($languagesIsoIds as $language) {
foreach ($robots_content['Directories'] as $dir) {
fwrite(
$write_fd,
sprintf(
'Disallow: %s%s/%s%s',
$uri['physical'],
$language['iso_code'],
$dir,
PHP_EOL
)
);
}
}
}
// Files
if (count($robots_content['Files'])) {
fwrite($write_fd, "# Files\n");
foreach ($robots_content['Files'] as $iso_code => $files) {
foreach ($files as $file) {
if (count($languagesIsoIds) > 1) {
fwrite($write_fd, 'Disallow: /*' . $iso_code . '/' . $file . PHP_EOL);
} else {
fwrite($write_fd, 'Disallow: ' . $uri['physical'] . $file . PHP_EOL);
}
}
}
}
}
}
}
if (null === Context::getContext()) {
$sitemap_file = _PS_ROOT_DIR_ . DIRECTORY_SEPARATOR . 'index_sitemap.xml';
} else {
$sitemap_file = _PS_ROOT_DIR_ . DIRECTORY_SEPARATOR . Context::getContext()->shop->id . '_index_sitemap.xml';
}
// Sitemap
if (file_exists($sitemap_file) && filesize($sitemap_file)) {
fwrite($write_fd, "# Sitemap\n");
$sitemap_filename = basename($sitemap_file);
fwrite($write_fd, 'Sitemap: ' . static::getProtocol((bool) Configuration::get('PS_SSL_ENABLED')) . $_SERVER['SERVER_NAME']
. __PS_BASE_URI__ . $sitemap_filename . PHP_EOL);
}
if (true === $executeHook) {
Hook::exec('actionAdminMetaAfterWriteRobotsFile', [
'rb_data' => $robots_content,
'write_fd' => &$write_fd,
]);
}
fclose($write_fd);
return true;
}
/**
* @return array
*/
public static function getRobotsContent()
{
$tab = [];
// Special allow directives
$tab['Allow'] = [
'*/modules/*.css',
'*/modules/*.js',
'*/modules/*.png',
'*/modules/*.jpg',
'*/modules/*.gif',
'*/modules/*.svg',
'*/modules/*.webp',
'/js/jquery/*',
];
// Directories
$tab['Directories'] = [
'app/', 'cache/', 'classes/', 'config/', 'controllers/',
'download/', 'js/', 'localization/', 'log/', 'mails/', 'modules/', 'override/',
'pdf/', 'src/', 'tools/', 'translations/', 'upload/', 'var/', 'vendor/', 'webservice/',
];
// Files
$disallow_controllers = [
'addresses', 'address', 'authentication', 'cart', 'discount', 'footer',
'get-file', 'header', 'history', 'identity', 'images.inc', 'init', 'my-account', 'order',
'order-slip', 'order-detail', 'order-follow', 'order-return', 'order-confirmation', 'pagination', 'password',
'pdf-invoice', 'pdf-order-return', 'pdf-order-slip', 'product-sort', 'registration', 'search', 'statistics', 'attachment', 'guest-tracking',
];
// Rewrite files
$tab['Files'] = [];
if (Configuration::get('PS_REWRITING_SETTINGS')) {
$sql = 'SELECT DISTINCT ml.url_rewrite, l.iso_code
FROM ' . _DB_PREFIX_ . 'meta m
INNER JOIN ' . _DB_PREFIX_ . 'meta_lang ml ON ml.id_meta = m.id_meta
INNER JOIN ' . _DB_PREFIX_ . 'lang l ON l.id_lang = ml.id_lang
WHERE l.active = 1 AND m.page IN (\'' . implode('\', \'', $disallow_controllers) . '\')';
if ($results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql)) {
foreach ($results as $row) {
$tab['Files'][$row['iso_code']][] = $row['url_rewrite'];
}
}
}
$tab['GB'] = [
'?order=', '?tag=', '?id_currency=', '?search_query=', '?back=', '?n=',
'&order=', '&tag=', '&id_currency=', '&search_query=', '&back=', '&n=',
];
foreach ($disallow_controllers as $controller) {
$tab['GB'][] = 'controller=' . $controller;
}
return $tab;
}
public static function generateIndex()
{
@trigger_error(
sprintf(
'%s is deprecated since version 8.1. Use PrestaShop\Autoload\PrestashopAutoload instead.',
__METHOD__
),
E_USER_DEPRECATED
);
\PrestaShop\Autoload\PrestashopAutoload::getInstance()->generateIndex();
}
/**
* @return string php file to be run
*/
public static function getDefaultIndexContent()
{
return '<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;
';
}
/**
* Return the directory list from the given $path.
*
* @param string $path
*
* @return array
*/
public static function getDirectories($path)
{
if (function_exists('glob')) {
return self::getDirectoriesWithGlob($path);
}
return self::getDirectoriesWithReaddir($path);
}
/**
* Return the directory list from the given $path using php glob function.
*
* @param string $path
*
* @return array
*/
public static function getDirectoriesWithGlob($path)
{
$directoryList = glob($path . '/*', GLOB_ONLYDIR | GLOB_NOSORT);
if ($directoryList === false) {
return [];
}
$directoryList = array_map(function ($path) {
return substr($path, strrpos($path, '/') + 1);
}, $directoryList);
return $directoryList;
}
/**
* Return the directory list from the given $path using php readdir function.
*
* @param string $path
*
* @return array
*/
public static function getDirectoriesWithReaddir($path)
{
$directoryList = [];
$dh = @opendir($path);
if ($dh) {
while (($file = @readdir($dh)) !== false) {
if (is_dir($path . DIRECTORY_SEPARATOR . $file) && $file[0] != '.') {
$directoryList[] = $file;
}
}
@closedir($dh);
}
return $directoryList;
}
/**
* Display a warning message indicating that the method is deprecated.
*
* @param string $message
*/
public static function displayAsDeprecated($message = null)
{
$backtrace = debug_backtrace();
$callee = next($backtrace);
$class = isset($callee['class']) ? $callee['class'] : null;
if ($message === null) {
$message = 'The function ' . $callee['function'] . ' (Line ' . $callee['line'] . ') is deprecated and will be removed in the next major version.';
}
$error = 'Function <b>' . $callee['function'] . '()</b> is deprecated in <b>' . $callee['file'] . '</b> on line <b>' . $callee['line'] . '</b><br />';
Tools::throwDeprecated($error, $message, $class);
}
/**
* Display a warning message indicating that the parameter is deprecated.
*/
public static function displayParameterAsDeprecated($parameter)
{
$backtrace = debug_backtrace();
$callee = next($backtrace);
$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 />';
$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.';
$class = isset($callee['class']) ? $callee['class'] : null;
Tools::throwDeprecated($error, $message, $class);
}
public static function displayFileAsDeprecated()
{
$backtrace = debug_backtrace();
$callee = current($backtrace);
$error = 'File <b>' . $callee['file'] . '</b> is deprecated<br />';
$message = 'The file ' . $callee['file'] . ' is deprecated and will be removed in the next major version.';
$class = isset($callee['class']) ? $callee['class'] : null;
Tools::throwDeprecated($error, $message, $class);
}
protected static function throwDeprecated($error, $message, $class)
{
if (_PS_DISPLAY_COMPATIBILITY_WARNING_) {
@trigger_error($error, E_USER_DEPRECATED);
PrestaShopLogger::addLog($message, 3, $class);
}
}
public static function enableCache($level = 1, Context $context = null)
{
if (!$context) {
$context = Context::getContext();
}
$smarty = $context->smarty;
if (!Configuration::get('PS_SMARTY_CACHE')) {
return;
}
if ($smarty->force_compile == 0 && $smarty->caching == $level) {
return;
}
self::$_forceCompile = (int) $smarty->force_compile;
self::$_caching = (int) $smarty->caching;
$smarty->force_compile = false;
$smarty->caching = (int) $level;
$smarty->cache_lifetime = 31536000; // 1 Year
}
public static function restoreCacheSettings(Context $context = null)
{
if (!$context) {
$context = Context::getContext();
}
if (isset(self::$_forceCompile)) {
$context->smarty->force_compile = (bool) self::$_forceCompile;
}
if (isset(self::$_caching)) {
$context->smarty->caching = (int) self::$_caching;
}
}
public static function isCallable($function)
{
$disabled = explode(',', ini_get('disable_functions'));
return !in_array($function, $disabled) && is_callable($function);
}
public static function pRegexp($s, $delim)
{
$s = str_replace($delim, '\\' . $delim, $s);
foreach (['?', '[', ']', '(', ')', '{', '}', '-', '.', '+', '*', '^', '$', '`', '"', '%'] as $char) {
$s = str_replace($char, '\\' . $char, $s);
}
return $s;
}
public static function str_replace_once($needle, $replace, $haystack)
{
$pos = false;
if ($needle) {
$pos = strpos($haystack, $needle);
}
if ($pos === false) {
return $haystack;
}
return substr_replace($haystack, $replace, $pos, strlen($needle));
}
/**
* Identify the version of php
*
* @return string
*/
public static function checkPhpVersion()
{
if (defined('PHP_VERSION')) {
$version = PHP_VERSION;
} else {
$version = phpversion('');
}
// Specific ubuntu usecase: php version returns 5.2.4-2ubuntu5.2
if (strpos($version, '-') !== false) {
$version = substr($version, 0, strpos($version, '-'));
}
return $version;
}
/**
* Try to open a zip file in order to check if it's valid
*
* @param string $from_file
*
* @return bool success
*/
public static function ZipTest($from_file)
{
$zip = new ZipArchive();
return $zip->open($from_file, ZipArchive::CHECKCONS) === true;
}
/**
* Extract a zip file to the given directory
*
* @param string $from_file
* @param string $to_dir
*
* @return bool
*/
public static function ZipExtract($from_file, $to_dir)
{
if (!file_exists($to_dir)) {
mkdir($to_dir, PsFileSystem::DEFAULT_MODE_FOLDER);
}
$zip = new ZipArchive();
if ($zip->open($from_file) === true && $zip->extractTo($to_dir) && $zip->close()) {
return true;
}
return false;
}
/**
* @param string $path
* @param int $filemode
*
* @return bool
*/
public static function chmodr($path, $filemode)
{
if (!is_dir($path)) {
return @chmod($path, $filemode);
}
$dh = opendir($path);
while (($file = readdir($dh)) !== false) {
if ($file != '.' && $file != '..') {
$fullpath = $path . '/' . $file;
if (is_link($fullpath)) {
return false;
} elseif (!is_dir($fullpath) && !@chmod($fullpath, $filemode)) {
return false;
} elseif (!Tools::chmodr($fullpath, $filemode)) {
return false;
}
}
}
closedir($dh);
if (@chmod($path, $filemode)) {
return true;
} else {
return false;
}
}
/**
* Get products order field name for queries.
*
* @param string $type by|way
* @param string|bool|null $value If no index given, use default order from admin -> pref -> products
* @param bool|string $prefix
*
* @return string Order by sql clause
*/
public static function getProductsOrder($type, $value = null, $prefix = false)
{
switch ($type) {
case 'by':
$list = [0 => 'name', 1 => 'price', 2 => 'date_add', 3 => 'date_upd', 4 => 'position', 5 => 'manufacturer_name', 6 => 'quantity', 7 => 'reference'];
$value = (null === $value || $value === false || $value === '') ? (int) Configuration::get('PS_PRODUCTS_ORDER_BY') : $value;
$value = (isset($list[$value])) ? $list[$value] : ((in_array($value, $list)) ? $value : 'position');
$order_by_prefix = '';
if ($prefix) {
if ($value == 'id_product' || $value == 'date_add' || $value == 'date_upd' || $value == 'price') {
$order_by_prefix = 'p.';
} elseif ($value == 'name') {
$order_by_prefix = 'pl.';
} elseif ($value == 'manufacturer_name') {
$order_by_prefix = 'm.';
$value = 'name';
} elseif ($value == 'position' || empty($value)) {
$order_by_prefix = 'cp.';
}
}
return $order_by_prefix . $value;
case 'way':
$value = (null === $value || $value === false || $value === '') ? (int) Configuration::get('PS_PRODUCTS_ORDER_WAY') : $value;
$list = [0 => 'asc', 1 => 'desc'];
return (isset($list[$value])) ? $list[$value] : ((in_array($value, $list)) ? $value : 'asc');
}
return '';
}
/**
* Convert a shorthand byte value from a PHP configuration directive to an integer value.
*
* @param string $value value to convert
*
* @return int|string
*/
public static function convertBytes($value)
{
if (is_numeric($value)) {
return $value;
} else {
$value_length = strlen($value);
$qty = (int) substr($value, 0, $value_length - 1);
$unit = Tools::strtolower(substr($value, $value_length - 1));
switch ($unit) {
case 'k':
$qty *= 1024;
break;
case 'm':
$qty *= 1048576;
break;
case 'g':
$qty *= 1073741824;
break;
}
return $qty;
}
}
/**
* Concat $begin and $end, add ? or & between strings.
*
* @since 1.5.0
*
* @param string $begin
* @param string $end
*
* @return string
*/
public static function url($begin, $end)
{
return $begin . ((strpos($begin, '?') !== false) ? '&' : '?') . $end;
}
/**
* Display error and dies or silently log the error.
*
* @param string $msg
* @param bool $die
*
* @return bool success of logging
*/
public static function dieOrLog($msg, $die = true)
{
if ($die || (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_)) {
header('HTTP/1.1 500 Internal Server Error', true, 500);
die($msg);
}
return PrestaShopLogger::addLog($msg);
}
/**
* Convert \n and \r\n and \r to <br />.
*
* @param string|null $str String to transform
*
* @return string|null New string
*/
public static function nl2br($str)
{
if (empty($str)) {
return $str;
}
return str_replace(["\r\n", "\r", "\n", AddressFormat::FORMAT_NEW_LINE, PHP_EOL], '<br />', $str);
}
/**
* Clear cache for Smarty.
*
* @param Smarty $smarty
* @param bool|string $tpl
* @param string $cache_id
* @param string $compile_id
*
* @return int|null number of cache files deleted
*/
public static function clearCache($smarty = null, $tpl = false, $cache_id = null, $compile_id = null)
{
if ($smarty === null) {
$smarty = Context::getContext()->smarty;
}
if ($smarty === null) {
return null;
}
if (!$tpl && $cache_id === null && $compile_id === null) {
return $smarty->clearAllCache();
}
$ret = $smarty->clearCache($tpl, $cache_id, $compile_id);
Hook::exec('actionClearCache');
return $ret;
}
/**
* Clear compile for Smarty.
*
* @param Smarty $smarty
*
* @return int|null number of template files deleted
*/
public static function clearCompile($smarty = null)
{
if ($smarty === null) {
$smarty = Context::getContext()->smarty;
}
if ($smarty === null) {
return null;
}
$ret = $smarty->clearCompiledTemplate();
Hook::exec('actionClearCompileCache');
return $ret;
}
/**
* Clear Smarty cache and compile folders.
*/
public static function clearSmartyCache()
{
$smarty = Context::getContext()->smarty;
Tools::clearCache($smarty);
Tools::clearCompile($smarty);
}
/**
* Clear Symfony cache.
*
* @param string $env
*/
public static function clearSf2Cache($env = null)
{
// This is the legacy method to clear Symfony cache, but it can result in unexpected behaviour with Container rebuild
// it should not be used anymore and will be removed. Until then, it fallbacks on the proper SymfonyCacheClearer service
$container = SymfonyContainer::getInstance();
if (null === $container) {
self::removeSymfonyCache($env);
return;
}
/** @var CacheClearerInterface|null $symfonyCacheClearer */
$symfonyCacheClearer = $container->get('prestashop.adapter.cache.clearer.symfony_cache_clearer');
if ($symfonyCacheClearer) {
$symfonyCacheClearer->clear();
}
}
private static function removeSymfonyCache(?string $env = null): void
{
if (null === $env) {
$env = _PS_ENV_;
}
$dir = _PS_ROOT_DIR_ . '/var/cache/' . $env . '/';
register_shutdown_function(function () use ($dir) {
$fs = new Filesystem();
$fs->remove($dir);
Hook::exec('actionClearSf2Cache');
});
}
/**
* Clear both Smarty and Symfony cache.
*/
public static function clearAllCache()
{
Tools::clearSmartyCache();
Tools::clearSf2Cache();
}
/**
* @deprecated since 8.1 and will be removed in next major version.
*
* @param int|bool $id_product
*/
public static function clearColorListCache($id_product = false)
{
// Change template dir if called from the BackOffice
$current_template_dir = Context::getContext()->smarty->getTemplateDir();
Context::getContext()->smarty->setTemplateDir(_PS_THEME_DIR_);
Tools::clearCache(null, _PS_THEME_DIR_ . 'product-list-colors.tpl', Product::getColorsListCacheId((int) $id_product, false));
Context::getContext()->smarty->setTemplateDir($current_template_dir);
}
/**
* Allow to get the memory limit in octets.
*
* @since 1.4.5.0
*
* @return int|string the memory limit value in octet
*/
public static function getMemoryLimit()
{
$memory_limit = @ini_get('memory_limit');
return Tools::getOctets($memory_limit);
}
/**
* Gets the value of a configuration option in octets.
*
* @since 1.5.0
*
* @param string $option
*
* @return int|string the value of a configuration option in octets
*/
public static function getOctets($option)
{
if (preg_match('/[0-9]+k/i', $option)) {
return 1024 * (int) $option;
}
if (preg_match('/[0-9]+m/i', $option)) {
return 1024 * 1024 * (int) $option;
}
if (preg_match('/[0-9]+g/i', $option)) {
return 1024 * 1024 * 1024 * (int) $option;
}
return $option;
}
/**
* @return bool true if the server use 64bit arch
*/
public static function isX86_64arch()
{
return PHP_INT_MAX == '9223372036854775807';
}
/**
* @return bool true if php-cli is used
*/
public static function isPHPCLI()
{
return defined('STDIN') || (Tools::strtolower(PHP_SAPI) == 'cli' && (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])));
}
public static function argvToGET($argc, $argv)
{
if ($argc <= 1) {
return;
}
// get the first argument and parse it like a query string
parse_str($argv[1], $args);
if (!is_array($args) || !count($args)) {
return;
}
$_GET = array_merge($args, $_GET);
$_SERVER['QUERY_STRING'] = $argv[1];
}
/**
* Get max file upload size considering server settings and optional max value.
*
* @param int $max_size optional max file size
*
* @return int max file size in bytes
*/
public static function getMaxUploadSize($max_size = 0)
{
$values = [Tools::convertBytes(ini_get('upload_max_filesize'))];
if ($max_size > 0) {
$values[] = $max_size;
}
$post_max_size = Tools::convertBytes(ini_get('post_max_size'));
if ($post_max_size > 0) {
$values[] = $post_max_size;
}
return min($values);
}
/**
* apacheModExists return true if the apache module $name is loaded.
*
* @TODO move this method in class Information (when it will exist)
*
* Notes: This method requires either apache_get_modules or phpinfo()
* to be available. With CGI mod, we cannot get php modules
*
* @param string $name module name
*
* @return bool true if exists
*
* @since 1.4.5.0
*/
public static function apacheModExists($name)
{
if (function_exists('apache_get_modules')) {
static $apache_module_list = null;
if (!is_array($apache_module_list)) {
$apache_module_list = apache_get_modules();
}
// we need strpos (example, evasive can be evasive20)
foreach ($apache_module_list as $module) {
if (strpos($module, $name) !== false) {
return true;
}
}
}
return false;
}
/**
* Fix native uasort see: http://php.net/manual/en/function.uasort.php#114535.
*
* @param array $array
* @param callable $cmp_function
*/
public static function uasort(&$array, $cmp_function)
{
if (count($array) < 2) {
return;
}
$halfway = (int) (count($array) / 2);
$array1 = array_slice($array, 0, $halfway, true);
$array2 = array_slice($array, $halfway, null, true);
self::uasort($array1, $cmp_function);
self::uasort($array2, $cmp_function);
if (call_user_func($cmp_function, end($array1), reset($array2)) < 1) {
$array = $array1 + $array2;
return;
}
$array = [];
reset($array1);
reset($array2);
while (current($array1) && current($array2)) {
if (call_user_func($cmp_function, current($array1), current($array2)) < 1) {
$array[key($array1)] = current($array1);
next($array1);
} else {
$array[key($array2)] = current($array2);
next($array2);
}
}
while (current($array1)) {
$array[key($array1)] = current($array1);
next($array1);
}
while (current($array2)) {
$array[key($array2)] = current($array2);
next($array2);
}
}
/**
* Copy the folder $src into $dst, $dst is created if it do not exist.
*
* @param string $src
* @param string $dst
* @param bool $del if true, delete the file after copy
*/
public static function recurseCopy($src, $dst, $del = false)
{
if (!Tools::file_exists_cache($src)) {
return false;
}
$dir = opendir($src);
if (!Tools::file_exists_cache($dst)) {
mkdir($dst);
}
while (false !== ($file = readdir($dir))) {
if (($file != '.') && ($file != '..')) {
if (is_dir($src . DIRECTORY_SEPARATOR . $file)) {
self::recurseCopy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file, $del);
} else {
copy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file);
if ($del && is_writable($src . DIRECTORY_SEPARATOR . $file)) {
unlink($src . DIRECTORY_SEPARATOR . $file);
}
}
}
}
closedir($dir);
if ($del && is_writable($src)) {
rmdir($src);
}
}
/**
* @param string $path Path to scan
* @param string $ext Extention to filter files
* @param string $dir Add this to prefix output for example /path/dir/*
*
* @return array List of file found
*
* @since 1.5.0
*/
public static function scandir($path, $ext = 'php', $dir = '', $recursive = false)
{
$path = rtrim(rtrim($path, '\\'), '/') . '/';
$real_path = rtrim(rtrim($path . $dir, '\\'), '/') . '/';
$files = scandir($real_path, SCANDIR_SORT_NONE);
if (!$files) {
return [];
}
$filtered_files = [];
$real_ext = false;
if (!empty($ext)) {
$real_ext = '.' . $ext;
}
$real_ext_length = strlen($real_ext);
$subdir = ($dir) ? $dir . '/' : '';
foreach ($files as $file) {
if (!$real_ext || (strpos($file, $real_ext) && strpos($file, $real_ext) == (strlen($file) - $real_ext_length))) {
$filtered_files[] = $subdir . $file;
}
if ($recursive && $file[0] != '.' && is_dir($real_path . $file)) {
foreach (Tools::scandir($path, $ext, $subdir . $file, $recursive) as $subfile) {
$filtered_files[] = $subfile;
}
}
}
return $filtered_files;
}
/**
* Align version sent and use internal function.
*
* @param string $v1
* @param string $v2
* @param string $operator
*
* @return mixed
*/
public static function version_compare($v1, $v2, $operator = '<')
{
Tools::alignVersionNumber($v1, $v2);
return version_compare($v1, $v2, $operator);
}
/**
* Align 2 version with the same number of sub version
* version_compare will work better for its comparison :)
* (Means: '1.8' to '1.9.3' will change '1.8' to '1.8.0').
*
* @param string $v1
* @param string $v2
*/
public static function alignVersionNumber(&$v1, &$v2)
{
$len1 = count(explode('.', trim($v1, '.')));
$len2 = count(explode('.', trim($v2, '.')));
$len = 0;
$str = '';
if ($len1 > $len2) {
$len = $len1 - $len2;
$str = &$v2;
} elseif ($len2 > $len1) {
$len = $len2 - $len1;
$str = &$v1;
}
for ($len; $len > 0; --$len) {
$str .= '.0';
}
}
public static function modRewriteActive()
{
if (Tools::apacheModExists('mod_rewrite')) {
return true;
}
if ((isset($_SERVER['HTTP_MOD_REWRITE']) && Tools::strtolower($_SERVER['HTTP_MOD_REWRITE']) == 'on') || Tools::strtolower(getenv('HTTP_MOD_REWRITE')) == 'on') {
return true;
}
return false;
}
public static function unSerialize($serialized, $object = false)
{
if (is_string($serialized) && (strpos($serialized, 'O:') === false || !preg_match('/(^|;|{|})O:[0-9]+:"/', $serialized)) && !$object || $object) {
return @unserialize($serialized);
}
return false;
}
/**
* Reproduce array_unique working before php version 5.2.9.
*
* @param array $array
*
* @return array
*/
public static function arrayUnique($array)
{
return array_unique($array, SORT_REGULAR);
}
/**
* Delete unicode class from regular expression patterns.
*
* @deprecated Since 8.0.0 and will be removed in the next major.
*
* @param string $pattern
*
* @return string pattern
*
* @throws Exception
*/
public static function cleanNonUnicodeSupport($pattern)
{
@trigger_error(
sprintf(
'%s is deprecated since version 8.0.0. Its use is not required.',
__METHOD__
),
E_USER_DEPRECATED
);
return $pattern;
}
/**
* Returns an array containing information about
* HTTP file upload variable ($_FILES).
*
* @param string $input File upload field name
* @param bool $return_content If true, returns uploaded file contents
*
* @return array|null
*/
public static function fileAttachment($input = 'fileUpload', $return_content = true)
{
$file_attachment = null;
if (isset($_FILES[$input]['name']) && !empty($_FILES[$input]['name']) && !empty($_FILES[$input]['tmp_name'])) {
$file_attachment['rename'] = uniqid() . Tools::strtolower(substr($_FILES[$input]['name'], -5));
if ($return_content) {
$file_attachment['content'] = file_get_contents($_FILES[$input]['tmp_name']);
}
$file_attachment['tmp_name'] = $_FILES[$input]['tmp_name'];
$file_attachment['name'] = $_FILES[$input]['name'];
$file_attachment['mime'] = $_FILES[$input]['type'];
$file_attachment['error'] = $_FILES[$input]['error'];
$file_attachment['size'] = $_FILES[$input]['size'];
}
return $file_attachment;
}
public static function changeFileMTime($file_name)
{
@touch($file_name);
}
public static function waitUntilFileIsModified($file_name, $timeout = 180)
{
@ini_set('max_execution_time', $timeout);
$time_limit = ini_get('max_execution_time');
if (!$time_limit) {
$time_limit = 30;
}
$time_limit -= 5;
$start_time = microtime(true);
$last_modified = @filemtime($file_name);
while (true) {
if (((microtime(true) - $start_time) > $time_limit) || @filemtime($file_name) > $last_modified) {
break;
}
clearstatcache();
usleep(300);
}
}
/**
* Delete a substring from another one starting from the right.
*
* @param string $str
* @param string $str_search
*
* @return string
*/
public static function rtrimString($str, $str_search)
{
$length_str = strlen($str_search);
if (strlen($str) >= $length_str && substr($str, -$length_str) == $str_search) {
$str = substr($str, 0, -$length_str);
}
return $str;
}
/**
* Format a number into a human readable format
* e.g. 24962496 => 23.81M.
*
* @param float $size
* @param int $precision
*
* @return string
*/
public static function formatBytes($size, $precision = 2)
{
if (!$size) {
return '0';
}
$base = log($size) / log(1024);
$suffixes = ['B', 'KB', 'MB', 'GB', 'TB'];
return round(1024 ** ($base - floor($base)), $precision) . Context::getContext()->getTranslator()->trans($suffixes[floor($base)], [], 'Shop.Theme.Catalog');
}
public static function boolVal($value)
{
if (empty($value)) {
$value = false;
}
return (bool) $value;
}
public static function getUserPlatform()
{
if (isset(self::$_user_plateform)) {
return self::$_user_plateform;
}
$user_agent = $_SERVER['HTTP_USER_AGENT'];
self::$_user_plateform = 'unknown';
if (preg_match('/linux/i', $user_agent)) {
self::$_user_plateform = 'Linux';
} elseif (preg_match('/macintosh|mac os x/i', $user_agent)) {
self::$_user_plateform = 'Mac';
} elseif (preg_match('/windows|win32/i', $user_agent)) {
self::$_user_plateform = 'Windows';
}
return self::$_user_plateform;
}
public static function getUserBrowser()
{
if (isset(self::$_user_browser)) {
return self::$_user_browser;
}
$user_agent = $_SERVER['HTTP_USER_AGENT'];
self::$_user_browser = 'unknown';
if (preg_match('/MSIE/i', $user_agent) && !preg_match('/Opera/i', $user_agent)) {
self::$_user_browser = 'Internet Explorer';
} elseif (preg_match('/Firefox/i', $user_agent)) {
self::$_user_browser = 'Mozilla Firefox';
} elseif (preg_match('/Chrome/i', $user_agent)) {
self::$_user_browser = 'Google Chrome';
} elseif (preg_match('/Safari/i', $user_agent)) {
self::$_user_browser = 'Apple Safari';
} elseif (preg_match('/Opera/i', $user_agent)) {
self::$_user_browser = 'Opera';
} elseif (preg_match('/Netscape/i', $user_agent)) {
self::$_user_browser = 'Netscape';
}
return self::$_user_browser;
}
/**
* Allows to display the category description without HTML tags and slashes.
*
* @deprecated since version 8.1.0, to be removed.
*
* @return string
*/
public static function getDescriptionClean($description)
{
@trigger_error(
sprintf(
'%s is deprecated since version 8.1.0. There is no replacement',
__METHOD__
),
E_USER_DEPRECATED
);
return strip_tags(stripslashes($description));
}
public static function purifyHTML($html, $uri_unescape = null, $allow_style = false)
{
static $use_html_purifier = null;
static $purifier = null;
if (defined('PS_INSTALLATION_IN_PROGRESS') || !Configuration::configurationIsLoaded()) {
return $html;
}
if ($use_html_purifier === null) {
$use_html_purifier = (bool) Configuration::get('PS_USE_HTMLPURIFIER');
}
if ($use_html_purifier) {
if ($purifier === null) {
$config = HTMLPurifier_Config::createDefault();
$config->set('Attr.EnableID', true);
$config->set('Attr.AllowedRel', ['nofollow']);
$config->set('HTML.Trusted', true);
$config->set('Cache.SerializerPath', _PS_CACHE_DIR_ . 'purifier');
$config->set('Attr.AllowedFrameTargets', ['_blank', '_self', '_parent', '_top']);
if (is_array($uri_unescape)) {
$config->set('URI.UnescapeCharacters', implode('', $uri_unescape));
}
if (Configuration::get('PS_ALLOW_HTML_IFRAME')) {
$config->set('HTML.SafeIframe', true);
$config->set('HTML.SafeObject', true);
$config->set('URI.SafeIframeRegexp', '/.*/');
}
// http://developers.whatwg.org/the-video-element.html#the-video-element
if ($def = $config->getHTMLDefinition(true)) {
/* @var HTMLPurifier_HTMLDefinition|HTMLPurifier_HTMLModule $def */
$def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [
'src' => 'URI',
'type' => 'Text',
'width' => 'Length',
'height' => 'Length',
'poster' => 'URI',
'preload' => 'Enum#auto,metadata,none',
'controls' => 'Bool',
]);
$def->addElement('source', 'Block', 'Flow', 'Common', [
'src' => 'URI',
'type' => 'Text',
]);
if ($allow_style) {
$def->addElement('style', 'Block', 'Flow', 'Common', ['type' => 'Text']);
}
}
$purifier = new HTMLPurifier($config);
}
$html = $purifier->purify($html);
}
return $html;
}
/**
* Check if a constant was already defined.
*
* @param string $constant Constant name
* @param mixed $value Default value to set if not defined
*/
public static function safeDefine($constant, $value)
{
if (!defined($constant)) {
define($constant, $value);
}
}
/**
* Spread an amount on lines, adjusting the $column field,
* with the biggest adjustments going to the rows having the
* highest $sort_column.
*
* E.g.:
* $rows = [['a' => 5.1], ['a' => 8.2]];
* spreadAmount(0.3, 1, $rows, 'a');
* => $rows is [['a' => 8.4], ['a' => 5.2]]
*
* @param float $amount The amount to spread across the rows
* @param int $precision Rounding precision
* e.g. if $amount is 1, $precision is 0 and $rows = [['a' => 2], ['a' => 1]]
* then the resulting $rows will be [['a' => 3], ['a' => 1]]
* But if $precision were 1, then the resulting $rows would be [['a' => 2.5], ['a' => 1.5]]
* @param array $rows An array, associative or not, containing arrays that have at least $column and $sort_column fields
* @param string $column The column on which to perform adjustments
*/
public static function spreadAmount($amount, $precision, &$rows, $column)
{
if (!is_array($rows) || empty($rows)) {
return;
}
$sort_function = function ($a, $b) use ($column) {
return $b[$column] > $a[$column] ? 1 : -1;
};
uasort($rows, $sort_function);
$unit = 10 ** $precision;
$int_amount = (int) round($unit * $amount);
$remainder = $int_amount % count($rows);
$amount_to_spread = ($int_amount - $remainder) / count($rows) / $unit;
$sign = ($amount >= 0 ? 1 : -1);
$position = 0;
foreach ($rows as &$row) {
$adjustment_factor = $amount_to_spread;
if ($position < abs($remainder)) {
$adjustment_factor += $sign * 1 / $unit;
}
$row[$column] += $adjustment_factor;
++$position;
}
unset($row);
}
/**
* Replaces elements from passed arrays into the first array recursively.
*
* @param array $base the array in which elements are replaced
* @param array $replacements the array from which elements will be extracted
*
* @deprecated since version 8.0.0, to be removed.
*/
public static function arrayReplaceRecursive($base, $replacements)
{
Tools::displayAsDeprecated('Use PHP\'s array_replace_recursive() instead');
return array_replace_recursive($base, $replacements);
}
/**
* Return path to a Product or a CMS category.
*
* @param string $url_base Start URL
* @param int $id_category Start category
* @param string $path Current path
* @param string $highlight String to highlight (in XHTML/CSS)
* @param string $category_type Category type (products/cms)
* @param bool $home
*/
public static function getPath($url_base, $id_category, $path = '', $highlight = '', $category_type = 'catalog', $home = false)
{
$context = Context::getContext();
if ($category_type == 'catalog') {
$category = Db::getInstance()->getRow('
SELECT id_category, level_depth, nleft, nright
FROM ' . _DB_PREFIX_ . 'category
WHERE id_category = ' . (int) $id_category);
if (isset($category['id_category'])) {
$sql = 'SELECT c.id_category, cl.name, cl.link_rewrite
FROM ' . _DB_PREFIX_ . 'category c
LEFT JOIN ' . _DB_PREFIX_ . 'category_lang cl ON (cl.id_category = c.id_category' . Shop::addSqlRestrictionOnLang('cl') . ')
WHERE c.nleft <= ' . (int) $category['nleft'] . '
AND c.nright >= ' . (int) $category['nright'] . '
AND cl.id_lang = ' . (int) $context->language->id .
($home ? ' AND c.id_category=' . (int) $id_category : '') . '
AND c.id_category != ' . (int) Category::getTopCategory()->id . '
GROUP BY c.id_category
ORDER BY c.level_depth ASC
LIMIT ' . (!$home ? (int) $category['level_depth'] + 1 : 1);
$categories = Db::getInstance()->executeS($sql);
$full_path = '';
$n = 1;
$n_categories = (int) count($categories);
foreach ($categories as $category) {
$action = (($category['id_category'] == (int) Configuration::get('PS_HOME_CATEGORY') || $home) ? 'index' : 'updatecategory');
$link_params = ['action' => $action, 'id_category' => (int) $category['id_category']];
$edit_link = Context::getContext()->link->getAdminLink('AdminCategories', true, $link_params);
$link_params['action'] = 'index';
$index_link = Context::getContext()->link->getAdminLink('AdminCategories', true, $link_params);
$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> ';
$full_path .= $edit .
($n < $n_categories ? '<a href="' . Tools::safeOutput($index_link) . '" title="' . htmlentities($category['name'], ENT_NOQUOTES, 'UTF-8') . '">' : '') .
(!empty($highlight) ? str_ireplace($highlight, '<span class="highlight">' . htmlentities($highlight, ENT_NOQUOTES, 'UTF-8') . '</span>', $category['name']) : $category['name']) .
($n < $n_categories ? '</a>' : '') .
(($n++ != $n_categories || !empty($path)) ? ' > ' : '');
}
return $full_path . $path;
}
} elseif ($category_type == 'cms') {
$category = new CMSCategory($id_category, $context->language->id);
if (!$category->id) {
return $path;
}
$name = ($highlight != null) ? str_ireplace($highlight, '<span class="highlight">' . $highlight . '</span>', CMSCategory::hideCMSCategoryPosition($category->name)) : CMSCategory::hideCMSCategoryPosition($category->name);
$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)) . '">
<i class="icon-pencil"></i></a> ';
if ($category->id == 1) {
$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)) . '">
<i class="icon-home"></i></a></li> ';
}
$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)) . '">
' . $name . '</a></li> > ' . $path;
if ($category->id == 1) {
return substr($path, 0, strlen($path) - 3);
}
return Tools::getPath($url_base, $category->id_parent, $path, '', 'cms');
}
}
public static function redirectToInstall()
{
if (file_exists(__DIR__ . '/../install')) {
if (defined('_PS_ADMIN_DIR_')) {
header('Location: ../install/');
} else {
header('Location: install/');
}
} elseif (file_exists(__DIR__ . '/../install-dev')) {
if (defined('_PS_ADMIN_DIR_')) {
header('Location: ../install-dev/');
} else {
header('Location: install-dev/');
}
} else {
die('Error: "install" directory is missing');
}
exit;
}
/**
* @param array $fallbackParameters
*/
public static function setFallbackParameters(array $fallbackParameters): void
{
static::$fallbackParameters = $fallbackParameters;
}
/**
* @param string $file_to_refresh
* @param string $external_file
*
* @return bool
*/
public static function refreshFile(string $file_to_refresh, string $external_file): bool
{
return (bool) static::copy($external_file, _PS_ROOT_DIR_ . $file_to_refresh);
}
/**
* @param string $file
* @param int $timeout
*
* @return bool
*/
public static function isFileFresh(string $file, int $timeout = self::CACHE_LIFETIME_SECONDS): bool
{
if (($time = @filemtime(_PS_ROOT_DIR_ . $file)) && filesize(_PS_ROOT_DIR_ . $file) > 0) {
return (time() - $time) < $timeout;
}
return false;
}
/**
* @return bool
*/
public static function isCountryFromBrowserAvailable(): bool
{
$languageAvailable = static::getCountryIsoCodeFromHeader();
if ($languageAvailable === null) {
return false;
}
return Configuration::get('PS_DETECT_COUNTRY')
&& Validate::isLanguageIsoCode($languageAvailable);
}
/**
* @return string|null
*/
public static function getCountryIsoCodeFromHeader(): ?string
{
if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
return null;
}
preg_match(static::LANGUAGE_EXTRACTOR_REGEXP, $_SERVER['HTTP_ACCEPT_LANGUAGE'], $languages);
return $languages[0] ?? null;
}
/**
* Inserts a new element in array after a given index
*
* @param array $array Array to modify
* @param string $targetKey Key to search for
* @param string $newDataKey Key for an added data
* @param array $newDataArray New data to insert
*
* @return array
*/
public static function arrayInsertElementAfterKey(array $array, string $targetKey, string $newDataKey, array $newDataArray): array
{
if (array_key_exists($targetKey, $array)) {
$newArray = [];
foreach ($array as $k => $value) {
$newArray[$k] = $value;
if ($k === $targetKey) {
$newArray[$newDataKey] = $newDataArray;
}
}
return $newArray;
}
return $array;
}
}
/**
* Compare 2 prices to sort products.
*
* @param array{"price_tmp": float} $a
* @param array{"price_tmp": float} $b
*
* @return int
*/
function cmpPriceAsc($a, $b)
{
return $a['price_tmp'] <=> $b['price_tmp'];
}
/**
* @param array{"price_tmp": float} $a
* @param array{"price_tmp": float} $b
*
* @return int
*/
function cmpPriceDesc($a, $b)
{
return $b['price_tmp'] <=> $a['price_tmp'];
}
xxxxx1.0, XXX xxxx