Files
2025-06-24 14:14:35 +02:00

629 lines
18 KiB
PHP

<?php
/**
* Redis Cache
* Version: 2.1.1
* Copyright (c) 2020-2022. Mateusz Szymański Teamwant
* https://teamwant.pl
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @author Teamwant <kontakt@teamwant.pl>
* @copyright Copyright 2020-2023 © Teamwant Mateusz Szymański All right reserved
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* @category Teamwant
* @package Teamwant
*/
namespace Teamwant\Prestashop17\Redis\Classes\Cache;
use CacheCore;
use Context;
use InvalidArgumentException;
use Predis\Client;
use Teamwant\Prestashop17\Redis\Classes\FileManager;
use Teamwant\Prestashop17\Redis\Classes\Validator;
use Throwable;
use Tools;
abstract class CacheRedis extends CacheCore
{
/**
* @var bool
*/
protected $is_connected = false;
/**
* @var Client
*/
private $client;
/**
* @var Validator
*/
private $validator;
/**
* @var bool
*/
private $usePrefix = false;
/**
* @var bool
*/
private $useCacheAdmin = true;
/**
* @var null|string
*/
private $prefix;
/**
* @var string
*/
private $controller_type;
/**
* @var int
*/
private $defalut_ttl = 0;
/**
* @var string
*/
private $redis_engine = null;
/**
* @var string
*/
private $type = 'predis'; //predis jest domyslnym silnikiem
/**
* @var bool|string
*/
private $domainHash = false;
/**
* @var array List of blacklisted tables for SQL cache, these tables won't be indexed
*/
public $blacklist = [
'cart',
'cart_cart_rule',
'cart_product',
'connections',
'connections_source',
'connections_page',
'customer',
'customer_group',
'customized_data',
'guest',
'pagenotfound',
'page_viewed',
'employee',
'log',
];
public $queryBlacklist = [];
public function __construct()
{
$this->validator = new Validator();
$c = Context::getContext();
if (!empty($c) && !empty($c->controller) && !empty($c->controller->controller_type)) {
$this->controller_type = $c->controller->controller_type;
}
$this->connect();
}
public function setQuery($query, $result)
{
if ($this->isBlacklist($query)) {
return;
}
$this->disableCacheForAddressPaymentAndCarrier();
if (!empty($this->queryBlacklist)) {
foreach ($this->queryBlacklist as $find) {
if (strpos($query, $find) !== false) {
return;
}
}
}
//\Db::getInstance()->query('CREATE TABLE IF NOT EXISTS `ps_debug1` ( `md5` VARCHAR(255) NOT NULL , `sql` LONGTEXT NOT NULL , UNIQUE (`md5`)) ENGINE = InnoDB;');
//\Db::getInstance()->insert(
// 'debug1',
// [
// 'md5' => $this->getQueryHash($query),
// 'sql' => str_replace("'", "\'", $query),
// ],
// false,
// false,
// \Db::INSERT_IGNORE
//);
if (empty($result)) {
$result = [];
}
$key = $this->getQueryHash($query);
$this->set($key, $result);
}
public function clearQuery($query)
{
$key = $this->getQueryHash($query);
$this->_delete($key);
}
public function getQueryHash($query)
{
//$key = $this->getKey(Tools::hashIV(preg_replace('/(\s)*/', ' ', $query)));
//
////jesli cache jest na czarnej liscie to trzeba go usunac
//foreach ($this->queryBlacklist as $find) {
// if (strpos($query, $find) !== false) {
// $this->_delete($key);
// }
//}
return $this->getKey(Tools::hashIV(preg_replace('/(\s)*/', ' ', $query)));
}
private function connectPredis(array $connectConfiguration)
{
$this->type = 'predis';
$this->is_connected = false;
try {
if (1 === count($connectConfiguration) && isset($connectConfiguration[0])) {
$this->client = new Client(
$connectConfiguration[0],
['cluster' => 'redis']
);
} else {
$this->client = new Client(
$connectConfiguration,
['cluster' => 'redis']
);
}
$this->client->connect();
$this->is_connected = $this->client->isConnected();
} catch (InvalidArgumentException $iae) {
if (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_) {
@trigger_error("Redis InvalidArgumentException, please check your server configuration. Turn off debug to hide this!", E_USER_NOTICE);
}
} catch (Throwable $thr) {
if (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_) {
@trigger_error("Redis Error, please check your server configuration. Turn off debug to hide this!", E_USER_NOTICE);
if (Tools::getIsset('redis_healthcheck_key')) {
echo '<p style="color:red">' . $thr->getMessage() . '</p>';
}
}
}
}
private function connectPhpredis(array $connectConfiguration)
{
$this->type = 'phpredis';
$this->is_connected = false;
try {
if (1 === count($connectConfiguration) && isset($connectConfiguration[0])) {
$connectionHost = isset($connectConfiguration[0]['host']) ? $connectConfiguration[0]['host'] : $connectConfiguration[0]['path'];
$this->client = new \Redis();
$this->client->connect(
sprintf('%s://%s', $connectConfiguration[0]['scheme'], $connectionHost),
$connectConfiguration[0]['port']
);
if (!empty($connectConfiguration[0]['username']) && !empty($connectConfiguration[0]['password'])) {
$this->client->auth(['user' => $connectConfiguration[0]['username'], 'pass' => $connectConfiguration[0]['password']]);
} elseif (!empty($connectConfiguration[0]['password'])) {
$this->client->auth(['pass' => $connectConfiguration[0]['password']]);
}
if (isset($connectConfiguration[0]['database'])) {
$this->client->select((int) $connectConfiguration[0]['database']);
}
$this->client->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_PHP);
$this->is_connected = $this->client->isConnected();
} else {
//todo: wykonac phpredis cluster
$this->connectPredis($connectConfiguration);
}
} catch (InvalidArgumentException $iae) {
if (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_) {
@trigger_error("Redis InvalidArgumentException, please check your server configuration. Turn off debug to hide this!", E_USER_NOTICE);
}
} catch (Throwable $thr) {
if (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_) {
@trigger_error("Redis Error, please check your server configuration. Turn off debug to hide this!", E_USER_NOTICE);
if (Tools::getIsset('redis_healthcheck_key')) {
echo '<p style="color:red">' . $thr->getMessage() . '</p>';
}
}
}
}
public function connect()
{
$connectConfiguration = $this->parseConnectionConfigFile();
if (empty($connectConfiguration) || empty($connectConfiguration[0])) {
$this->is_connected = false;
return;
}
if (extension_loaded('redis') && $this->redis_engine === 'phpredis') {
$this->connectPhpredis($connectConfiguration);
//} else if () { //credis
} else if (class_exists('\Predis\Client')) { // predis - domyslna opcja
$this->connectPredis($connectConfiguration);
} else {
$this->is_connected = false;
}
}
private function parseConnectionConfigFile()
{
$fileManager = new FileManager();
/** @var array $content */
$config = json_decode($fileManager->parseConfigFile('_RedisConfiguration.php'), true);
if (isset($config['_config']['use_cache_admin'])) {
$this->useCacheAdmin = (bool)$config['_config']['use_cache_admin'];
}
if (isset($config['_config']['use_prefix']) && !empty($config['_config']['prefix'])) {
$this->usePrefix = (bool)$config['_config']['use_prefix'];
$this->prefix = (string)$config['_config']['prefix'];
}
if (isset($config['_config']['use_multistore']) && $config['_config']['use_multistore']) {
$this->domainHash = $_SERVER['HTTP_HOST'];
}
if (!empty($config['_config']['defalut_ttl'])) {
$this->defalut_ttl = (int) $config['_config']['defalut_ttl'];
}
if (!empty($config['_config']['redis_engine'])) {
$this->redis_engine = $config['_config']['redis_engine'];
}
if (!empty($config['_servers'])) {
$redis_arr = [];
foreach ($config['_servers'] as $item) {
$item = $this->validator->validateRedisRow($item);
if (!$item) {
continue;
}
if ($item['scheme'] === 'unix') {
$item['path'] = $item['host'];
unset($item['host']);
}
$redis_arr[$item['alias']] = $item;
}
return array_values($redis_arr);
}
return null;
}
public static function flushAllDb()
{
$fileManager = new FileManager();
$validator = new Validator();
/** @var array $content */
$config = json_decode($fileManager->parseConfigFile('_RedisConfiguration.php'), true);
if (!empty($config['_servers'])) {
foreach ($config['_servers'] as $item) {
$item = $validator->validateRedisRow($item);
if (!$item) {
continue;
}
if ($item['scheme'] === 'unix') {
$item['path'] = $item['host'];
unset($item['host']);
}
$client = new Client($item);
$client->connect();
if ($client->isConnected()) {
$client->flushdb();
}
}
}
return true;
}
protected function _delete($key)
{
if ($this->isDisabled()) {
return false;
}
try {
return $this->client->del(
$this->getKey($key)
);
} catch (Throwable $thr) {
if (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_) {
@trigger_error("Redis Error, please check your server configuration. Turn off debug to hide this!", E_USER_NOTICE);
if (Tools::getIsset('redis_healthcheck_key')) {
echo '<p style="color:red">' . $thr->getMessage() . '</p>';
}
}
return false;
}
}
private function getKey(string $key)
{
if ($this->usePrefix) {
if (substr($key, 0, strlen($this->prefix)) === $this->prefix) {
return $key . ($this->domainHash ?: '');
}
return $this->prefix . $key . ($this->domainHash ?: '');
}
return $key . ($this->domainHash ?: '');
}
protected function _set($key, $value, $ttl = 0)
{
$measure = 0;
if (($ttl === NULL || $ttl === 0) && $this->defalut_ttl > 0) {
$ttl = $this->defalut_ttl;
}
if ($this->isDisabled()) {
return false;
}
if ($this->twredisDisableProductPriceCache()) {
return false;
}
if ($this->disableCacheForOrderPage()) {
return false;
}
try {
if ($measure) {
$start = microtime(true);
}
if ($this->type == 'phpredis') {
if ($ttl === NULL || $ttl === 0) {
$result = $this->client->set($this->getKey($key), $value);
} else {
$result = $this->client->setex($this->getKey($key), ($ttl * 60), $value);
}
} else {
if ($ttl === NULL || $ttl === 0) {
$result = $this->client->set($this->getKey($key), serialize($value));
} else {
$result = $this->client->set($this->getKey($key), serialize($value), 'ex', ($ttl * 60));
}
}
if ($measure) {
$time_elapsed_secs = microtime(true) - $start;
file_put_contents(_PS_ROOT_DIR_ . '/measure-redis.txt', $time_elapsed_secs . PHP_EOL, FILE_APPEND);
}
return false;
} catch (Throwable $thr) {
if (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_) {
@trigger_error("Redis Error, please check your server configuration. Turn off debug to hide this!", E_USER_NOTICE);
if (Tools::getIsset('redis_healthcheck_key')) {
echo '<p style="color:red">' . $thr->getMessage() . '</p>';
}
}
$result = false;
}
if ($result === false) {
$this->setAdjustTableCacheSize(true);
}
return $result;
}
protected function _get($key)
{
if ($this->isDisabled()) {
return false;
}
if ($this->twredisDisableProductPriceCache()) {
return false;
}
if ($this->disableCacheForOrderPage()) {
return false;
}
if ($this->type == 'phpredis') {
return $this->client->get(
$this->getKey($key)
);
}
return unserialize($this->client->get(
$this->getKey($key)
));
}
protected function _exists($key)
{
if ($this->isDisabled()) {
return false;
}
return $this->client->exists($this->getKey($key));
}
protected function _writeKeys()
{
if (!$this->is_connected) {
return false;
}
return true;
}
protected function _flush()
{
if ($this->isDisabled()) {
return false;
}
return $this->client->flushall();
}
/**
* Metoda sprawdza czy redis faktycznie ma zostac uruchomiony po warunkach, np. w adminie
*
* @return bool
*/
private function isDisabled()
{
if (false === $this->useCacheAdmin) {
if (
$this->controller_type && 'admin' === $this->controller_type
|| defined('_PS_ADMIN_DIR_')
) {
return true;
}
}
return false;
}
private function twredisDisableProductPriceCache()
{
try {
if (!empty(Context::getContext()->shop) && \Configuration::get('twredis_disable_product_price_cache', null, null, null, 0)) {
$arr = debug_backtrace(2, 10);
if (!empty($arr)) {
foreach ($arr as $k => $v) {
if ($v['function'] == 'priceCalculation') {
return true;
}
}
}
}
} catch (Throwable $e) {
//do nothing
}
return false;
}
/**
* jesli true to pomija controller = order
* @return bool
*/
private function disableCacheForOrderPage() {
$enabled = false;
if (Context::getContext()->disableCacheForOrderPage_initialized) {
return Context::getContext()->disableCacheForOrderPage;
}
if (!empty(Context::getContext()->shop) && \Configuration::get('twredis_disable_cache_for_order_page', null, null, null, 0)) {
$enabled = true;
}
$disable = false;
if ($enabled) {
$controller = Tools::getValue('controller');
if ($controller == 'order') {
$disable = true;
} else if ($controller == 'supercheckout') {
$disable = true;
}
}
Context::getContext()->disableCacheForOrderPage_initialized = true;
Context::getContext()->disableCacheForOrderPage = $disable;
return $disable;
}
/**
* ignorujemy sqlki jesli configuration na to pozwala
* @return void
*/
private function disableCacheForAddressPaymentAndCarrier() {
$enable = false;
if (empty(Context::getContext()->shop)) {
return;
}
if (Context::getContext()->disableCacheForAddressPaymentAndCarrier_initialized) {
$enable = Context::getContext()->disableCacheForAddressPaymentAndCarrier;
} else {
if (\Configuration::get('twredis_disable_cache_for_address_payment_and_carrier', null, null, null, 0)) {
$enable = true;
}
Context::getContext()->disableCacheForAddressPaymentAndCarrier_initialized = true;
Context::getContext()->disableCacheForAddressPaymentAndCarrier = $enable;
}
if (!$enable) {
return;
}
$this->queryBlacklist[] = 'FROM `ps_carrier`';
$this->queryBlacklist[] = 'FROM `ps_address` a';
$this->queryBlacklist[] = 'SELECT d.`price` FROM `ps_delivery` d';
$this->queryBlacklist[] = 'ps_checkpayment';
$this->queryBlacklist[] = 'ps_wirepayment';
$this->queryBlacklist[] = 'h.name = "actionPaymentPreferencesForm"';
$this->queryBlacklist[] = 'adminpaymentpreferences';
}
protected function isBlacklist($query)
{
foreach ($this->blacklist as $find) {
if (false !== strpos($query, _DB_PREFIX_ . $find)) {
return true;
}
}
return false;
}
}