/home/sharedstore/public_html/prestashop.sharedstore.ma/classes
NameSizeModeActions
assets/-0755rm
cache/-0755rm
checkout/-0755rm
container/-0755rm
controller/-0755rm
db/-0755rm
exception/-0755rm
form/-0755rm
helper/-0755rm
lang/-0755rm
log/-0755rm
module/-0755rm
order/-0755rm
pdf/-0755rm
product/-0755rm
proxy/-0755rm
range/-0755rm
shop/-0755rm
Smarty/-0755rm
stock/-0755rm
tax/-0755rm
tree/-0755rm
webservice/-0755rm
.htaccess1700644editdlrm
Access.php135730644editdlrm
Address.php230160644editdlrm
AddressChecksumCore.php18510644editdlrm
AddressFormat.php251250644editdlrm
Alias.php51060644editdlrm
Attachment.php115570644editdlrm
AttributeGroup.php134880644editdlrm
Carrier.php656660644editdlrm
Cart.php2211920644editdlrm
CartRule.php927650644editdlrm
Category.php891710644editdlrm
Chart.php41770644editdlrm
ChecksumInterface.php12460644editdlrm
CMS.php117250644editdlrm
CMSCategory.php241480644editdlrm
CMSRole.php18220644editdlrm
Combination.php180120644editdlrm
Configuration.php271560644editdlrm
ConfigurationKPI.php91260644editdlrm
ConfigurationTest.php123090644editdlrm
Connection.php102620644editdlrm
ConnectionsSource.php49260644editdlrm
Contact.php39540644editdlrm
Context.php183720644editdlrm
Cookie.php178060644editdlrm
Country.php167230644editdlrm
CSV.php31100644editdlrm
Currency.php371670644editdlrm
Curve.php29930644editdlrm
Customer.php535760644editdlrm
CustomerAddress.php12950644editdlrm
CustomerMessage.php58900644editdlrm
CustomerSession.php27180644editdlrm
CustomerThread.php98480644editdlrm
Customization.php137540644editdlrm
CustomizationField.php29150644editdlrm
DateRange.php25320644editdlrm
Delivery.php33170644editdlrm
Dispatcher.php488100644editdlrm
Employee.php240620644editdlrm
EmployeeSession.php27180644editdlrm
Feature.php116560644editdlrm
FeatureFlag.php32260644editdlrm
FeatureValue.php82490644editdlrm
FileUploader.php38200644editdlrm
Gender.php28760644editdlrm
Group.php153230644editdlrm
GroupReduction.php90250644editdlrm
Guest.php85290644editdlrm
Hook.php509090644editdlrm
Image.php324180644editdlrm
ImageManager.php325490644editdlrm
ImageType.php71550644editdlrm
index.php13720644editdlrm
Language.php635470644editdlrm
Link.php638140644editdlrm
LocalizationPack.php236040644editdlrm
Mail.php343120644editdlrm
Manufacturer.php217450644editdlrm
ManufacturerAddress.php13110644editdlrm
Media.php330210644editdlrm
Message.php66260644editdlrm
Meta.php185770644editdlrm
Notification.php84700644editdlrm
ObjectModel.php837730644editdlrm
Pack.php263320644editdlrm
Page.php45980644editdlrm
PaymentFree.php13620644editdlrm
PaymentModule.php611970644editdlrm
PhpEncryption.php32910644editdlrm
PhpEncryptionEngine.php39490644editdlrm
PrestaShopAutoload.php97990644editdlrm
PrestaShopBackup.php117020644editdlrm
PrestaShopCollection.php217900644editdlrm
PrestaShopLogger.php85230644editdlrm
Product.php3180560644editdlrm
ProductAssembler.php68950644editdlrm
ProductAttribute.php138080644editdlrm
ProductDownload.php102940644editdlrm
ProductPresenterFactory.php39830644editdlrm
ProductSale.php130000644editdlrm
ProductSupplier.php85370644editdlrm
Profile.php85230644editdlrm
QqUploadedFileForm.php53170644editdlrm
QqUploadedFileXhr.php58250644editdlrm
QuickAccess.php64020644editdlrm
RequestSql.php214530644editdlrm
Risk.php24190644editdlrm
Search.php592290644editdlrm
SearchEngine.php28790644editdlrm
SpecificPrice.php332120644editdlrm
SpecificPriceRule.php143740644editdlrm
State.php79560644editdlrm
Store.php63940644editdlrm
Supplier.php183390644editdlrm
SupplierAddress.php12990644editdlrm
Tab.php222230644editdlrm
Tag.php135580644editdlrm
Tools.php1409030644editdlrm
Translate.php120590644editdlrm
TranslatedConfiguration.php47920644editdlrm
Upgrader.php118590644editdlrm
Uploader.php111040644editdlrm
Validate.php398010644editdlrm
ValidateConstraintTranslator.php26110644editdlrm
WarehouseAddress.php13020644editdlrm
Zone.php35000644editdlrm
Edit: /home/sharedstore/public_html/prestashop.sharedstore.ma/classes/Tools.php (140903B)
* @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\IpUtils; 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 current 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 '
'; } /** * 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 .= ''; } } 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) { //TODO use Validate::isUrl instead when it will be less permissive and also allows schemes to be validated $scheme = parse_url($url, PHP_URL_SCHEME); // Check if the scheme is allowed if (!in_array(strtolower($scheme), ['http', 'https'], true)) { return false; } $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*(.*)#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, "\nSecFilterEngine Off\nSecFilterScanPOST Off\n\n\n"); } // RewriteEngine fwrite($write_fd, "\n"); // Ensure HTTP_MOD_REWRITE variable is set in environment fwrite($write_fd, "\n"); fwrite($write_fd, "SetEnv HTTP_MOD_REWRITE On\n"); fwrite($write_fd, "\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, "\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, " Header set Access-Control-Allow-Origin \"*\" \n\n"); fwrite($write_fd, ' # Apache 2.2 Order deny,allow Deny from all # Apache 2.4 Require all denied '); // Cache control if ($cache_control) { $cache_control = " 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\" Header unset Etag FileETag none 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 \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 ' * @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 ' . $callee['function'] . '() is deprecated in ' . $callee['file'] . ' on line ' . $callee['line'] . '
'; 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 ' . $parameter . ' in function ' . (isset($callee['function']) ? $callee['function'] : '') . '() is deprecated in ' . $callee['file'] . ' on line ' . (isset($callee['line']) ? $callee['line'] : '(undefined)') . '
'; $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 ' . $callee['file'] . ' is deprecated
'; $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
. * * @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], '
', $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', 'autoplay' => 'Bool', 'loop' => 'Bool', 'muted' => 'Bool', 'playsinline' => '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 = ' '; $full_path .= $edit . ($n < $n_categories ? '' : '') . (!empty($highlight) ? str_ireplace($highlight, '' . htmlentities($highlight, ENT_NOQUOTES, 'UTF-8') . '', $category['name']) : $category['name']) . ($n < $n_categories ? '' : '') . (($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, '' . $highlight . '', CMSCategory::hideCMSCategoryPosition($category->name)) : CMSCategory::hideCMSCategoryPosition($category->name); $edit = ' '; if ($category->id == 1) { $edit = '
  • '; } $path = $edit . '
  • ' . $name . '
  • > ' . $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; } /** * Checks if the current visitor is allowed to view the page even if maintenace mode is on, either via IP whitelist or being logged in in backoffice. */ public static function isAllowedToBypassMaintenance() { $is_admin = (int) (new Cookie('psAdmin'))->id_employee; $maintenance_allow_admins = (bool) Configuration::get('PS_MAINTENANCE_ALLOW_ADMINS'); if ($is_admin && $maintenance_allow_admins) { return true; } $allowed_ips = array_map('trim', explode(',', Configuration::get('PS_MAINTENANCE_IP'))); if (IpUtils::checkIp(Tools::getRemoteAddr(), $allowed_ips)) { return true; } return false; } } /** * 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']; }