Files
lulandia.pl/modules/pagecache/classes/JprestaUtils.php
2025-04-01 00:38:54 +02:00

1401 lines
56 KiB
PHP

<?php
/**
* Page Cache Ultimate, Page Cache standard and Speed pack are powered by Jpresta (jpresta . com)
*
* @author Jpresta
* @copyright Jpresta
* @license See the license of this module in file LICENSE.txt, thank you.
*/
namespace JPresta\SpeedPack;
use Cache;
use Configuration;
use Context;
use DB;
use FilesystemIterator;
use Hook;
use ImageType;
use JprestaUtilsDispatcher;
use Language;
use Logger;
use Module;
use Exception;
use PrestaShopDatabaseException;
use PrestaShopLogger;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionObject;
use ReflectionProperty;
use Shop;
use ShopUrl;
use Tools;
if (!defined('_PS_VERSION_')) {exit;}
// Check existence of the class to be able to handle compatibility problems in a friendly way
if (!class_exists('JprestaUtils')) {
require_once 'JprestaUtilsDispatcher.php';
class JprestaUtils
{
public static function getImageTypeFormattedName($name)
{
if (method_exists('ImageType', 'getFormattedName')) {
return ImageType::getFormattedName($name);
} else {
$themeName = Context::getContext()->shop->theme_name;
$nameWithoutThemeName = str_replace(['_' . $themeName, $themeName . '_'], '', $name);
//check if the theme name is already in $name if yes only return $name
if ($themeName !== null && strstr($name, $themeName) && ImageType::getByNameNType($name)) {
return $name;
}
if (ImageType::getByNameNType($nameWithoutThemeName . '_' . $themeName)) {
return $nameWithoutThemeName . '_' . $themeName;
}
if (ImageType::getByNameNType($themeName . '_' . $nameWithoutThemeName)) {
return $themeName . '_' . $nameWithoutThemeName;
}
return $nameWithoutThemeName . '_default';
}
}
/**
* Original PHP code by Chirp Internet: www.chirp.com.au, Please acknowledge use of this code by including this header.
*
* @param string
* @param string $base Base URL
* @param $managedControllers
* @param bool|string $tagIgnoreStart
* @param bool|string $tagIgnoreEnd
* @param bool|string $ignoreBeforePattern
* @param bool|string $ignoreAfterPattern
* @return array List of URLs
*/
public static function parseLinks($html, $base, $managedControllers, $tagIgnoreStart = false, $tagIgnoreEnd = false, $ignoreBeforePattern = false, $ignoreAfterPattern = false)
{
if ($ignoreBeforePattern) {
$startPos = self::strpos($html, $ignoreBeforePattern);
if ($startPos !== false) {
$endPos = false;
if ($ignoreAfterPattern) {
$endPos = self::strpos($html, $ignoreAfterPattern, $startPos);
}
if ($endPos !== false) {
return self::parseLinks(Tools::substr($html, $startPos, $endPos - $startPos), $base, $managedControllers);
} else {
return self::parseLinks(Tools::substr($html, $startPos), $base, $managedControllers);
}
}
}
$startPos = false;
if ($tagIgnoreStart !== false) {
$startPos = self::strpos($html, $tagIgnoreStart);
}
if ($startPos !== false) {
$linksAfter = array();
$endPos = self::strpos($html, $tagIgnoreEnd, min(Tools::strlen($html), $startPos + 4));
$linksBefore = self::parseLinks(Tools::substr($html, 0, $startPos), $base, $managedControllers,
$tagIgnoreStart, $tagIgnoreEnd);
if ($endPos !== false) {
$linksAfter = self::parseLinks(Tools::substr($html, $endPos + 4), $base, $managedControllers,
$tagIgnoreStart, $tagIgnoreEnd);
}
return array_merge($linksBefore, $linksAfter);
} else {
$links = array();
$base_relative = preg_replace('/https?:\/\//', '//', $base === null ? '' : $base);
$base_exp = preg_replace('/([^a-zA-Z0-9])/', '\\\\$1', $base === null ? '' : $base);
$base_exp = preg_replace('/https?/', 'http[s]?', $base_exp === null ? '' : $base_exp);
$regexp = '<a\s[^>]*href=(\"??)' . $base_exp . '([^\" >]*?)\\1[^>]*>(.*)<\/a>';
$isMultiLanguageActivated = Language::isMultiLanguageActivated();
if (preg_match_all("/$regexp/siU", $html, $matches, PREG_SET_ORDER)) {
// The links array will help us to remove duplicates
foreach ($matches as $match) {
// $match[2] = link address
// $match[3] = link text
// Insert backlinks that correspond to a possibily cached page into the database
$url = $match[2];
// Add leading /
if (strpos($url, "/") > 0 || strpos($url, "/") === false) {
$url = "/" . $url;
}
// Remove language part if any
$url_without_lang = $url;
if ($isMultiLanguageActivated && preg_match('#^/([a-z]{2})(?:/.*)?$#', $url, $m)) {
$url_without_lang = Tools::substr($url, 3);
}
$anchorPos = strpos($url_without_lang, '#');
if ($anchorPos !== false) {
$url_without_lang = Tools::substr($url_without_lang, 0, $anchorPos);
}
$bl_controller = JprestaUtilsDispatcher::getPageCacheInstance()->getControllerFromURL($url_without_lang);
if ($bl_controller === false) {
if (JprestaUtils::isModuleEnabled('smartseourl')) {
$bl_controller = self::getControllerFromURLSmartseourl($url_without_lang);
} else {
// To avoid re-installation of override we have this workaround
$bl_controller = JprestaUtilsDispatcher::getPageCacheInstance()->getControllerFromURL('en' . $url_without_lang);
}
}
if (in_array($bl_controller, $managedControllers)) {
$links[$match[2]] = $base_relative . $match[2];
}
}
}
return $links;
}
}
/**
* @param string $url
* @param int $curl_timeout
* @param array $opts
*
* @return bool|string
*
* @throws Exception
*/
public static function file_get_contents_curl(
$url,
$curl_timeout,
$opts
)
{
$content = false;
if (function_exists('curl_init')) {
if (method_exists('Tools', 'refreshCACertFile')) {
// Does not exist in some PS1.6
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, $opts['http']['content']);
}
}
}
$content = curl_exec($curl);
if (false === $content && _PS_MODE_DEV_) {
$errorMessage = sprintf('file_get_contents_curl failed to download %s : (error code %d) %s',
$url,
curl_errno($curl),
curl_error($curl)
);
throw new \Exception($errorMessage);
}
curl_close($curl);
}
return $content;
}
public static function getControllerFromURLSmartseourl($url)
{
static $smartseourl = null;
if ($smartseourl === null) {
$smartseourl = Module::getInstanceByName('smartseourl');
}
if (!file_exists($smartseourl->path_cache)) {
$smartseourl->createCacheFolders();
}
$request_uri = $url;
$rewrite = trim($request_uri, '/');
if (strpos($rewrite, '.html') > 0) {
$rewrite = explode('.html', $rewrite);
$rewrite = $rewrite[0];
}
$is_cache = $smartseourl->getDirectoryTree($smartseourl->path_cache, md5($rewrite));
if ($is_cache) {
if ($is_cache['type'] === 'cms_category') {
$is_cache['type'] = 'cms';
}
return $is_cache['type'];
} else {
$context = Context::getContext();
$result = \SSURewriteClass::getBy($rewrite, null, $context->language->id, $context->shop->id);
if ($result) {
return $result['table_type'];
}
}
return false;
}
public static function decodeConfiguration($value)
{
if ($value) {
$value = str_replace('&lt;', '<', $value);
}
return $value;
}
public static function encodeConfiguration($value)
{
if ($value) {
$value = str_replace('<', '&lt;', $value);
}
return $value;
}
public static function parseCSS($html, $base)
{
$links = array();
$base_exp = preg_replace('/([^a-zA-Z0-9])/', '\\\\$1', $base === null ? '' : $base);
$regexp = '<link\s[^>]*href=(\"??)[^\" >]*' . $base_exp . '([^\" >]*?)\\1[^>]*>';
if (preg_match_all("/$regexp/siU", $html, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$links[] = $match[2];
}
}
return $links;
}
public static function parseJS($html, $base)
{
$links = array();
$base_exp = preg_replace('/([^a-zA-Z0-9])/', '\\\\$1', $base === null ? '' : $base);
$regexp = '<script\s[^>]*src=(\"??)[^\" >]*' . $base_exp . '([^\" >]*?)\\1[^>]*>';
if (preg_match_all("/$regexp/siU", $html, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$links[] = $match[2];
}
}
return $links;
}
/**
* Delete a file
*
* @param $file string The file to delete
* @return bool true if the file has been deleted
*/
public static function deleteFile($file)
{
if (is_file($file) && @unlink($file) === false) {
$error = error_get_last();
if ($error && stripos($error['message'], 'No such file or directory') === false) {
// Ignore error when the directory does not exist anymore
self::addLog('Utils | Cannot delete file ' . $file . ' : ' . $error['message'], 3);
return false;
}
}
return true;
}
/**
* @param $oldname string
* @param $newname string
* @return bool
*/
public static function rename($oldname, $newname)
{
try {
if (rename($oldname, $newname) === false) {
$error = error_get_last();
if ($error && stripos($error['message'], 'No such file or directory') === false) {
// Ignore error when the directory does not exist anymore
self::addLog('Utils | Cannot rename ' . $oldname . ' to ' . $newname . ': ' . $error['message'], 2);
return false;
}
}
} catch (Exception $e) {
self::addLog('Utils | Cannot rename ' . $oldname . ' to ' . $newname . ': ' . $e->getMessage(), 2);
return false;
}
return true;
}
/**
* Delete directory and subdirectories with their files.
*
* @param $dir string Directory to delete
* @param int $timeoutSeconds
* @return bool true if the directory has been deleted
*/
public static function deleteDirectory($dir, $timeoutSeconds = 0)
{
$startTime = microtime(true);
$errorCount = 0;
$first_error = null;
if (file_exists($dir)) {
$it = new RecursiveDirectoryIterator($dir, FilesystemIterator::CURRENT_AS_PATHNAME | FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files as $file) {
if (is_dir($file)) {
if (@rmdir($file) === false) {
$error = error_get_last();
if ($error && stripos($error['message'], 'No such file or directory') === false) {
// Ignore error when the directory does not exist anymore
$errorCount++;
if (!$first_error) {
$first_error = $error['message'];
}
}
}
} else {
if (@unlink($file) === false) {
$error = error_get_last();
if ($error && stripos($error['message'], 'No such file or directory') === false) {
// Ignore error when the file does not exist anymore
$errorCount++;
if (!$first_error) {
$first_error = $error['message'];
}
}
}
}
if ($timeoutSeconds > 0 && (microtime(true) - $startTime) > $timeoutSeconds) {
// It's too long, stopping
if (!$first_error) {
$first_error = "too long to delete everything (> $timeoutSeconds seconds)";
}
$errorCount++;
break;
}
}
if (!$errorCount && @rmdir($dir) === false) {
$error = error_get_last();
if ($error && stripos($error['message'], 'No such file or directory') === false) {
// Ignore error when the directory does not exist anymore
$errorCount++;
if (!$first_error) {
$first_error = $error['message'];
}
}
}
if ($errorCount > 0) {
self::addLog('Utils | ' . $errorCount . ' error(s) during deletion of ' . $dir . ' - First error: ' . $first_error, 3);
}
return $errorCount === 0;
}
return true;
}
/**
* @param $sourceDir string Source directory
* @param $targetDir string Target directory
* @param $replace bool if true, replace files into target directory
* @return bool true on success, false on failure
*/
public static function copyFiles($sourceDir, $targetDir, $replace = false)
{
$ret = true;
if (is_dir($sourceDir)) {
if (!is_dir($targetDir) && !mkdir($targetDir, 0777, true)) {
self::addLog("Utils | Cannot create directory $targetDir", 2);
return false;
}
$files = scandir($sourceDir);
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
if (is_dir($sourceDir . '/' . $file)) {
$ret = $ret && self::copyFiles($sourceDir . '/' . $file, $targetDir . '/' . $file);
} else {
if ($replace || !file_exists($targetDir . '/' . $file)) {
if (!copy($sourceDir . '/' . $file, $targetDir . '/' . $file)) {
self::addLog("Utils | Cannot copy $sourceDir/$file to $targetDir/$file", 2);
$ret = false;
}
}
}
}
}
}
return $ret;
}
/**
* Creates a backup file, then search and replace in it
*
* @param $file string File to modify
* @param mixed $search <p>
* The value being searched for, otherwise known as the needle.
* An array may be used to designate multiple needles.
* </p>
* @param mixed $replace <p>
* The replacement value that replaces found search
* values. An array may be used to designate multiple replacements.
* </p>
*/
public static function replaceInFile($file, $search, $replace)
{
if (is_file($file)) {
$i = 1;
$suffix = '-backup-' . date('Ymd');
while (file_exists($file . $suffix)) {
$suffix = '-backup-' . date('Ymd') . '-' . $i;
$i++;
}
Tools::copy($file, $file . $suffix);
$content = Tools::file_get_contents($file);
$content = str_replace($search, $replace, $content);
file_put_contents($file, $content);
}
}
public static function isAjax() {
// Usage of ajax parameter is deprecated
$isAjax = Tools::getValue('ajax') || Tools::isSubmit('ajax');
if (!$isAjax && isset($_SERVER['HTTP_ACCEPT'])) {
$isAjax = preg_match('#\bapplication/json\b#', $_SERVER['HTTP_ACCEPT']);
}
if (!$isAjax && isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
$isAjax = $_SERVER['HTTP_X_REQUESTED_WITH'] === 'xmlhttprequest';
}
if (!$isAjax && isset($_SERVER['HTTP_SEC_FETCH_MODE'])) {
$isAjax = $_SERVER['HTTP_SEC_FETCH_MODE'] === 'cors';
}
return $isAjax;
}
// Does not support flag GLOB_BRACE
public static function glob_recursive($pattern, $flags = 0)
{
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
$files = array_merge($files, self::glob_recursive($dir . '/' . basename($pattern), $flags));
}
return $files;
}
public static function strpos($str, $find, $offset = 0, $encoding = 'UTF-8')
{
if (method_exists('Tools', 'strpos')) {
return Tools::strpos($str, $find, $offset, $encoding);
} else {
if (function_exists('mb_strpos')) {
return mb_strpos($str, $find, $offset, $encoding);
}
return strpos($str, $find, $offset);
}
}
public static function startsWith($haystack, $needle)
{
$length = Tools::strlen($needle);
return (Tools::substr($haystack, 0, $length) === $needle);
}
public static function endsWith($haystack, $needle)
{
$length = Tools::strlen($needle);
if ($length == 0) {
return true;
}
return (Tools::substr($haystack, -$length) === $needle);
}
public static function trimTo($string, $default)
{
if (!$string) {
return $default;
}
$ret = trim($string);
if (Tools::strlen($ret === 0)) {
return $default;
}
return $ret;
}
/**
* Determine if a variable is iterable. i.e. can be used to loop over.
*
* @return bool
*/
public static function isIterable($var)
{
return $var !== null && (is_array($var) || $var instanceof \Traversable);
}
/**
* @param $id_shop int ID of the desired shop
* @return string URL path for the most adapted language
*/
public static function getUrlLang($id_shop) {
$psRewritingSettings = (int) Configuration::get('PS_REWRITING_SETTINGS', null, null, $id_shop);
if (!Language::isMultiLanguageActivated($id_shop) || !$psRewritingSettings) {
return '';
}
$selectedLang = null;
$shopDefaultLangId = Configuration::get('PS_LANG_DEFAULT', null, null, $id_shop);
$languages = Language::getLanguages(true, $id_shop, false);
if (!$languages) {
return '';
}
foreach ($languages as $lang) {
if ($selectedLang === null) {
$selectedLang = $lang;
}
if ($shopDefaultLangId == $lang['id_lang']) {
$selectedLang = $lang;
}
if (Context::getContext()->employee->id_lang == $lang['id_lang']) {
$selectedLang = $lang;
break;
}
}
return $selectedLang['iso_code'] . '/';
}
public static function getDomains()
{
// Don't rely on Tools::getDomains() since it can be overwritten by modules like ets_upgrade which does not
// return domains as expected
$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 valuesAreIdentical($v1, $v2)
{
$type1 = gettype($v1);
$type2 = gettype($v2);
switch (true) {
case ($type1 === 'boolean'):
if ($type2 === 'string') {
if (($v1 && ((int)$v2) !== 1) || (!$v1 && ((int)$v2) !== 0)) {
// Can be string "1" or "0"
return false;
}
} // Else do strict comparison here.
else {
if ($v1 !== $v2) {
return false;
}
}
break;
case ($type1 === 'integer'):
if ($type2 === 'string') {
if ($v1 !== (int)$v2) {
return false;
}
} // Else do strict comparison here.
else {
if ($v1 !== $v2) {
return false;
}
}
break;
case ($type1 === 'double'):
if ($type2 === 'string') {
if ($v1 !== (float)$v2) {
return false;
}
} // Else do strict comparison here.
else {
if ($v1 !== $v2) {
return false;
}
}
break;
case ($type1 === 'string'):
//Do strict comparison here.
if ($v1 !== $v2) {
return false;
}
break;
case ($type1 === 'array'):
$bool = self::arraysAreIdentical($v1, $v2);
if ($bool === false) {
return false;
}
break;
case ($type1 === 'object'):
$diffs = self::getObjectDifferences($v1, $v2);
if (count($diffs) > 0) {
return false;
}
break;
case ($type1 === 'NULL'):
//Since both types were of type NULL, consider their "values" equal.
break;
case ($type1 === 'resource'):
//How to compare if at all?
break;
case ($type1 === 'unknown type'):
//How to compare if at all?
break;
} //end switch
//All tests passed.
return true;
}
public static function getObjectDifferences($o1, $o2)
{
$differences = array();
// Now do strict(er) comparison.
$reflectionObject = new ReflectionObject($o1);
$properties = $reflectionObject->getProperties(ReflectionProperty::IS_PUBLIC);
foreach ($properties as $property) {
if (in_array($property->name, ['date_upd', 'indexed']) || $property->isStatic()) {
continue;
}
if (!property_exists($o2, $property->name)) {
$differences[$property->name] = self::toString($o1->{$property->name}) . ' <> (not set)';
} else {
$bool = self::valuesAreIdentical($o1->{$property->name}, $o2->{$property->name});
if ($bool === false) {
$differences[$property->name] = self::toString($o1->{$property->name}) . ' <> ' . self::toString($o2->{$property->name});
}
}
}
// All tests passed.
return $differences;
}
public static function arraysAreIdentical($arr1, $arr2)
{
if ($arr1 === null || $arr2 === null) {
return $arr1 === $arr2;
}
if (!is_array($arr1) || !is_array($arr2)) {
return false;
}
$count = count($arr1);
// Require that they have the same size.
if (count($arr2) !== $count) {
return false;
}
// Require that they have the same keys.
$arrKeysInCommon = array_intersect_key($arr1, $arr2);
if (count($arrKeysInCommon) !== $count) {
return false;
}
// Require that they have the same value for same key.
foreach ($arr1 as $key => $val) {
$bool = self::valuesAreIdentical($val, $arr2[$key]);
if ($bool === false) {
return false;
}
}
// All tests passed.
return true;
}
public static function toString($val)
{
$type = gettype($val);
switch (true) {
case ($type === 'boolean'):
return $val ? 'true' : 'false';
case ($type === 'array'):
return 'array[' . count($val) . ']';
case ($type === 'NULL'):
return '(null)';
case ($type === 'unknown type'):
return '(unknown type)';
default:
return (string)$val;
}
}
public static function getDatabaseName()
{
if (Tools::version_compare(_PS_VERSION_, '1.7', '>')) {
$configFile = dirname(__FILE__) . '/../../../app/config/parameters.php';
if (file_exists($configFile)) {
$config = require $configFile;
return $config['parameters']['database_name'];
} else {
return _DB_NAME_;
}
} else {
return _DB_NAME_;
}
}
/**
* @param string $sql SQL query to execute
* @param bool $logOnError true if you want errors to be logged
* @param bool $throwOnError true if you want errors to throw PrestaShopDatabaseException
* @return bool true if OK
* @throws PrestaShopDatabaseException
*/
public static function dbExecuteSQL($sql, $logOnError = true, $throwOnError = false)
{
$db = DB::getInstance();
$result = false;
try {
$result = $db->execute($sql, false);
if (!$result) {
$msg = 'SQL Error #' . $db->getNumberError() . ': "' . $db->getMsgError() . '" in ' . self::getCallerInfos();
$msg .= ' - SQL query was: "' . $sql . '"';
if ($logOnError) {
self::addLog('Utils | ' . $msg, 2);
}
if ($throwOnError) {
throw new PrestaShopDatabaseException($msg);
}
}
} catch (Exception $e) {
$msg = 'SQL Error #' . $db->getNumberError() . ': "' . $db->getMsgError() . '" in ' . self::getCallerInfos();
$msg .= ' - SQL query was: "' . $sql . '"';
if ($logOnError) {
self::addLog('Utils | ' . $msg . ' - exception: ' . $e->getMessage(), 2);
}
if ($throwOnError) {
throw new PrestaShopDatabaseException($msg, 0, $e);
}
}
return $result;
}
/**
* @param string $sql SQL query to execute
* @param bool $logOnError true if you want errors to be logged
* @param bool $throwOnError true if you want errors to throw PrestaShopDatabaseException
* @return array
* @throws PrestaShopDatabaseException
*/
public static function dbSelectRows($sql, $logOnError = true, $throwOnError = false)
{
$db = DB::getInstance();
$result = [];
try {
$result = $db->executeS($sql, true, false);
if (!$result && $db->getNumberError() != 0) {
$msg = 'SQL Error #' . $db->getNumberError() . ': "' . $db->getMsgError() . '" in ' . self::getCallerInfos();
$msg .= ' - SQL query was: "' . $sql . '"';
if ($logOnError) {
self::addLog('Utils | ' . $msg, 2);
}
if ($throwOnError) {
throw new PrestaShopDatabaseException($msg);
}
}
} catch (Exception $e) {
$msg = 'SQL Error #' . $db->getNumberError() . ': "' . $db->getMsgError() . '" in ' . self::getCallerInfos();
$msg .= ' - SQL query was: "' . $sql . '"';
if ($logOnError) {
self::addLog('Utils | ' . $msg . ' - exception: ' . $e->getMessage(), 2);
}
if ($throwOnError) {
throw new PrestaShopDatabaseException($msg, 0, $e);
}
}
if (!is_array($result)) {
$result = [];
}
return $result;
}
public static function dbColumnExists($tableName, $column)
{
$db = Db::getInstance();
$row = self::dbSelectRows('SHOW COLUMNS FROM `' . $db->escape($tableName) . '` LIKE \'' . $db->escape($column) . '\';', true, true);
return is_array($row) && count($row) > 0 && is_array($row[0]) && count($row[0]) > 0;
}
public static function dbGetTableOfColumn($column, $listTables) {
$db = Db::getInstance();
return self::dbGetValue('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=' . self::dbToString($db,
self::getDatabaseName()) . ' AND TABLE_NAME IN (' . implode(',', array_map(function ($str) {
return '\'' . $str . '\'';
}, $listTables)) . ') AND COLUMN_NAME=\'' . $db->escape($column) . '\'');
}
public static function dbTableExists($tableName)
{
$db = Db::getInstance();
return self::dbGetValue('SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=' . self::dbToString($db,
self::getDatabaseName()) . ' AND table_name=' . self::dbToString($db, $tableName)) > 0;
}
/**
* @param $tableName string Name of the table
* @param $columns string[] Names of the columns
* @return bool|string Name of the index or false if it does not exist
* @throws PrestaShopDatabaseException
*/
public static function dbIndexExists($tableName, $columns)
{
$cols = [];
if (!is_array($columns)) {
$cols[0] = $columns;
} else {
$cols = $columns;
}
if (count($cols) > 0) {
$firsColumn = reset($cols);
$db = Db::getInstance();
$rows = self::dbSelectRows('SELECT * FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema=' . self::dbToString($db,
self::getDatabaseName()) . ' AND table_name=' . self::dbToString($db,
$tableName) . ' AND column_name=' . self::dbToString($db, $firsColumn));
foreach ($rows as $row) {
$indexCols = self::dbGetIndexColumns($tableName, $row['INDEX_NAME']);
if (count($indexCols) === count($cols) && count(array_diff($cols, $indexCols)) === 0) {
return $row['INDEX_NAME'];
}
}
}
return false;
}
/**
* @param $tableName string Name of the table
* @param $indexName string Name of the index
* @return string[] Names of the columns
* @throws PrestaShopDatabaseException
*/
public static function dbGetIndexColumns($tableName, $indexName)
{
$columns = [];
if (is_string($tableName) && is_string($indexName)) {
$db = Db::getInstance();
$rows = self::dbSelectRows('SELECT SEQ_IN_INDEX, COLUMN_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE '
. 'table_schema=' . self::dbToString($db, self::getDatabaseName())
. ' AND table_name=' . self::dbToString($db, $tableName)
. ' AND index_name=' . self::dbToString($db, $indexName)
. ' ORDER BY SEQ_IN_INDEX ASC'
);
foreach ($rows as $row) {
$columns[(int)$row['SEQ_IN_INDEX']] = $row['COLUMN_NAME'];
}
}
return $columns;
}
/**
* @param $tableName string Name of the table
* @param $columns string[] Names of columns in the index
* @param $indexName string|null Name of the index (optional)
* @return bool true if index has been created
*/
public static function dbCreateIndexIfNotExists($tableName, $columns, $indexName = null)
{
$cols = [];
if (is_string($tableName) && (is_string($columns) || is_array($columns))) {
if (!is_array($columns)) {
$cols[0] = $columns;
} else {
$cols = $columns;
}
if (count($cols) > 0) {
$db = Db::getInstance();
if (self::dbIndexExists($tableName, $columns) === false) {
$colsList = '';
foreach ($cols as $col) {
if (Tools::strlen($colsList) > 0) {
$colsList .= ',';
}
$colsList .= '`' . $db->escape(trim($col)) . '`';
}
if (self::dbExecuteSQL('ALTER TABLE `' . $db->escape($tableName) . '` ADD INDEX '.($indexName ? '`' . $db->escape($indexName) . '` ' : '').'(' . $colsList . ');')) {
self::addLog("Utils | Index created for table $tableName on column (" . $colsList . ')', 1);
return true;
}
}
}
}
return false;
}
/**
* @param $tableName string Name of the table
* @param $columns string[] Names of columns in the index
* @return bool true if the index has been deleted
*/
public static function dbDeleteIndexIfExists($tableName, $columns)
{
$cols = [];
if (is_string($tableName) && (is_string($columns) || is_array($columns))) {
if (!is_array($columns)) {
$cols[0] = $columns;
} else {
$cols = $columns;
}
if (count($cols) > 0) {
$db = Db::getInstance();
$indexName = self::dbIndexExists($tableName, $columns);
if ($indexName !== false) {
$colsList = '';
foreach ($cols as $col) {
if (Tools::strlen($colsList) > 0) {
$colsList .= ',';
}
$colsList .= '`' . $db->escape(trim($col)) . '`';
}
if (self::dbExecuteSQL('ALTER TABLE `' . $db->escape($tableName) . '` DROP INDEX `' . $db->escape($indexName) . '`;')) {
self::addLog("Utils | Index $indexName deleted for table $tableName on column (" . $colsList . ')', 1);
return true;
}
}
}
}
return false;
}
public static function dbDeleteIndexByName($tableName, $indexName, $logOnError = true, $throwOnError = false) {
$db = Db::getInstance();
self::dbExecuteSQL('ALTER TABLE `' . $db->escape($tableName) . '` DROP INDEX `' . $db->escape($indexName) . '`;', $logOnError, $throwOnError);
}
/**
* @param string $sql SQL query to execute
* @param bool $logOnError true if you want errors to be logged
* @param bool $throwOnError true if you want errors to throw PrestaShopDatabaseException
* @return mixed First value of the first row
* @throws PrestaShopDatabaseException
*/
public static function dbGetValue($sql, $logOnError = true, $throwOnError = false)
{
$row = self::dbSelectRows($sql, $logOnError, $throwOnError);
if (is_array($row) && count($row) > 0 && is_array($row[0]) && count($row[0]) > 0) {
return array_pop($row[0]);
}
return null;
}
/**
* @param string $sql SQL query to execute
* @param bool $logOnError true if you want errors to be logged
* @param bool $throwOnError true if you want errors to throw PrestaShopDatabaseException
* @return mixed First row
* @throws PrestaShopDatabaseException
*/
public static function dbGetRow($sql, $logOnError = true, $throwOnError = false)
{
$row = self::dbSelectRows($sql, $logOnError, $throwOnError);
if (is_array($row) && count($row) > 0 && is_array($row[0])) {
return $row[0];
}
return null;
}
public static function getModuleInstanceById($id_module)
{
// Some fu**ing modules (like pohodaconnector) modify the shop context in the constructor so we set it back
$currentShopContext = Shop::getContextShopID(true);
$moduleInstance = Module::getInstanceById($id_module);
if ($currentShopContext && Shop::isFeatureActive()) {
Shop::setContext(Shop::CONTEXT_SHOP, $currentShopContext);
}
return $moduleInstance;
}
/**
* @return string Caller information s a string : file:line::function()
*/
private static function getCallerInfos()
{
$traces = debug_backtrace();
if (isset($traces[2])) {
return $traces[1]['file'] . ':' . $traces[1]['line'] . '::' . $traces[2]['function'] . '()';
}
return '?';
}
/**
* @param string $filePathContains
* @param string $functionName
* @return bool
*/
public static function isCaller($filePathContains, $functionName)
{
$traces = debug_backtrace();
if (isset($traces[2])) {
return strpos($filePathContains, $traces[2]['file']) >= 0 && strcmp($functionName, $traces[2]['function']) === 0;
}
return false;
}
/**
* @return string
*/
public static function getStackTrace()
{
$e = new \Exception;
return $e->getTraceAsString();
}
/**
* jTraceEx() - provide a Java style exception trace
* @param \Throwable $e
* @param array $seen array passed to recursive calls to accumulate trace lines already seen leave as NULL when
* calling this function
* @return string One entry per trace line
*/
public static function jTraceEx($e, $seen = null)
{
$starter = $seen ? 'Caused by: ' : '';
$result = array();
if (!$seen) $seen = array();
$trace = $e->getTrace();
$prev = $e->getPrevious();
$result[] = sprintf('%s%s: %s', $starter, get_class($e), $e->getMessage());
$file = $e->getFile();
$line = $e->getLine();
while (true) {
$current = "$file:$line";
if (is_array($seen) && in_array($current, $seen)) {
$result[] = sprintf(' ... %d more', count($trace) + 1);
break;
}
$result[] = sprintf(' at %s%s%s(%s%s%s)',
count($trace) && array_key_exists('class', $trace[0]) ? str_replace('\\', '.', $trace[0]['class']) : '',
count($trace) && array_key_exists('class', $trace[0]) && array_key_exists('function', $trace[0]) ? '.' : '',
count($trace) && array_key_exists('function', $trace[0]) ? str_replace('\\', '.', $trace[0]['function']) : '(main)',
$line === null ? $file : basename($file),
$line === null ? '' : ':',
$line === null ? '' : $line);
if (is_array($seen))
$seen[] = "$file:$line";
if (!count($trace))
break;
$file = array_key_exists('file', $trace[0]) ? $trace[0]['file'] : 'Unknown Source';
$line = array_key_exists('file', $trace[0]) && array_key_exists('line', $trace[0]) && $trace[0]['line'] ? $trace[0]['line'] : null;
array_shift($trace);
}
$result = join("\n", $result);
if ($prev) {
$result .= "\n" . self::jTraceEx($prev, $seen);
}
return $result;
}
public static function getRequestHeaderValue($headerName)
{
$headerNameLower = Tools::strtolower($headerName);
$headers = self::getAllHeaders();
if (array_key_exists($headerName, $headers)) {
return $headers[$headerNameLower];
}
return null;
}
public static function getAllHeaders()
{
static $headers = null;
if ($headers === null) {
$headers = [];
foreach ($_SERVER as $name => $value) {
if (Tools::substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-',
Tools::strtolower(str_replace('_', ' ', Tools::substr($name, 5))))] = $value;
}
}
}
return $headers;
}
public static function currentVisitorAcceptWebp()
{
$accept = self::getCookie('jpresta_accept_webp')
|| (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false);
if (!self::isAjax() && self::getCookie('jpresta_accept_webp') === null) {
self::setCookie('jpresta_accept_webp',
(int)(isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false),
60 * 24 * 365);
}
return $accept;
}
public static function setCookie($name, $value, $expiresInMinutes)
{
if (!headers_sent()) {
if (PHP_VERSION_ID <= 50200) {
/* PHP version > 5.2.0 */
setcookie($name, $value, time() + 60 * (int)$expiresInMinutes, '/', '', 0);
} else {
setcookie($name, $value, time() + 60 * (int)$expiresInMinutes, '/', '', 0, false);
}
}
}
public static function getCookie($cookieName, $defaultValue = null)
{
if (array_key_exists($cookieName, $_COOKIE)) {
// Necessary to avoid errors in Prestashop Addons validator
foreach ($_COOKIE as $key => $cookieValue) {
if ($key === $cookieName) {
return $cookieValue;
}
}
}
return $defaultValue;
}
public static function dbToInt($val)
{
if ($val && !empty($val)) {
if (is_numeric($val)) {
// Preserve unsigned integers for 32bit systems
return $val;
}
}
return 'NULL';
}
public static function dbWhereIntEqual($col, $val)
{
if ($val && !empty($val)) {
if (is_numeric($val)) {
// Preserve unsigned integers for 32bit systems
return '`' . $col . '`=' . $val;
}
}
return '`' . $col . '` IS NULL';
}
/**
* @param $db Db
* @param $val
* @return string
*/
public static function dbToString($db, $val) {
if ($val && !empty($val)) {
return '\'' . $db->escape($val, true) . '\'';
}
else {
return 'NULL';
}
}
public static function getCanonicalHookNames()
{
if (Tools::version_compare(_PS_VERSION_, '8', '>=')) {
$cacheId = 'hook_canonical_names';
if (!Cache::isStored($cacheId)) {
$databaseResults = Db::getInstance()->executeS('SELECT name, alias FROM `' . _DB_PREFIX_ . 'hook_alias`');
$hooksByAlias = [];
if ($databaseResults) {
foreach ($databaseResults as $record) {
$hooksByAlias[Tools::strtolower($record['alias'])] = $record['name'];
}
}
Cache::store($cacheId, $hooksByAlias);
return $hooksByAlias;
}
return Cache::retrieve($cacheId);
} else {
return Hook::getHookAliasList();
}
}
public static function getConfigurationAllShop($key, $default = false, $idLang = null)
{
if (Tools::version_compare(_PS_VERSION_, '1.7', '<')) {
if (Configuration::hasKey($key, $idLang, 0, 0)) {
return Configuration::get($key, $idLang, 0, 0);
}
return $default;
}
return Configuration::get($key, $idLang, 0, 0, $default);
}
public static function getConfigurationByShopId($key, $id_shop, $default = false, $idLang = null)
{
if (Tools::version_compare(_PS_VERSION_, '1.7', '<')) {
if ($id_shop === null || !Shop::isFeatureActive()) {
$id_shop = Shop::getContextShopID(true);
} else {
$id_shop = (int)$id_shop;
}
if (Configuration::hasKey($key, $idLang, 0, $id_shop)) {
return Configuration::get($key, $idLang, 0, $id_shop);
}
return $default;
}
return Configuration::get($key, $idLang, 0, (int)$id_shop, $default);
}
public static function getConfigurationOfCurrentShop($key, $default = false, $idLang = null)
{
if (Tools::version_compare(_PS_VERSION_, '1.7', '<')) {
if (Configuration::hasKey($key, $idLang, null, null)) {
return Configuration::get($key, $idLang, null, null);
}
return $default;
}
return Configuration::get($key, $idLang, null, null, $default);
}
public static function saveConfigurationByShopId($key, $value, $id_shop)
{
Configuration::updateValue($key, $value, false, null, $id_shop);
}
public static function saveConfigurationAllShop($key, $value)
{
// Make sure no value is store for a specific shop
Configuration::deleteByName($key);
// Then save the new value in the global context
Configuration::updateValue($key, $value, false, 0, 0);
}
public static function saveConfigurationOfCurrentShop($key, $value)
{
Configuration::updateValue($key, $value, false);
}
public static function isModuleEnabledByShopId($id_module, $id_shop)
{
$ret = (int)JprestaUtils::dbGetValue('SELECT count(*) FROM `' . _DB_PREFIX_ . 'module_shop` WHERE `id_module` = ' . (int)$id_module . ' AND `id_shop` = ' . (int)$id_shop);
return $ret > 0;
}
public static function isModuleEnabled($module_name)
{
if (!Cache::isStored('Module::isEnabled' . $module_name)) {
$active = false;
$id_module = Module::getModuleIdByName($module_name);
if ($id_module && Db::getInstance()->getValue('SELECT `id_module` FROM `' . _DB_PREFIX_ . 'module_shop` WHERE `id_module` = ' . (int)$id_module . ' AND `id_shop` = ' . (int)Context::getContext()->shop->id)) {
$active = true;
}
Cache::store('Module::isEnabled' . $module_name, (bool)$active);
return (bool)$active;
}
return Cache::retrieve('Module::isEnabled' . $module_name);
}
/**
* Get a security token specific to JPresta modules. Create it if it does not exists
* @param null $id_shop
* @return string
*/
public static function getSecurityToken($id_shop = null)
{
if ($id_shop === null) {
$id_shop = Shop::getContextShopID();
}
$token = self::getConfigurationByShopId('pagecache_cron_token', $id_shop);
if (!$token) {
$token = self::generateRandomString();
self::saveConfigurationByShopId('pagecache_cron_token', $token, $id_shop);
}
return $token;
}
/**
* Fix a bug when multi-store has been disabled after creating some shops
* @return int[] List of shop IDs
*/
public static function getCompleteListOfShopsID() {
$allShopIds = Shop::getCompleteListOfShopsID();
if (!Shop::isFeatureActive() && count($allShopIds) !== 1) {
$allShopIds = [Shop::getContextShopID()];
}
return $allShopIds;
}
public static function generateRandomString($length = 16)
{
$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
$final_rand = '';
for ($i = 0; $i < $length; $i++) {
$final_rand .= $chars [rand(0, Tools::strlen($chars) - 1)];
}
return $final_rand;
}
/**
* @param $message
* @param int $severity 1 = info, 2 = warning, 3 = error, 4 = critical error
* @param null $errorCode
* @param null $objectType
* @param null $objectId
* @param bool $allowDuplicate
* @param null $idEmployee
*/
public static function addLog($message, $severity = 1, $errorCode = null, $objectType = null, $objectId = null, $allowDuplicate = false, $idEmployee = null)
{
if (class_exists('PrestaShopLogger')) {
// Since PS 1.6.0.2
PrestaShopLogger::addLog('JPresta | ' . str_replace('<', '&lt;', $message), $severity, $errorCode, $objectType, $objectId, $allowDuplicate, $idEmployee);
} else {
Logger::addLog('JPresta | ' . $message, $severity, $errorCode, $objectType, $objectId, $allowDuplicate, $idEmployee);
}
}
public static function isSearchEngine()
{
return (
isset($_SERVER['HTTP_USER_AGENT'])
&& preg_match('/bot|crawl|slurp|spider|mediapartners|gtmetrix|chrome-lighthouse/i', $_SERVER['HTTP_USER_AGENT'])
);
}
}
}