629 lines
18 KiB
PHP
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;
|
|
}
|
|
|
|
}
|