4033 lines
132 KiB
PHP
4033 lines
132 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* For the full copyright and license information, please view the
|
||
|
|
* docs/licenses/LICENSE.txt file that was distributed with this source code.
|
||
|
|
*/
|
||
|
|
|
||
|
|
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\Locale\Repository as LocaleRepository;
|
||
|
|
use PrestaShop\PrestaShop\Core\Localization\LocaleInterface;
|
||
|
|
use PrestaShop\PrestaShop\Core\Security\Hashing;
|
||
|
|
use PrestaShop\PrestaShop\Core\Security\OpenSsl\OpenSSL;
|
||
|
|
use PrestaShop\PrestaShop\Core\Security\PasswordGenerator;
|
||
|
|
use PrestaShop\PrestaShop\Core\Util\String\StringModifier;
|
||
|
|
use PrestaShopBundle\Security\Admin\UserTokenManager;
|
||
|
|
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 string|false 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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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 user to another page (using header Location)
|
||
|
|
*
|
||
|
|
* Warning: uses exit
|
||
|
|
*
|
||
|
|
* @param string $url Desired URL
|
||
|
|
*/
|
||
|
|
public static function redirectAdmin($url)
|
||
|
|
{
|
||
|
|
header('Location: ' . self::sanitizeAdminUrl($url));
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sanitize an url used in the Admin (back office context) to make sure it is correctly written to be
|
||
|
|
* used for redirection. If the provided URL is not absolute the shop url is prepended, if the admin
|
||
|
|
* folder is absent it is also prepended. Absolute urls are left untouched.
|
||
|
|
*
|
||
|
|
* @param string $url
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function sanitizeAdminUrl(string $url): string
|
||
|
|
{
|
||
|
|
$link = Context::getContext()->link;
|
||
|
|
if (!preg_match('@^https?://@i', $url) && $link) {
|
||
|
|
$baseUrl = rtrim($link->getAdminBaseLink(), '/');
|
||
|
|
|
||
|
|
// Removes physical_uri from baseUrl to avoid duplicate admin path
|
||
|
|
$physicalUri = Context::getContext()->shop->physical_uri;
|
||
|
|
if (str_starts_with($url, $physicalUri)) {
|
||
|
|
$url = substr($url, strlen($physicalUri));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!str_contains($url, basename(_PS_ADMIN_DIR_))) {
|
||
|
|
$baseUrl .= '/' . basename(_PS_ADMIN_DIR_);
|
||
|
|
}
|
||
|
|
|
||
|
|
$url = $baseUrl . '/' . trim($url, '/');
|
||
|
|
}
|
||
|
|
|
||
|
|
return $url;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the available protocol for the current shop in use
|
||
|
|
* SSL if Configuration is set on and available for the server.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function getShopProtocol()
|
||
|
|
{
|
||
|
|
$protocol = (Configuration::get('PS_SSL_ENABLED') || Tools::usingSecureMode()) ? 'https://' : 'http://';
|
||
|
|
|
||
|
|
return $protocol;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the set protocol according to configuration (http[s]).
|
||
|
|
*
|
||
|
|
* @param bool $use_ssl true if require ssl
|
||
|
|
*
|
||
|
|
* @return string (http|https)
|
||
|
|
*/
|
||
|
|
public static function getProtocol($use_ssl = null)
|
||
|
|
{
|
||
|
|
return null !== $use_ssl && $use_ssl ? 'https://' : 'http://';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the <b>current</b> host used, with the protocol (http or https) if $http is true
|
||
|
|
* This function should not be used to choose http or https domain name.
|
||
|
|
* Use Tools::getShopDomain() or Tools::getShopDomainSsl instead.
|
||
|
|
*
|
||
|
|
* @param bool $http
|
||
|
|
* @param bool $entities
|
||
|
|
* @param bool $ignore_port
|
||
|
|
*
|
||
|
|
* @return string host
|
||
|
|
*/
|
||
|
|
public static function getHttpHost($http = false, $entities = false, $ignore_port = false)
|
||
|
|
{
|
||
|
|
$httpHost = '';
|
||
|
|
if (array_key_exists('HTTP_HOST', $_SERVER)) {
|
||
|
|
$httpHost = $_SERVER['HTTP_HOST'];
|
||
|
|
}
|
||
|
|
|
||
|
|
$host = (!empty($_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 (!empty($_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 (!empty($_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]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* This method was named "Change language in cookie while clicking on a flag.",
|
||
|
|
* but as of 25.12.2025, it does not really work at all. Language detection will
|
||
|
|
* never work because the language is exclusively determined by the URL, the isolang
|
||
|
|
* is always set and the HTTP_ACCEPT_LANGUAGE is not parsed properly anyway.
|
||
|
|
*
|
||
|
|
* @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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detects proper id_language by the isolang parameter in the request and assigns
|
||
|
|
* it to the context and cookie. The method naming is a bit confusing as it does
|
||
|
|
* not switch anything. Language is exclusively determined by the URL.
|
||
|
|
*
|
||
|
|
* @todo - The behavior is a bit non stable, it should probably throw exceptions
|
||
|
|
* or somehow notify that nonsense is being present. If you for example pass a non
|
||
|
|
* existent language in the URL and you get "zz" in isolang, you will end up with:
|
||
|
|
* $_GET['isolang'] = zz
|
||
|
|
* $_GET['id_lang'] = null
|
||
|
|
* $context->cookie->id_lang = default language id from config.inc.php
|
||
|
|
* $context->language = default language from config.inc.php
|
||
|
|
*
|
||
|
|
* @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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* This takes the isolang parameter from $_GET, converts it to an id_lang
|
||
|
|
* and assigns it into the GET. The isolang in the request always set by the Dispatcher.
|
||
|
|
* Either to a language code from the URL or the default language.
|
||
|
|
*/
|
||
|
|
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 we got a sensible language ID from the request, we will set it into the cookie
|
||
|
|
* and set it as the context language. There is already a default language set in the
|
||
|
|
* context from config.inc.php.
|
||
|
|
*
|
||
|
|
* @todo Please note that the cookie language ID has no effect as the language
|
||
|
|
* is exclusively determined by the URL. It may only by accessed by some really old
|
||
|
|
* codebase to read the language ID, but they should be updated to use the context
|
||
|
|
* language directly.
|
||
|
|
*/
|
||
|
|
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 current locale
|
||
|
|
*
|
||
|
|
* @param Context $context
|
||
|
|
*
|
||
|
|
* @return LocaleInterface
|
||
|
|
*
|
||
|
|
* @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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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, bool $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)) {
|
||
|
|
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');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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) {
|
||
|
|
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
|
||
|
|
*
|
||
|
|
* @deprecated since 9.0.0 - Please throw an exception directly. It will be handled better and logged
|
||
|
|
* in all enviroments, to both PHP and our logs. This method will be eventually removed
|
||
|
|
*/
|
||
|
|
public static function displayError($errorMessage = null, $htmlentities = null, ?Context $context = null)
|
||
|
|
{
|
||
|
|
header('HTTP/1.1 500 Internal Server Error', true, 500);
|
||
|
|
if (null !== $htmlentities) {
|
||
|
|
self::displayParameterAsDeprecated('htmlentities');
|
||
|
|
}
|
||
|
|
if (null !== $context) {
|
||
|
|
self::displayParameterAsDeprecated('context');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (null === $errorMessage) {
|
||
|
|
$errorMessage = Context::getContext()
|
||
|
|
->getTranslator()
|
||
|
|
->trans('Fatal error', [], 'Admin.Notifications.Error');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_PS_MODE_DEV_) {
|
||
|
|
throw new PrestaShopException($errorMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* @phpstan-ignore-next-line */
|
||
|
|
return $errorMessage;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Display an error with detailed object.
|
||
|
|
*
|
||
|
|
* @param mixed $object
|
||
|
|
* @param bool $kill
|
||
|
|
*
|
||
|
|
* @return mixed
|
||
|
|
*/
|
||
|
|
public static function dieObject($object, $kill = true)
|
||
|
|
{
|
||
|
|
dump($object);
|
||
|
|
|
||
|
|
if ($kill) {
|
||
|
|
die('END');
|
||
|
|
}
|
||
|
|
|
||
|
|
return $object;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function debug_backtrace($start = 0, $limit = null)
|
||
|
|
{
|
||
|
|
$backtrace = debug_backtrace();
|
||
|
|
array_shift($backtrace);
|
||
|
|
for ($i = 0; $i < $start; ++$i) {
|
||
|
|
array_shift($backtrace);
|
||
|
|
}
|
||
|
|
|
||
|
|
echo '
|
||
|
|
<div style="margin:10px;padding:10px;border:1px solid #666666">
|
||
|
|
<ul>';
|
||
|
|
$i = 0;
|
||
|
|
foreach ($backtrace as $id => $trace) {
|
||
|
|
if ((int) $limit && (++$i > $limit)) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
$relative_file = (isset($trace['file'])) ? 'in /' . ltrim(str_replace([_PS_ROOT_DIR_, '\\'], ['', '/'], $trace['file']), '/') : '';
|
||
|
|
$current_line = (isset($trace['line'])) ? ':' . $trace['line'] : '';
|
||
|
|
|
||
|
|
echo '<li>
|
||
|
|
<b>' . ((isset($trace['class'])) ? $trace['class'] : '') . ((isset($trace['type'])) ? $trace['type'] : '') . $trace['function'] . '</b>
|
||
|
|
' . $relative_file . $current_line . '
|
||
|
|
</li>';
|
||
|
|
}
|
||
|
|
echo '</ul>
|
||
|
|
</div>';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Prints object information into error log.
|
||
|
|
*
|
||
|
|
* @see error_log()
|
||
|
|
* @deprecated since 9.0.0 and will be removed in 10.0.0. Use error_log directly.
|
||
|
|
* If you have an object or array, you can stringify it for example by print_r($object, true).
|
||
|
|
*
|
||
|
|
* @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 has
|
||
|
|
*
|
||
|
|
* @return string Hashed password
|
||
|
|
*/
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
public static function hashIV($data)
|
||
|
|
{
|
||
|
|
return (new Hashing())->hash($data, _COOKIE_IV_);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get token to prevent CSRF.
|
||
|
|
*
|
||
|
|
* @param bool|string $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)
|
||
|
|
{
|
||
|
|
$container = SymfonyContainer::getInstance();
|
||
|
|
if (null !== $container) {
|
||
|
|
/** @var UserTokenManager $userTokenManager */
|
||
|
|
$userTokenManager = $container->get(UserTokenManager::class);
|
||
|
|
|
||
|
|
return $userTokenManager->getSymfonyToken();
|
||
|
|
}
|
||
|
|
|
||
|
|
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
|
||
|
|
*
|
||
|
|
* @return bool|string
|
||
|
|
*/
|
||
|
|
public static function getAdminTokenLiteSmarty($params)
|
||
|
|
{
|
||
|
|
$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 a friendly url made from the provided string
|
||
|
|
* If the mbstring library is available, the output is the same as the js function of the same name.
|
||
|
|
*
|
||
|
|
* @param string $str
|
||
|
|
*
|
||
|
|
* @return string|bool
|
||
|
|
*/
|
||
|
|
public static function str2url($str)
|
||
|
|
{
|
||
|
|
static $allow_accented_chars = null;
|
||
|
|
|
||
|
|
if ($allow_accented_chars === null) {
|
||
|
|
$allow_accented_chars = Configuration::get('PS_ALLOW_ACCENTED_CHARS_URL');
|
||
|
|
}
|
||
|
|
|
||
|
|
return self::getStringModifier()->str2url((string) $str, $allow_accented_chars);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Replace all accented chars by their equivalent non accented chars.
|
||
|
|
*
|
||
|
|
* @param string $str
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function replaceAccentedChars($str)
|
||
|
|
{
|
||
|
|
return self::getStringModifier()->replaceAccentedChars($str);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reuse the StringModifier for performance reasons.
|
||
|
|
*
|
||
|
|
* @return StringModifier
|
||
|
|
*/
|
||
|
|
private static function getStringModifier()
|
||
|
|
{
|
||
|
|
if (!isset(self::$_string_modifier)) {
|
||
|
|
self::$_string_modifier = new StringModifier();
|
||
|
|
}
|
||
|
|
|
||
|
|
return self::$_string_modifier;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Truncate strings.
|
||
|
|
*
|
||
|
|
* @param string $str
|
||
|
|
* @param int $max_length Max length
|
||
|
|
* @param string $suffix Suffix optional
|
||
|
|
*
|
||
|
|
* @return string $str truncated
|
||
|
|
*/
|
||
|
|
/* CAUTION : Use it only on module hookEvents.
|
||
|
|
** For other purposes use the smarty function instead */
|
||
|
|
public static function truncate($str, $max_length, $suffix = '...')
|
||
|
|
{
|
||
|
|
if (Tools::strlen($str) <= $max_length) {
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
$str = utf8_decode($str);
|
||
|
|
|
||
|
|
return utf8_encode(substr($str, 0, $max_length - Tools::strlen($suffix)) . $suffix);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copied from CakePHP String utility file */
|
||
|
|
public static function truncateString($text, $length = 120, $options = [])
|
||
|
|
{
|
||
|
|
$default = [
|
||
|
|
'ellipsis' => '...', 'exact' => true, 'html' => true,
|
||
|
|
];
|
||
|
|
|
||
|
|
$options = array_merge($default, $options);
|
||
|
|
extract($options);
|
||
|
|
if (isset($html)) {
|
||
|
|
/* @var bool $exact */
|
||
|
|
/* @var bool $html */
|
||
|
|
if (Tools::strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
|
||
|
|
return $text;
|
||
|
|
}
|
||
|
|
|
||
|
|
$total_length = Tools::strlen(strip_tags($ellipsis ?? ''));
|
||
|
|
$open_tags = [];
|
||
|
|
$truncate = '';
|
||
|
|
preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
|
||
|
|
|
||
|
|
foreach ($tags as $tag) {
|
||
|
|
if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
|
||
|
|
if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
|
||
|
|
array_unshift($open_tags, $tag[2]);
|
||
|
|
} elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $close_tag)) {
|
||
|
|
$pos = array_search($close_tag[1], $open_tags);
|
||
|
|
if ($pos !== false) {
|
||
|
|
array_splice($open_tags, $pos, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$truncate .= $tag[1];
|
||
|
|
$content_length = Tools::strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3]));
|
||
|
|
|
||
|
|
if ($content_length + $total_length > $length) {
|
||
|
|
$left = $length - $total_length;
|
||
|
|
$entities_length = 0;
|
||
|
|
|
||
|
|
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) {
|
||
|
|
foreach ($entities[0] as $entity) {
|
||
|
|
if ($entity[1] + 1 - $entities_length <= $left) {
|
||
|
|
--$left;
|
||
|
|
$entities_length += Tools::strlen($entity[0]);
|
||
|
|
} else {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$truncate .= Tools::substr($tag[3], 0, $left + $entities_length);
|
||
|
|
|
||
|
|
break;
|
||
|
|
} else {
|
||
|
|
$truncate .= $tag[3];
|
||
|
|
$total_length += $content_length;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($total_length >= $length) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (Tools::strlen($text) <= $length) {
|
||
|
|
return $text;
|
||
|
|
}
|
||
|
|
|
||
|
|
$truncate = Tools::substr($text, 0, $length - Tools::strlen($ellipsis ?? ''));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!isset($exact) || !$exact) {
|
||
|
|
$spacepos = Tools::strrpos($truncate ?? '', ' ');
|
||
|
|
if (isset($html)) {
|
||
|
|
$truncate_check = Tools::substr($truncate ?? '', 0, $spacepos);
|
||
|
|
$last_open_tag = Tools::strrpos($truncate_check, '<');
|
||
|
|
$last_close_tag = Tools::strrpos($truncate_check, '>');
|
||
|
|
|
||
|
|
if ($last_open_tag > $last_close_tag) {
|
||
|
|
preg_match_all('/<[\w]+[^>]*>/s', $truncate ?? '', $last_tag_matches);
|
||
|
|
$last_tag = array_pop($last_tag_matches[0]);
|
||
|
|
$spacepos = Tools::strrpos($truncate, $last_tag) + Tools::strlen($last_tag);
|
||
|
|
}
|
||
|
|
|
||
|
|
$bits = Tools::substr($truncate ?? '', $spacepos);
|
||
|
|
preg_match_all('/<\/([a-z]+)>/', $bits, $dropped_tags, PREG_SET_ORDER);
|
||
|
|
|
||
|
|
if (!empty($dropped_tags)) {
|
||
|
|
if (!empty($open_tags)) {
|
||
|
|
foreach ($dropped_tags as $closing_tag) {
|
||
|
|
if (!in_array($closing_tag[1], $open_tags)) {
|
||
|
|
array_unshift($open_tags, $closing_tag[1]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
foreach ($dropped_tags as $closing_tag) {
|
||
|
|
$open_tags[] = $closing_tag[1];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$truncate = Tools::substr($truncate, 0, $spacepos);
|
||
|
|
}
|
||
|
|
|
||
|
|
$truncate .= ($ellipsis ?? '');
|
||
|
|
|
||
|
|
if (isset($html)) {
|
||
|
|
$open_tags = $open_tags ?? [];
|
||
|
|
foreach ($open_tags as $tag) {
|
||
|
|
$truncate .= '</' . $tag . '>';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $truncate;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function normalizeDirectory($directory)
|
||
|
|
{
|
||
|
|
return rtrim($directory, '/\\') . DIRECTORY_SEPARATOR;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate date form.
|
||
|
|
*
|
||
|
|
* @return array $tab html data with 3 cells :['days'], ['months'], ['years']
|
||
|
|
*/
|
||
|
|
public static function dateYears()
|
||
|
|
{
|
||
|
|
$tab = [];
|
||
|
|
for ($i = date('Y'); $i >= 1900; --$i) {
|
||
|
|
$tab[] = $i;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $tab;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function dateDays()
|
||
|
|
{
|
||
|
|
$tab = [];
|
||
|
|
for ($i = 1; $i != 32; ++$i) {
|
||
|
|
$tab[] = $i;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $tab;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function dateMonths()
|
||
|
|
{
|
||
|
|
$tab = [];
|
||
|
|
for ($i = 1; $i != 13; ++$i) {
|
||
|
|
$tab[$i] = date('F', mktime(0, 0, 0, $i, (int) date('m'), (int) date('Y')));
|
||
|
|
}
|
||
|
|
|
||
|
|
return $tab;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function hourGenerate($hours, $minutes, $seconds)
|
||
|
|
{
|
||
|
|
return implode(':', [$hours, $minutes, $seconds]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function dateFrom($date)
|
||
|
|
{
|
||
|
|
$tab = explode(' ', $date);
|
||
|
|
if (!isset($tab[1])) {
|
||
|
|
$date .= ' ' . Tools::hourGenerate(0, 0, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $date;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function dateTo($date)
|
||
|
|
{
|
||
|
|
$tab = explode(' ', $date);
|
||
|
|
if (!isset($tab[1])) {
|
||
|
|
$date .= ' ' . Tools::hourGenerate(23, 59, 59);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $date;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function strtolower($str)
|
||
|
|
{
|
||
|
|
if (null === $str || is_array($str)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return mb_strtolower($str, 'UTF-8');
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function strlen($str, $encoding = 'UTF-8')
|
||
|
|
{
|
||
|
|
if (null === $str || is_array($str)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$str = html_entity_decode($str, ENT_COMPAT, 'UTF-8');
|
||
|
|
|
||
|
|
return mb_strlen($str, $encoding);
|
||
|
|
}
|
||
|
|
|
||
|
|
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.
|
||
|
|
*
|
||
|
|
* Warning - this method accepts our own PS rounding constants with different integer values.
|
||
|
|
*
|
||
|
|
* @param float $value
|
||
|
|
* @param int $precision
|
||
|
|
* @param int<0,5>|null $round_mode
|
||
|
|
*
|
||
|
|
* @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:
|
||
|
|
case PS_ROUND_HALF_UP:
|
||
|
|
default:
|
||
|
|
// PHP rounding mode is Prestashop rounding mode - 1, see config/defines.inc.php
|
||
|
|
/* @phpstan-ignore-next-line */
|
||
|
|
return round($value, $precision, $round_mode - 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* This method is a wrapper for PHP round method. It doesn't do much now, but it
|
||
|
|
* was needed in the past, because PHP did not support rounding modes like it does
|
||
|
|
* not. There was a huge logic here.
|
||
|
|
*
|
||
|
|
* Warning - this method accepts our own PS rounding methods with different integer values.
|
||
|
|
*
|
||
|
|
* @deprecated since 9.0.0 and will be removed in 10.0.0. Use ps_round or round directly.
|
||
|
|
*
|
||
|
|
* @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)
|
||
|
|
{
|
||
|
|
return Tools::ps_round($value, $places, $mode);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @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 string|false
|
||
|
|
*
|
||
|
|
* @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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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 string|false false or the file 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)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function parserSQL($sql)
|
||
|
|
{
|
||
|
|
if (strlen($sql) > 0) {
|
||
|
|
$parser = new PHPSQLParser($sql);
|
||
|
|
|
||
|
|
return $parser->parsed;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
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';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if option "Apache optimization" was enabled in performance settings
|
||
|
|
if (null === $cache_control) {
|
||
|
|
$cache_control = (int) Configuration::get('PS_HTACCESS_CACHE_CONTROL');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (null === $disable_multiviews) {
|
||
|
|
$disable_multiviews = (bool) Configuration::get('PS_HTACCESS_DISABLE_MULTIVIEWS');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($disable_modsec === null) {
|
||
|
|
$disable_modsec = (int) Configuration::get('PS_HTACCESS_DISABLE_MODSEC');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check current content of .htaccess and save all code outside of prestashop comments
|
||
|
|
$specific_before = $specific_after = '';
|
||
|
|
if (file_exists($path)) {
|
||
|
|
$content = file_get_contents($path);
|
||
|
|
if (preg_match('#^(.*)\# ~~start~~.*\# ~~end~~[^\n]*(.*)$#s', $content, $m)) {
|
||
|
|
$specific_before = $m[1];
|
||
|
|
$specific_after = $m[2];
|
||
|
|
} else {
|
||
|
|
// For retrocompatibility
|
||
|
|
if (preg_match('#\# http://www\.prestashop\.com - http://www\.prestashop\.com/forums\s*(.*)<IfModule mod_rewrite\.c>#si', $content, $m)) {
|
||
|
|
$specific_before = $m[1];
|
||
|
|
} else {
|
||
|
|
$specific_before = $content;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Write .htaccess data
|
||
|
|
if (!$write_fd = @fopen($path, 'wb')) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if ($specific_before) {
|
||
|
|
fwrite($write_fd, trim($specific_before) . "\n\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
$domains = self::getDomains();
|
||
|
|
|
||
|
|
// Write data in .htaccess file
|
||
|
|
fwrite($write_fd, "# ~~start~~ Do not remove this comment, Prestashop will keep automatically the code outside this comment when .htaccess will be generated again\n");
|
||
|
|
fwrite($write_fd, "# .htaccess automaticaly generated by PrestaShop e-commerce open-source solution\n");
|
||
|
|
fwrite($write_fd, "# https://www.prestashop-project.org\n\n");
|
||
|
|
|
||
|
|
fwrite($write_fd, "# Prevent directory listings\n");
|
||
|
|
fwrite($write_fd, "Options -Indexes\n\n");
|
||
|
|
|
||
|
|
if ($disable_modsec) {
|
||
|
|
fwrite($write_fd, "<IfModule mod_security.c>\nSecFilterEngine Off\nSecFilterScanPOST Off\n</IfModule>\n\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
// RewriteEngine
|
||
|
|
fwrite($write_fd, "<IfModule mod_rewrite.c>\n");
|
||
|
|
|
||
|
|
// Ensure HTTP_MOD_REWRITE variable is set in environment
|
||
|
|
fwrite($write_fd, "<IfModule mod_env.c>\n");
|
||
|
|
fwrite($write_fd, "SetEnv HTTP_MOD_REWRITE On\n");
|
||
|
|
fwrite($write_fd, "</IfModule>\n\n");
|
||
|
|
|
||
|
|
// Disable multiviews ?
|
||
|
|
if ($disable_multiviews) {
|
||
|
|
fwrite($write_fd, "\n# Disable Multiviews\nOptions -Multiviews\n\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
fwrite($write_fd, "RewriteEngine on\n");
|
||
|
|
|
||
|
|
if (
|
||
|
|
!$medias
|
||
|
|
&& Configuration::getMultiShopValues('PS_MEDIA_SERVER_1')
|
||
|
|
&& Configuration::getMultiShopValues('PS_MEDIA_SERVER_2')
|
||
|
|
&& Configuration::getMultiShopValues('PS_MEDIA_SERVER_3')
|
||
|
|
) {
|
||
|
|
$medias = [
|
||
|
|
Configuration::getMultiShopValues('PS_MEDIA_SERVER_1'),
|
||
|
|
Configuration::getMultiShopValues('PS_MEDIA_SERVER_2'),
|
||
|
|
Configuration::getMultiShopValues('PS_MEDIA_SERVER_3'),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
$media_domains = '';
|
||
|
|
foreach ($medias as $media) {
|
||
|
|
foreach ($media as $media_url) {
|
||
|
|
if ($media_url) {
|
||
|
|
$media_domains .= 'RewriteCond %{HTTP_HOST} ^' . $media_url . '$ [OR]' . PHP_EOL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Propagate authorization header to PHP that is normally removed by Apache.
|
||
|
|
* In the past, it was passed only if PS_WEBSERVICE_CGI_HOST was enabled,
|
||
|
|
* but today, there is no reason not to pass it.
|
||
|
|
*/
|
||
|
|
fwrite($write_fd, "# Sets the HTTP_AUTHORIZATION header removed by apache\n");
|
||
|
|
fwrite($write_fd, "RewriteCond %{HTTP:Authorization} .\n");
|
||
|
|
fwrite($write_fd, "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\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, "# Rewrites for product images (support up to < 10 million images)\n");
|
||
|
|
|
||
|
|
// Rewrite product images < 10 millions
|
||
|
|
$path_components = [];
|
||
|
|
for ($i = 1; $i <= 7; ++$i) {
|
||
|
|
$path_components[] = '$' . ($i + 1); // paths start on 2
|
||
|
|
$path_images = 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_images . '/$1$' . ($i + 2) . " [L]\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
fwrite($write_fd, "# Rewrites for category images\n");
|
||
|
|
fwrite($write_fd, $media_domains);
|
||
|
|
fwrite($write_fd, $domain_rewrite_cond);
|
||
|
|
fwrite($write_fd, 'RewriteRule ^c/([\d]+)(|_thumb)(\-[\.*\w-]*)/.+(\.(?:jpe?g|webp|png|avif))$ %{ENV:REWRITEBASE}img/c/$1$2$3$4 [L]' . PHP_EOL);
|
||
|
|
fwrite($write_fd, $media_domains);
|
||
|
|
fwrite($write_fd, $domain_rewrite_cond);
|
||
|
|
fwrite($write_fd, 'RewriteRule ^c/([a-zA-Z_-]+)(|_thumb)(-[\d]+)?/.+(\.(?:jpe?g|webp|png|avif))$ %{ENV:REWRITEBASE}img/c/$1$2$3$4 [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# Send all other traffic to dispatcher\n");
|
||
|
|
fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -s [OR]\n");
|
||
|
|
fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -l [OR]\n");
|
||
|
|
fwrite($write_fd, "RewriteCond %{REQUEST_FILENAME} -d\n");
|
||
|
|
if (Shop::isFeatureActive()) {
|
||
|
|
fwrite($write_fd, $domain_rewrite_cond);
|
||
|
|
}
|
||
|
|
fwrite($write_fd, "RewriteRule ^.*$ - [NC,L]\n");
|
||
|
|
if (Shop::isFeatureActive()) {
|
||
|
|
fwrite($write_fd, $domain_rewrite_cond);
|
||
|
|
}
|
||
|
|
fwrite($write_fd, "RewriteRule ^.*\$ %{ENV:REWRITEBASE}index.php [NC,L]\n");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fwrite($write_fd, "</IfModule>\n\n");
|
||
|
|
|
||
|
|
// Serve fonts properly and avoid CORS issues
|
||
|
|
fwrite($write_fd, "# Serve fonts properly and avoid CORS issues\n");
|
||
|
|
fwrite($write_fd, "AddType application/vnd.ms-fontobject .eot\n");
|
||
|
|
fwrite($write_fd, "AddType font/ttf .ttf\n");
|
||
|
|
fwrite($write_fd, "AddType font/otf .otf\n");
|
||
|
|
fwrite($write_fd, "AddType application/font-woff .woff\n");
|
||
|
|
fwrite($write_fd, "AddType font/woff2 .woff2\n");
|
||
|
|
fwrite($write_fd, "<IfModule mod_headers.c>
|
||
|
|
<FilesMatch \"\.(ttf|ttc|otf|eot|woff|woff2|svg)$\">
|
||
|
|
Header set Access-Control-Allow-Origin \"*\"
|
||
|
|
</FilesMatch>
|
||
|
|
</IfModule>\n\n");
|
||
|
|
|
||
|
|
// Protect sensitive files from being accessed directly
|
||
|
|
fwrite($write_fd, '# Protect sensitive files from being accessed directly
|
||
|
|
<FilesMatch "^(composer\.lock|\.git.*|\.env.*)$">
|
||
|
|
# Apache 2.2
|
||
|
|
<IfModule !mod_authz_core.c>
|
||
|
|
Order deny,allow
|
||
|
|
Deny from all
|
||
|
|
</IfModule>
|
||
|
|
|
||
|
|
# Apache 2.4
|
||
|
|
<IfModule mod_authz_core.c>
|
||
|
|
Require all denied
|
||
|
|
</IfModule>
|
||
|
|
</FilesMatch>
|
||
|
|
|
||
|
|
');
|
||
|
|
// If option "Apache optimization" was enabled in performance settings, setup cache control
|
||
|
|
if ($cache_control) {
|
||
|
|
$cache_control = "# Cache control for static files
|
||
|
|
<IfModule mod_expires.c>
|
||
|
|
ExpiresActive On
|
||
|
|
AddType image/webp .webp
|
||
|
|
ExpiresByType image/webp \"access plus 1 month\"
|
||
|
|
ExpiresByType image/avif \"access plus 1 month\"
|
||
|
|
ExpiresByType image/gif \"access plus 1 month\"
|
||
|
|
ExpiresByType image/jpeg \"access plus 1 month\"
|
||
|
|
ExpiresByType image/png \"access plus 1 month\"
|
||
|
|
ExpiresByType text/css \"access plus 1 week\"
|
||
|
|
ExpiresByType text/javascript \"access plus 1 week\"
|
||
|
|
ExpiresByType application/javascript \"access plus 1 week\"
|
||
|
|
ExpiresByType application/x-javascript \"access plus 1 week\"
|
||
|
|
ExpiresByType image/x-icon \"access plus 1 year\"
|
||
|
|
ExpiresByType image/svg+xml \"access plus 1 year\"
|
||
|
|
ExpiresByType image/vnd.microsoft.icon \"access plus 1 year\"
|
||
|
|
ExpiresByType application/font-woff \"access plus 1 year\"
|
||
|
|
ExpiresByType application/x-font-woff \"access plus 1 year\"
|
||
|
|
ExpiresByType font/woff2 \"access plus 1 year\"
|
||
|
|
ExpiresByType application/vnd.ms-fontobject \"access plus 1 year\"
|
||
|
|
ExpiresByType font/opentype \"access plus 1 year\"
|
||
|
|
ExpiresByType font/ttf \"access plus 1 year\"
|
||
|
|
ExpiresByType font/otf \"access plus 1 year\"
|
||
|
|
ExpiresByType application/x-font-ttf \"access plus 1 year\"
|
||
|
|
ExpiresByType application/x-font-otf \"access plus 1 year\"
|
||
|
|
</IfModule>
|
||
|
|
|
||
|
|
# Remove Etag header as this can cause issues with caching
|
||
|
|
<IfModule mod_headers.c>
|
||
|
|
Header unset Etag
|
||
|
|
</IfModule>
|
||
|
|
FileETag none
|
||
|
|
|
||
|
|
# Enable GZIP compression for text, HTML, JavaScript, CSS, fonts and SVG files
|
||
|
|
<IfModule mod_deflate.c>
|
||
|
|
<IfModule mod_filter.c>
|
||
|
|
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/x-javascript font/ttf application/x-font-ttf font/otf application/x-font-otf font/opentype image/svg+xml
|
||
|
|
</IfModule>
|
||
|
|
</IfModule>\n\n";
|
||
|
|
fwrite($write_fd, $cache_control);
|
||
|
|
}
|
||
|
|
|
||
|
|
// In case the user hasn't rewrite mod enabled
|
||
|
|
fwrite($write_fd, "#If rewrite mod isn't enabled\n");
|
||
|
|
|
||
|
|
// Do not remove ($domains is already iterated upper)
|
||
|
|
reset($domains);
|
||
|
|
$domain = current($domains);
|
||
|
|
fwrite($write_fd, 'ErrorDocument 404 ' . $domain[0]['physical'] . "index.php?controller=404\n\n");
|
||
|
|
|
||
|
|
fwrite($write_fd, '# ~~end~~ Do not remove this comment, Prestashop will keep automatically the code outside this comment when .htaccess will be generated again');
|
||
|
|
if ($specific_after) {
|
||
|
|
fwrite($write_fd, "\n\n" . trim($specific_after));
|
||
|
|
}
|
||
|
|
fclose($write_fd);
|
||
|
|
|
||
|
|
if (!defined('PS_INSTALLATION_IN_PROGRESS')) {
|
||
|
|
Hook::exec('actionHtaccessCreate', ['path' => $path]);
|
||
|
|
}
|
||
|
|
|
||
|
|
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();
|
||
|
|
|
||
|
|
// Allow modules to modify the contents of the file
|
||
|
|
if (true === $executeHook) {
|
||
|
|
Hook::exec('actionAdminMetaBeforeWriteRobotsFile', [
|
||
|
|
'rb_data' => &$robots_content,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// File header
|
||
|
|
fwrite($write_fd, "# robots.txt automatically generated by PrestaShop e-commerce open-source solution\n");
|
||
|
|
fwrite($write_fd, "# https://www.prestashop-project.org\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\n");
|
||
|
|
|
||
|
|
// User-Agent, we match everything
|
||
|
|
fwrite($write_fd, "User-agent: *\n");
|
||
|
|
|
||
|
|
// Allow directives for modules
|
||
|
|
if (count($robots_content['Allow'])) {
|
||
|
|
fwrite($write_fd, "\n# Allow directives for modules\n");
|
||
|
|
foreach ($robots_content['Allow'] as $allow) {
|
||
|
|
fwrite($write_fd, 'Allow: ' . $allow . PHP_EOL);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Non-friendly URLs and parameters blocked from crawling
|
||
|
|
if (count($robots_content['GB'])) {
|
||
|
|
fwrite($write_fd, "\n# Non-friendly URLs and parameters blocked from crawling\n");
|
||
|
|
foreach ($robots_content['GB'] as $gb) {
|
||
|
|
fwrite($write_fd, 'Disallow: /*' . $gb . PHP_EOL);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// List of friendly rewrites on the shop blocked from crawling
|
||
|
|
if (count($robots_content['Directories'])) {
|
||
|
|
// For this, we will need language iso codes for the URLs
|
||
|
|
$prefixesForGeneration = [];
|
||
|
|
|
||
|
|
// We will use all language prefixes
|
||
|
|
$languagesIsoIds = Language::getIsoIds();
|
||
|
|
foreach ($languagesIsoIds as $language) {
|
||
|
|
$prefixesForGeneration[] = $language['iso_code'] . '/';
|
||
|
|
}
|
||
|
|
|
||
|
|
// And a non-prefixed version also
|
||
|
|
$prefixesForGeneration[] = '';
|
||
|
|
|
||
|
|
// We will also load the iso code of our default language
|
||
|
|
$defaultLanguageIso = Language::getIsoById((int) Configuration::get('PS_LANG_DEFAULT'));
|
||
|
|
|
||
|
|
// And we load all domains and their physical uris
|
||
|
|
// We don't care about the domain, we are doing relative paths
|
||
|
|
foreach (self::getDomains() as $uriList) {
|
||
|
|
foreach ($uriList as $uri) {
|
||
|
|
// And start a new section
|
||
|
|
fwrite($write_fd, sprintf("\n# Rules for %s%s", $uri['physical'], PHP_EOL));
|
||
|
|
fwrite($write_fd, "# Directories blocked from crawling\n");
|
||
|
|
|
||
|
|
// Directories blocked from crawling
|
||
|
|
foreach ($prefixesForGeneration as $prefix) {
|
||
|
|
foreach ($robots_content['Directories'] as $dir) {
|
||
|
|
fwrite(
|
||
|
|
$write_fd,
|
||
|
|
sprintf(
|
||
|
|
'Disallow: %s%s%s%s',
|
||
|
|
$uri['physical'],
|
||
|
|
$prefix,
|
||
|
|
$dir,
|
||
|
|
PHP_EOL
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Friendly URLs blocked from crawling
|
||
|
|
if (count($robots_content['Files'])) {
|
||
|
|
fwrite($write_fd, "# Friendly URLs blocked from crawling\n");
|
||
|
|
foreach ($robots_content['Files'] as $iso_code => $files) {
|
||
|
|
foreach ($files as $file) {
|
||
|
|
// Render language version all the time
|
||
|
|
fwrite($write_fd, 'Disallow: ' . $uri['physical'] . $iso_code . '/' . $file . PHP_EOL);
|
||
|
|
|
||
|
|
// If the language is a default one, also render it without prefix
|
||
|
|
if ($iso_code == $defaultLanguageIso) {
|
||
|
|
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, "\n# 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 = [];
|
||
|
|
|
||
|
|
// Allow directives for modules
|
||
|
|
$tab['Allow'] = [
|
||
|
|
'*/modules/*.css',
|
||
|
|
'*/modules/*.js',
|
||
|
|
'*/modules/*.png',
|
||
|
|
'*/modules/*.jpg',
|
||
|
|
'*/modules/*.gif',
|
||
|
|
'*/modules/*.svg',
|
||
|
|
'*/modules/*.webp',
|
||
|
|
'*/modules/*.avif',
|
||
|
|
'/js/jquery/*',
|
||
|
|
];
|
||
|
|
|
||
|
|
// Directories blocked from crawling
|
||
|
|
$tab['Directories'] = [
|
||
|
|
'app/', 'cache/', 'classes/', 'config/', 'controllers/',
|
||
|
|
'download/', 'js/', 'localization/', 'log/', 'mails/', 'modules/', 'override/',
|
||
|
|
'pdf/', 'src/', 'tools/', 'translations/', 'upload/', 'var/', 'vendor/', 'webservice/',
|
||
|
|
];
|
||
|
|
|
||
|
|
// Friendly URLs blocked from crawling
|
||
|
|
$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',
|
||
|
|
];
|
||
|
|
$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) . '\') AND
|
||
|
|
ml.url_rewrite IS NOT NULL AND ml.url_rewrite != \'\'';
|
||
|
|
if ($results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql)) {
|
||
|
|
foreach ($results as $row) {
|
||
|
|
$tab['Files'][$row['iso_code']][] = $row['url_rewrite'];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Non-friendly URLs and parameters blocked from crawling
|
||
|
|
// For example, "q" is a filter query, "order" is sorting etc
|
||
|
|
// Don't think about meaning of "GB"
|
||
|
|
$tab['GB'] = [
|
||
|
|
'?order=', '?tag=', '?id_currency=', '?search_query=', '?back=', '?n=', '?q=',
|
||
|
|
'&order=', '&tag=', '&id_currency=', '&search_query=', '&back=', '&n=', '&q=',
|
||
|
|
];
|
||
|
|
|
||
|
|
// List of list of non-friendly URLs to block from crawling.
|
||
|
|
foreach ($disallow_controllers as $controller) {
|
||
|
|
$tab['GB'][] = 'controller=' . $controller;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $tab;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return string php file to be run
|
||
|
|
*/
|
||
|
|
public static function getDefaultIndexContent()
|
||
|
|
{
|
||
|
|
return '<?php
|
||
|
|
/**
|
||
|
|
* For the full copyright and license information, please view the
|
||
|
|
* docs/licenses/LICENSE.txt file that was distributed with this source code.
|
||
|
|
*/
|
||
|
|
|
||
|
|
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
|
||
|
|
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
|
||
|
|
|
||
|
|
header("Cache-Control: no-store, no-cache, must-revalidate");
|
||
|
|
header("Cache-Control: post-check=0, pre-check=0", false);
|
||
|
|
header("Pragma: no-cache");
|
||
|
|
|
||
|
|
header("Location: ../");
|
||
|
|
exit;
|
||
|
|
';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return the directory list from the given $path.
|
||
|
|
*
|
||
|
|
* @param string $path
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public static function getDirectories($path)
|
||
|
|
{
|
||
|
|
if (function_exists('glob')) {
|
||
|
|
return self::getDirectoriesWithGlob($path);
|
||
|
|
}
|
||
|
|
|
||
|
|
return self::getDirectoriesWithReaddir($path);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return the directory list from the given $path using php glob function.
|
||
|
|
*
|
||
|
|
* @param string $path
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public static function getDirectoriesWithGlob($path)
|
||
|
|
{
|
||
|
|
$directoryList = glob($path . '/*', GLOB_ONLYDIR | GLOB_NOSORT);
|
||
|
|
if ($directoryList === false) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
$directoryList = array_map(function ($path) {
|
||
|
|
return substr($path, strrpos($path, '/') + 1);
|
||
|
|
}, $directoryList);
|
||
|
|
|
||
|
|
return $directoryList;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return the directory list from the given $path using php readdir function.
|
||
|
|
*
|
||
|
|
* @param string $path
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public static function getDirectoriesWithReaddir($path)
|
||
|
|
{
|
||
|
|
$directoryList = [];
|
||
|
|
$dh = @opendir($path);
|
||
|
|
if ($dh) {
|
||
|
|
while (($file = @readdir($dh)) !== false) {
|
||
|
|
if (is_dir($path . DIRECTORY_SEPARATOR . $file) && $file[0] != '.') {
|
||
|
|
$directoryList[] = $file;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
@closedir($dh);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $directoryList;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Display a warning message indicating that the method is deprecated.
|
||
|
|
*
|
||
|
|
* @param string $message
|
||
|
|
*/
|
||
|
|
public static function displayAsDeprecated($message = null)
|
||
|
|
{
|
||
|
|
$backtrace = debug_backtrace();
|
||
|
|
$callee = next($backtrace);
|
||
|
|
$class = isset($callee['class']) ? $callee['class'] : null;
|
||
|
|
|
||
|
|
if ($message === null) {
|
||
|
|
$message = 'The function ' . $callee['function'] . ' (Line ' . $callee['line'] . ') is deprecated and will be removed in the next major version.';
|
||
|
|
}
|
||
|
|
|
||
|
|
$error = 'Function <b>' . $callee['function'] . '()</b> is deprecated in <b>' . $callee['file'] . '</b> on line <b>' . $callee['line'] . '</b><br />';
|
||
|
|
|
||
|
|
Tools::throwDeprecated($error, $message, $class);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Display a warning message indicating that the parameter is deprecated.
|
||
|
|
*/
|
||
|
|
public static function displayParameterAsDeprecated($parameter)
|
||
|
|
{
|
||
|
|
$backtrace = debug_backtrace();
|
||
|
|
$callee = next($backtrace);
|
||
|
|
$error = 'Parameter <b>' . $parameter . '</b> in function <b>' . (isset($callee['function']) ? $callee['function'] : '') . '()</b> is deprecated in <b>' . $callee['file'] . '</b> on line <b>' . (isset($callee['line']) ? $callee['line'] : '(undefined)') . '</b><br />';
|
||
|
|
$message = 'The parameter ' . $parameter . ' in function ' . $callee['function'] . ' (Line ' . (isset($callee['line']) ? $callee['line'] : 'undefined') . ') is deprecated and will be removed in the next major version.';
|
||
|
|
$class = isset($callee['class']) ? $callee['class'] : null;
|
||
|
|
|
||
|
|
Tools::throwDeprecated($error, $message, $class);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function displayFileAsDeprecated()
|
||
|
|
{
|
||
|
|
$backtrace = debug_backtrace();
|
||
|
|
$callee = current($backtrace);
|
||
|
|
$error = 'File <b>' . $callee['file'] . '</b> is deprecated<br />';
|
||
|
|
$message = 'The file ' . $callee['file'] . ' is deprecated and will be removed in the next major version.';
|
||
|
|
$class = isset($callee['class']) ? $callee['class'] : null;
|
||
|
|
|
||
|
|
Tools::throwDeprecated($error, $message, $class);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected static function throwDeprecated($error, $message, $class)
|
||
|
|
{
|
||
|
|
if (_PS_DISPLAY_COMPATIBILITY_WARNING_) {
|
||
|
|
@trigger_error($error, E_USER_DEPRECATED);
|
||
|
|
PrestaShopLogger::addLog($message, 3, $class);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function enableCache($level = 1, ?Context $context = null)
|
||
|
|
{
|
||
|
|
if (!$context) {
|
||
|
|
$context = Context::getContext();
|
||
|
|
}
|
||
|
|
$smarty = $context->smarty;
|
||
|
|
if (!Configuration::get('PS_SMARTY_CACHE')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if ($smarty->force_compile == 0 && $smarty->caching == $level) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
self::$_forceCompile = (int) $smarty->force_compile;
|
||
|
|
self::$_caching = (int) $smarty->caching;
|
||
|
|
$smarty->force_compile = false;
|
||
|
|
$smarty->caching = (int) $level;
|
||
|
|
$smarty->cache_lifetime = 31536000; // 1 Year
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function restoreCacheSettings(?Context $context = null)
|
||
|
|
{
|
||
|
|
if (!$context) {
|
||
|
|
$context = Context::getContext();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isset(self::$_forceCompile)) {
|
||
|
|
$context->smarty->force_compile = (bool) self::$_forceCompile;
|
||
|
|
}
|
||
|
|
if (isset(self::$_caching)) {
|
||
|
|
$context->smarty->caching = (int) self::$_caching;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function isCallable($function)
|
||
|
|
{
|
||
|
|
$disabled = explode(',', ini_get('disable_functions'));
|
||
|
|
|
||
|
|
return !in_array($function, $disabled) && is_callable($function);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function pRegexp($s, $delim)
|
||
|
|
{
|
||
|
|
$s = str_replace($delim, '\\' . $delim, $s);
|
||
|
|
foreach (['?', '[', ']', '(', ')', '{', '}', '-', '.', '+', '*', '^', '$', '`', '"', '%'] as $char) {
|
||
|
|
$s = str_replace($char, '\\' . $char, $s);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $s;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function str_replace_once($needle, $replace, $haystack)
|
||
|
|
{
|
||
|
|
$pos = false;
|
||
|
|
if ($needle) {
|
||
|
|
$pos = strpos($haystack, $needle);
|
||
|
|
}
|
||
|
|
if ($pos === false) {
|
||
|
|
return $haystack;
|
||
|
|
}
|
||
|
|
|
||
|
|
return substr_replace($haystack, $replace, $pos, strlen($needle));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Identify the version of php
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function checkPhpVersion()
|
||
|
|
{
|
||
|
|
if (defined('PHP_VERSION')) {
|
||
|
|
$version = PHP_VERSION;
|
||
|
|
} else {
|
||
|
|
$version = phpversion('');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Specific ubuntu usecase: php version returns 5.2.4-2ubuntu5.2
|
||
|
|
if (strpos($version, '-') !== false) {
|
||
|
|
$version = substr($version, 0, strpos($version, '-'));
|
||
|
|
}
|
||
|
|
|
||
|
|
return $version;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Try to open a zip file in order to check if it's valid
|
||
|
|
*
|
||
|
|
* @param string $from_file
|
||
|
|
*
|
||
|
|
* @return bool success
|
||
|
|
*/
|
||
|
|
public static function ZipTest($from_file)
|
||
|
|
{
|
||
|
|
$zip = new ZipArchive();
|
||
|
|
|
||
|
|
return $zip->open($from_file, ZipArchive::CHECKCONS) === true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Extract a zip file to the given directory
|
||
|
|
*
|
||
|
|
* @param string $from_file
|
||
|
|
* @param string $to_dir
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public static function ZipExtract($from_file, $to_dir)
|
||
|
|
{
|
||
|
|
if (!file_exists($to_dir)) {
|
||
|
|
mkdir($to_dir, PsFileSystem::DEFAULT_MODE_FOLDER);
|
||
|
|
}
|
||
|
|
|
||
|
|
$zip = new ZipArchive();
|
||
|
|
if ($zip->open($from_file) === true && $zip->extractTo($to_dir) && $zip->close()) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param string $path
|
||
|
|
* @param int $filemode
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public static function chmodr($path, $filemode)
|
||
|
|
{
|
||
|
|
if (!is_dir($path)) {
|
||
|
|
return @chmod($path, $filemode);
|
||
|
|
}
|
||
|
|
$dh = opendir($path);
|
||
|
|
while (($file = readdir($dh)) !== false) {
|
||
|
|
if ($file != '.' && $file != '..') {
|
||
|
|
$fullpath = $path . '/' . $file;
|
||
|
|
if (is_link($fullpath)) {
|
||
|
|
return false;
|
||
|
|
} elseif (!is_dir($fullpath) && !@chmod($fullpath, $filemode)) {
|
||
|
|
return false;
|
||
|
|
} elseif (!Tools::chmodr($fullpath, $filemode)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
closedir($dh);
|
||
|
|
if (@chmod($path, $filemode)) {
|
||
|
|
return true;
|
||
|
|
} else {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get products order field name for queries.
|
||
|
|
*
|
||
|
|
* @param string $type by|way
|
||
|
|
* @param string|bool|null $value If no index given, use default order from admin -> pref -> products
|
||
|
|
* @param bool|string $prefix
|
||
|
|
*
|
||
|
|
* @return string Order by sql clause
|
||
|
|
*/
|
||
|
|
public static function getProductsOrder($type, $value = null, $prefix = false)
|
||
|
|
{
|
||
|
|
switch ($type) {
|
||
|
|
case 'by':
|
||
|
|
$list = [0 => 'name', 1 => 'price', 2 => 'date_add', 3 => 'date_upd', 4 => 'position', 5 => 'manufacturer_name', 6 => 'quantity', 7 => 'reference', 8 => 'sales'];
|
||
|
|
$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.
|
||
|
|
*
|
||
|
|
* @param string $begin
|
||
|
|
* @param string $end
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function url($begin, $end)
|
||
|
|
{
|
||
|
|
return $begin . ((strpos($begin, '?') !== false) ? '&' : '?') . $end;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Display error and dies or silently log the error.
|
||
|
|
*
|
||
|
|
* @param string $msg
|
||
|
|
* @param bool $die
|
||
|
|
*
|
||
|
|
* @return bool success of logging
|
||
|
|
*/
|
||
|
|
public static function dieOrLog($msg, $die = true)
|
||
|
|
{
|
||
|
|
if ($die || (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_)) {
|
||
|
|
header('HTTP/1.1 500 Internal Server Error', true, 500);
|
||
|
|
die($msg);
|
||
|
|
}
|
||
|
|
|
||
|
|
return PrestaShopLogger::addLog($msg);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Convert \n and \r\n and \r to <br />.
|
||
|
|
*
|
||
|
|
* @param string|null $str String to transform
|
||
|
|
*
|
||
|
|
* @return string|null New string
|
||
|
|
*/
|
||
|
|
public static function nl2br($str)
|
||
|
|
{
|
||
|
|
if (empty($str)) {
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
return str_replace(["\r\n", "\r", "\n", AddressFormat::FORMAT_NEW_LINE, PHP_EOL], '<br />', $str);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear cache for Smarty.
|
||
|
|
*
|
||
|
|
* @param Smarty $smarty
|
||
|
|
* @param bool|string $tpl
|
||
|
|
* @param string $cache_id
|
||
|
|
* @param string $compile_id
|
||
|
|
*
|
||
|
|
* @return int|null number of cache files deleted
|
||
|
|
*/
|
||
|
|
public static function clearCache($smarty = null, $tpl = false, $cache_id = null, $compile_id = null)
|
||
|
|
{
|
||
|
|
if ($smarty === null) {
|
||
|
|
$smarty = Context::getContext()->smarty;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($smarty === null) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!$tpl && $cache_id === null && $compile_id === null) {
|
||
|
|
return $smarty->clearAllCache();
|
||
|
|
}
|
||
|
|
|
||
|
|
$ret = $smarty->clearCache($tpl, $cache_id, $compile_id);
|
||
|
|
|
||
|
|
Hook::exec('actionClearCache');
|
||
|
|
|
||
|
|
return $ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear compile for Smarty.
|
||
|
|
*
|
||
|
|
* @param Smarty $smarty
|
||
|
|
*
|
||
|
|
* @return int|null number of template files deleted
|
||
|
|
*/
|
||
|
|
public static function clearCompile($smarty = null)
|
||
|
|
{
|
||
|
|
if ($smarty === null) {
|
||
|
|
$smarty = Context::getContext()->smarty;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($smarty === null) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
$ret = $smarty->clearCompiledTemplate();
|
||
|
|
|
||
|
|
Hook::exec('actionClearCompileCache');
|
||
|
|
|
||
|
|
return $ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear Smarty cache and compile folders.
|
||
|
|
*/
|
||
|
|
public static function clearSmartyCache()
|
||
|
|
{
|
||
|
|
$smarty = Context::getContext()->smarty;
|
||
|
|
Tools::clearCache($smarty);
|
||
|
|
Tools::clearCompile($smarty);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear Symfony cache.
|
||
|
|
*
|
||
|
|
* @param string $env
|
||
|
|
*/
|
||
|
|
public static function clearSf2Cache($env = null)
|
||
|
|
{
|
||
|
|
// This is the legacy method to clear Symfony cache, but it can result in unexpected behaviour with Container rebuild
|
||
|
|
// it should not be used anymore and will be removed. Until then, it fallbacks on the proper SymfonyCacheClearer service
|
||
|
|
$container = SymfonyContainer::getInstance();
|
||
|
|
if (null === $container) {
|
||
|
|
self::removeSymfonyCache($env);
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @var CacheClearerInterface|null $symfonyCacheClearer */
|
||
|
|
$symfonyCacheClearer = $container->get('prestashop.adapter.cache.clearer.symfony_cache_clearer');
|
||
|
|
if ($symfonyCacheClearer) {
|
||
|
|
$symfonyCacheClearer->clear();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static function removeSymfonyCache(?string $env = null): void
|
||
|
|
{
|
||
|
|
if (null === $env) {
|
||
|
|
$env = _PS_ENV_;
|
||
|
|
}
|
||
|
|
|
||
|
|
$dir = _PS_ROOT_DIR_ . '/var/cache/' . $env . '/';
|
||
|
|
|
||
|
|
register_shutdown_function(function () use ($dir) {
|
||
|
|
$fs = new Filesystem();
|
||
|
|
$fs->remove($dir);
|
||
|
|
Hook::exec('actionClearSf2Cache');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear both Smarty and Symfony cache.
|
||
|
|
*/
|
||
|
|
public static function clearAllCache()
|
||
|
|
{
|
||
|
|
Tools::clearSmartyCache();
|
||
|
|
Tools::clearSf2Cache();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Allow to get the memory limit in octets.
|
||
|
|
*
|
||
|
|
* @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.
|
||
|
|
*
|
||
|
|
* @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' && 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
|
||
|
|
*/
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Safely unserializes input string with protection against object injection.
|
||
|
|
*
|
||
|
|
* @param string $serialized Serialized string to decode
|
||
|
|
* @param bool $allowObjects Whether to allow object unserialization
|
||
|
|
*
|
||
|
|
* @return mixed|null Unserialized data or false on failure
|
||
|
|
*/
|
||
|
|
public static function unSerialize($serialized, $allowObjects = false)
|
||
|
|
{
|
||
|
|
// Only allow if it's a string
|
||
|
|
if (!is_string($serialized)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for potentially malicious serialized objects
|
||
|
|
if (!$allowObjects) {
|
||
|
|
if (str_contains($serialized, 'O:') && preg_match('/(^|;|{|})O:[0-9]+:"/', $serialized)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Use native protection only if we disallow objects
|
||
|
|
return @unserialize($serialized, ['allowed_classes' => false]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Otherwise allow objects as usual
|
||
|
|
return @unserialize($serialized);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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();
|
||
|
|
$cacheDir = _PS_CACHE_DIR_ . 'purifier';
|
||
|
|
// Make sure the cache directory exists, as the purifier won't create it automatically
|
||
|
|
if (!file_exists($cacheDir) && !mkdir($cacheDir, PsFileSystem::DEFAULT_MODE_FOLDER, true) && !is_dir($cacheDir)) {
|
||
|
|
throw new RuntimeException(sprintf('HTML purifier directory "%s" can not be created', $cacheDir));
|
||
|
|
}
|
||
|
|
|
||
|
|
$config->set('Attr.EnableID', true);
|
||
|
|
$config->set('Attr.AllowedRel', ['nofollow']);
|
||
|
|
$config->set('HTML.Trusted', true);
|
||
|
|
$config->set('Cache.SerializerPath', $cacheDir);
|
||
|
|
$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']);
|
||
|
|
}
|
||
|
|
Hook::exec('actionModifyHtmlPurifierConfig', [
|
||
|
|
'config' => &$config,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
$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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return path to a Product or a CMS category.
|
||
|
|
*
|
||
|
|
* @param string $url_base Start URL
|
||
|
|
* @param int $id_category Start category
|
||
|
|
* @param string $path Current path
|
||
|
|
* @param string $highlight String to highlight (in XHTML/CSS)
|
||
|
|
* @param string $category_type Category type (products/cms)
|
||
|
|
* @param bool $home
|
||
|
|
*/
|
||
|
|
public static function getPath($url_base, $id_category, $path = '', $highlight = '', $category_type = 'catalog', $home = false)
|
||
|
|
{
|
||
|
|
$context = Context::getContext();
|
||
|
|
if ($category_type == 'catalog') {
|
||
|
|
$category = Db::getInstance()->getRow('
|
||
|
|
SELECT id_category, level_depth, nleft, nright
|
||
|
|
FROM ' . _DB_PREFIX_ . 'category
|
||
|
|
WHERE id_category = ' . (int) $id_category);
|
||
|
|
if (isset($category['id_category'])) {
|
||
|
|
$sql = 'SELECT c.id_category, cl.name, cl.link_rewrite
|
||
|
|
FROM ' . _DB_PREFIX_ . 'category c
|
||
|
|
LEFT JOIN ' . _DB_PREFIX_ . 'category_lang cl ON (cl.id_category = c.id_category' . Shop::addSqlRestrictionOnLang('cl') . ')
|
||
|
|
WHERE c.nleft <= ' . (int) $category['nleft'] . '
|
||
|
|
AND c.nright >= ' . (int) $category['nright'] . '
|
||
|
|
AND cl.id_lang = ' . (int) $context->language->id .
|
||
|
|
($home ? ' AND c.id_category=' . (int) $id_category : '') . '
|
||
|
|
AND c.id_category != ' . (int) Category::getTopCategory()->id . '
|
||
|
|
GROUP BY c.id_category
|
||
|
|
ORDER BY c.level_depth ASC
|
||
|
|
LIMIT ' . (!$home ? (int) $category['level_depth'] + 1 : 1);
|
||
|
|
$categories = Db::getInstance()->executeS($sql);
|
||
|
|
$full_path = '';
|
||
|
|
$n = 1;
|
||
|
|
$n_categories = (int) count($categories);
|
||
|
|
foreach ($categories as $category) {
|
||
|
|
$action = (($category['id_category'] == (int) Configuration::get('PS_HOME_CATEGORY') || $home) ? 'index' : 'updatecategory');
|
||
|
|
$link_params = ['action' => $action, 'id_category' => (int) $category['id_category']];
|
||
|
|
$edit_link = Context::getContext()->link->getAdminLink('AdminCategories', true, $link_params);
|
||
|
|
$link_params['action'] = 'index';
|
||
|
|
$index_link = Context::getContext()->link->getAdminLink('AdminCategories', true, $link_params);
|
||
|
|
$edit = '<a href="' . Tools::safeOutput($edit_link) . '" title="' . ($category['id_category'] == Category::getRootCategory()->id_category ? 'Home' : 'Modify') . '"><i class="icon-' . (($category['id_category'] == Category::getRootCategory()->id_category || $home) ? 'home' : 'pencil') . '"></i></a> ';
|
||
|
|
$full_path .= $edit .
|
||
|
|
($n < $n_categories ? '<a href="' . Tools::safeOutput($index_link) . '" title="' . htmlentities($category['name'], ENT_NOQUOTES, 'UTF-8') . '">' : '') .
|
||
|
|
(!empty($highlight) ? str_ireplace($highlight, '<span class="highlight">' . htmlentities($highlight, ENT_NOQUOTES, 'UTF-8') . '</span>', $category['name']) : $category['name']) .
|
||
|
|
($n < $n_categories ? '</a>' : '') .
|
||
|
|
(($n++ != $n_categories || !empty($path)) ? ' > ' : '');
|
||
|
|
}
|
||
|
|
|
||
|
|
return $full_path . $path;
|
||
|
|
}
|
||
|
|
} elseif ($category_type == 'cms') {
|
||
|
|
$category = new CMSCategory($id_category, $context->language->id);
|
||
|
|
if (!$category->id) {
|
||
|
|
return $path;
|
||
|
|
}
|
||
|
|
$name = ($highlight != null) ? str_ireplace($highlight, '<span class="highlight">' . $highlight . '</span>', CMSCategory::hideCMSCategoryPosition($category->name)) : CMSCategory::hideCMSCategoryPosition($category->name);
|
||
|
|
$edit = '<a href="' . Tools::safeOutput($url_base . '&id_cms_category=' . $category->id . '&updatecms_category&token=' . Tools::getAdminToken('AdminCmsContent' . (int) Tab::getIdFromClassName('AdminCmsContent') . (int) $context->employee->id)) . '">
|
||
|
|
<i class="icon-pencil"></i></a> ';
|
||
|
|
if ($category->id == 1) {
|
||
|
|
$edit = '<li><a href="' . Tools::safeOutput($url_base . '&id_cms_category=' . $category->id . '&viewcategory&token=' . Tools::getAdminToken('AdminCmsContent' . (int) Tab::getIdFromClassName('AdminCmsContent') . (int) $context->employee->id)) . '">
|
||
|
|
<i class="icon-home"></i></a></li> ';
|
||
|
|
}
|
||
|
|
$path = $edit . '<li><a href="' . Tools::safeOutput($url_base . '&id_cms_category=' . $category->id . '&viewcategory&token=' . Tools::getAdminToken('AdminCmsContent' . (int) Tab::getIdFromClassName('AdminCmsContent') . (int) $context->employee->id)) . '">
|
||
|
|
' . $name . '</a></li> > ' . $path;
|
||
|
|
if ($category->id == 1) {
|
||
|
|
return substr($path, 0, strlen($path) - 3);
|
||
|
|
}
|
||
|
|
|
||
|
|
return Tools::getPath($url_base, $category->id_parent, $path, '', 'cms');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function redirectToInstall()
|
||
|
|
{
|
||
|
|
// No redirection in CLI mode
|
||
|
|
if (Tools::isPHPCLI()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate a URL corresponding to the current page but
|
||
|
|
* with the query string altered.
|
||
|
|
*
|
||
|
|
* If $extraParams is set to NULL, then all query params are stripped.
|
||
|
|
*
|
||
|
|
* Otherwise, params from $extraParams that have a null value are stripped,
|
||
|
|
* and other params are added. Params not in $extraParams are unchanged.
|
||
|
|
*/
|
||
|
|
public static function updateCurrentQueryString(?array $extraParams = null): string
|
||
|
|
{
|
||
|
|
$uriWithoutParams = explode('?', $_SERVER['REQUEST_URI'])[0];
|
||
|
|
$url = Tools::getCurrentUrlProtocolPrefix() . $_SERVER['HTTP_HOST'] . $uriWithoutParams;
|
||
|
|
$params = [];
|
||
|
|
$paramsFromUri = '';
|
||
|
|
if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
|
||
|
|
$paramsFromUri = explode('?', $_SERVER['REQUEST_URI'])[1];
|
||
|
|
}
|
||
|
|
parse_str($paramsFromUri, $params);
|
||
|
|
|
||
|
|
if (null !== $extraParams) {
|
||
|
|
foreach ($extraParams as $key => $value) {
|
||
|
|
if (null === $value) {
|
||
|
|
unset($params[$key]);
|
||
|
|
} else {
|
||
|
|
$params[$key] = $value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (null !== $extraParams) {
|
||
|
|
foreach ($params as $key => $param) {
|
||
|
|
if ('' === $param) {
|
||
|
|
unset($params[$key]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$params = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
$queryString = str_replace('%2F', '/', http_build_query($params, '', '&'));
|
||
|
|
|
||
|
|
return $url . ($queryString ? "?$queryString" : '');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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'];
|
||
|
|
}
|