diff --git a/composer.json b/composer.json
index d9e8e74..6871003 100644
--- a/composer.json
+++ b/composer.json
@@ -5,10 +5,12 @@
},
"require": {
"php": ">=5.4.20",
- "colinmollenhour/credis": "^1.13",
+ "colinmollenhour/credis": "^1.11.1",
"twig/twig": "^3.0",
"mustache/mustache": "^2.0",
"smarty/smarty": "^3.0",
- "phpfastcache/phpfastcache": "^7.0"
+ "phpfastcache/phpfastcache": "^7.0",
+ "colinmollenhour/php-redis-session-abstract": "~1.4.0",
+ "colinmollenhour/cache-backend-redis": "^1.17"
}
}
diff --git a/system/session/Redis/AbstractCM/Handler.php b/system/session/Redis/AbstractCM/Handler.php
new file mode 100644
index 0000000..23c8a63
--- /dev/null
+++ b/system/session/Redis/AbstractCM/Handler.php
@@ -0,0 +1,61 @@
+=')) {
+ $sessionId = self::SESSION_PREFIX . $id;
+ $this->_redis->pipeline()
+ ->select($this->_dbNum)
+ ->hMSet($sessionId, array(
+ 'data' => $this->_encodeData($data),
+ 'lock' => 0, // 0 so that next lock attempt will get 1
+ ))
+ ->hIncrBy($sessionId, 'writes', 1)
+ ->expire($sessionId, min((int)$lifetime, (int)$this->_maxLifetime))
+ ->exec();
+ } else {
+ $sessionId = self::SESSION_PREFIX . $id;
+ $redis = $this->_redis;
+ $redis->select($this->_dbNum);
+ $redis->hMSet($sessionId, array(
+ 'data' => $this->_encodeData(serialize($data)),
+ 'lock' => 0, // 0 so that next lock attempt will get 1
+ ));
+ $redis->hIncrBy($sessionId, 'writes', 1);
+
+ $redis->expire($sessionId, min((int)$lifetime, (int)$this->_maxLifetime));
+ //->exec();
+ //$redis->exec();
+ }
+
+ }
+
+ public function setName($name)
+ {
+ $this->_name = $name;
+ }
+}
diff --git a/system/session/Redis/Config.php b/system/session/Redis/Config.php
new file mode 100644
index 0000000..1b2b2ae
--- /dev/null
+++ b/system/session/Redis/Config.php
@@ -0,0 +1,271 @@
+engine = \Phacil\Framework\Registry::getInstance();
+ $this->logLevel = \Phacil\Framework\Config::Debug() ? 6 : $this->logLevel;
+ $fg = [];
+ foreach (get_class_methods($this) as $key => $value) {
+ # code...
+ if ($value != '__construct' && $value != 'getLogLevel')
+ $fg[$value] = $this->$value();
+ }
+
+ return;
+ }
+
+ /**
+ *
+ * {@inheritdoc}
+ */
+ function getLogLevel() {
+ return $this->engine->config->get('session_redis_log_level') ?: $this->logLevel;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHost()
+ {
+ return $this->engine->config->get('session_redis_dsn') ? : self::PARAM_HOST;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPort()
+ {
+ return $this->engine->config->get('session_redis_port') ?: self::PARAM_PORT;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDatabase()
+ {
+ return (string)$this->engine->config->get('session_redis_database') ?: self::PARAM_DATABASE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPassword()
+ {
+ return $this->engine->config->get('session_redis_password') ?: '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTimeout()
+ {
+ return $this->engine->config->get('session_redis_timeout') ? : self::PARAM_TIMEOUT;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPersistentIdentifier()
+ {
+ return $this->engine->config->get('session_redis_persistent_id') ?: '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCompressionThreshold()
+ {
+ return $this->engine->config->get('session_redis_compression_threshold') ?: self::DEFAULT_COMPRESSION_THRESHOLD ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCompressionLibrary()
+ {
+ return $this->engine->config->get('session_redis_compression_library') ?: (self::PARAM_COMPRESSION_LIBRARY);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMaxConcurrency()
+ {
+ return $this->engine->config->get('session_redis_max_concurrency') ?: (self::PARAM_MAX_CONCURRENCY);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMaxLifetime()
+ {
+ return $this->engine->config->get('session_redis_max_lifetime') ?: self::SESSION_MAX_LIFETIME;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMinLifetime()
+ {
+ return (int)$this->engine->config->get('session_redis_min_lifetime') ?: (self::PARAM_MIN_LIFETIME);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDisableLocking()
+ {
+ return (bool)$this->engine->config->get('session_redis_disable_locking') ?: (self::PARAM_DISABLE_LOCKING);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBotLifetime()
+ {
+ return (int) $this->engine->config->get('session_redis_bot_lifetime') ?: (self::PARAM_BOT_LIFETIME);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBotFirstLifetime()
+ {
+ return (string)$this->engine->config->get('session_redis_bot_first_lifetime') ?: (self::PARAM_BOT_FIRST_LIFETIME);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFirstLifetime()
+ {
+ return (int)$this->engine->config->get('session_redis_first_lifetime') ?: (self::PARAM_FIRST_LIFETIME);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBreakAfter()
+ {
+ return (int)$this->engine->config->get('session_redis_break_after') ?: (self::PARAM_BREAK_AFTER);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLifetime()
+ {
+ return (int)$this->engine->config->get('session_redis_expire') ?: self::PARAM_SESSION_LIFETIME;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSentinelServers()
+ {
+ return $this->engine->config->get('session_redis_sentinel_servers') ?: null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSentinelMaster()
+ {
+ return $this->engine->config->get('session_redis_sentinel_master') ?: null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSentinelVerifyMaster()
+ {
+ return $this->engine->config->get('session_redis_verify_master') ?: (null);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSentinelConnectRetries()
+ {
+ return $this->engine->config->get('session_redis_sentinel_connect_retries') ?: (self::PARAM_SENTINEL_CONNECT_RETRIES);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFailAfter()
+ {
+ return (int)$this->engine->config->get('session_redis_fail_after') ?: self::DEFAULT_FAIL_AFTER;
+ }
+
+}
\ No newline at end of file
diff --git a/system/session/Redis/Handler.php b/system/session/Redis/Handler.php
new file mode 100644
index 0000000..2cdd9c3
--- /dev/null
+++ b/system/session/Redis/Handler.php
@@ -0,0 +1,176 @@
+config = $config;
+ $this->logger = $logger;
+ }
+
+
+ /**
+ * Get connection
+ *
+ * @return \Cm\RedisSession\Handler
+ * @throws Exception
+ */
+ private function getConnection()
+ {
+ $pid = getmypid();
+ if (!isset($this->connection[$pid])) {
+ try {
+ //$this->connection[$pid] = new \Cm\RedisSession\Handler($this->config, $this->logger);
+ $this->connection[$pid] = new \Phacil\Framework\Session\Redis\AbstractCM\Handler($this->config, $this->logger);
+ } catch (ConnectionFailedException $e) {
+ throw new Exception(($e->getMessage()));
+ }
+ }
+ return $this->connection[$pid];
+ }
+
+ function setName($name){
+ $this->name = $name;
+ }
+
+ /**
+ * Open session
+ *
+ * @param string $savePath ignored
+ * @param string $sessionName ignored
+ * @return bool
+ * @throws Exception
+ */
+ public function open($savePath, $sessionName)
+ {
+ return $this->getConnection()->open($savePath, $sessionName);
+ }
+
+ /**
+ * Fetch session data
+ *
+ * @param string $sessionId
+ * @return string
+ * @throws ConcurrentConnectionsExceededException
+ * @throws Exception
+ */
+ public function read($sessionId)
+ {
+ try {
+ return $this->getConnection()->read($sessionId);
+ } catch (ConcurrentConnectionsExceededException $e) {
+ throw new Exception($e->getMessage(), 1);
+
+ }
+ }
+
+ /**
+ * Update session
+ *
+ * @param string $sessionId
+ * @param string $sessionData
+ * @return boolean
+ * @throws Exception
+ */
+ public function write($sessionId, $sessionData)
+ {
+ try {
+ //$this->getConnection()->setName($this->name);
+ $cha = $this->getConnection()->write($sessionId, $sessionData);
+ return $cha;
+ } catch (ConcurrentConnectionsExceededException $e) {
+ throw new Exception($e->getMessage(), 1);
+ }
+
+ }
+
+ /**
+ * Destroy session
+ *
+ * @param string $sessionId
+ * @return boolean
+ * @throws Exception
+ */
+ public function destroy($sessionId)
+ {
+ return $this->getConnection()->destroy($sessionId);
+ }
+
+ /**
+ * Overridden to prevent calling getLifeTime at shutdown
+ *
+ * @return bool
+ * @throws Exception
+ */
+ public function close()
+ {
+ return $this->getConnection()->close();
+ }
+
+ /**
+ * Garbage collection
+ *
+ * @param int $maxLifeTime ignored
+ * @return boolean
+ * @throws Exception
+ * @SuppressWarnings(PHPMD.ShortMethodName)
+ */
+ public function gc($maxLifeTime)
+ {
+ return $this->getConnection()->gc($maxLifeTime);
+ }
+
+ /**
+ * Get the number of failed lock attempts
+ *
+ * @return int
+ * @throws Exception
+ */
+ public function getFailedLockAttempts()
+ {
+ return $this->getConnection()->getFailedLockAttempts();
+ }
+}
\ No newline at end of file
diff --git a/system/session/Redis/Logger.php b/system/session/Redis/Logger.php
new file mode 100644
index 0000000..cd0d63a
--- /dev/null
+++ b/system/session/Redis/Logger.php
@@ -0,0 +1,73 @@
+logLevel = $config->getLogLevel() ?: self::ALERT;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setLogLevel($level)
+ {
+ $this->logLevel = $level;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function log($message, $level)
+ {
+ $engine = \Phacil\Framework\Registry::getInstance();
+ if ($this->logLevel >= $level) {
+ switch ($level) {
+ case self::EMERGENCY:
+ case self::CRITICAL:
+ case self::ERROR:
+ trigger_error($message, E_ERROR);
+ break;
+ case self::ALERT:
+ case self::WARNING:
+ //$this->logger->alert($message);
+ $engine->log->write($message,E_WARNING);
+ break;
+ case self::NOTICE:
+ case self::INFO:
+ $engine->log->write($message);
+ break;
+ default:
+ $engine->log->write($message);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function logException(\Exception $e)
+ {
+ throw new Exception($e->getMessage());
+ }
+}
diff --git a/system/session/autoload.php b/system/session/autoload.php
index 77c9d87..fa05138 100644
--- a/system/session/autoload.php
+++ b/system/session/autoload.php
@@ -26,7 +26,7 @@ use Phacil\Framework\Config;
* @since 1.0.0
* @package Phacil\Framework
*/
-final class Session
+class Session
{
/**
*
@@ -61,6 +61,14 @@ final class Session
*/
public $redisKey;
+ /**
+ *
+ * @var \Phacil\Framework\Session\Redis\Handler
+ */
+ protected $saveHandler;
+
+ protected $redisExpire;
+
/**
*
* @param bool $redis
@@ -75,12 +83,14 @@ final class Session
{
$this->name = (Config::SESSION_PREFIX() ?: 'SESS') . (isset($_SERVER['REMOTE_ADDR']) ? md5($_SERVER['REMOTE_ADDR']) : md5(date("dmY")));
+ //define('SESSION_PREFIX_INTERNAL_REDIS', Config::REDIS_SESSION_PREFIX() ?: 'phacil_');
+
+ $this->redis($redis, $redisDSN, $redisPort, $redisPass, $redis_expire, $redis_prefix);
+
if (!session_id()) {
$this->openSession();
}
- $this->redis($redis, $redisDSN, $redisPort, $redisPass, $redis_expire, $redis_prefix);
-
if (session_name() === $this->name) {
$this->data =& $_SESSION;
} else {
@@ -130,6 +140,16 @@ final class Session
if (!$redis)
return false;
+ $redisConfig = new \Phacil\Framework\Session\Redis\Config();
+
+ $this->saveHandler = new \Phacil\Framework\Session\Redis\Handler($redisConfig, new \Phacil\Framework\Session\Redis\Logger($redisConfig));
+
+ $this->saveHandler->setName($this->name);
+
+ $a = $this->registerSaveHandler();
+
+ return;
+
$this->redisExpire = ($redis_expire) ?: session_cache_expire() * 60;
$this->redisPrefix = ($redis_prefix) ?: 'phacil_';
$this->generateRedisKey();
@@ -146,6 +166,24 @@ final class Session
return $this->redis;
}
+ /**
+ * Register save handler
+ *
+ * @return bool
+ */
+ protected function registerSaveHandler()
+ {
+ return session_set_save_handler(
+ //$this->saveHandler
+ [$this->saveHandler, 'open'],
+ [$this->saveHandler, 'close'],
+ [$this->saveHandler, 'read'],
+ [$this->saveHandler, 'write'],
+ [$this->saveHandler, 'destroy'],
+ [$this->saveHandler, 'gc']
+ );
+ }
+
/**
* Generate the Redis Session KEY
*
@@ -167,6 +205,7 @@ final class Session
*/
private function closeSession($force = false)
{
+ return ;
if (session_status() == PHP_SESSION_ACTIVE || $force) {
session_unset();
session_destroy();
@@ -186,21 +225,7 @@ final class Session
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443;
}
- /**
- * Set the Redis session data
- * @return void
- * @since 2.0.0
- */
- public function __destruct()
- {
- if ($this->redis) {
- $this->generateRedisKey();
-
- $this->redis->set($this->redisKey, serialize($_SESSION));
-
- $this->redis->expire($this->redisKey, ($this->redisExpire));
- }
- }
+
/**
* Flush all session data
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/.github/workflows/php.yml b/system/vendor/colinmollenhour/cache-backend-redis/.github/workflows/php.yml
new file mode 100644
index 0000000..607241c
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/.github/workflows/php.yml
@@ -0,0 +1,50 @@
+name: PHPUnit
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+
+ runs-on: ubuntu-latest
+
+ services:
+ redis:
+ image: redis
+ options: >-
+ --health-cmd "redis-cli ping"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+ ports:
+ - 6379:6379
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+
+ - name: Cache Composer packages
+ id: composer-cache
+ uses: actions/cache@v3
+ with:
+ path: vendor
+ key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-php-
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress
+
+ - name: Run PHPUnit test suite
+ run: composer run-script test
+
+ - name: Run PHP CS Fixer
+ run: composer run-script php-cs-fixer -- --dry-run
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/.gitignore b/system/vendor/colinmollenhour/cache-backend-redis/.gitignore
new file mode 100644
index 0000000..9e2f056
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/.gitignore
@@ -0,0 +1,4 @@
+.basedir
+vendor
+composer.lock
+/.php-cs-fixer.cache
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/.gitmodules b/system/vendor/colinmollenhour/cache-backend-redis/.gitmodules
new file mode 100644
index 0000000..cc36417
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "lib/Credis"]
+ path = lib/Credis
+ url = https://github.com/colinmollenhour/credis
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/.php-cs-fixer.dist.php b/system/vendor/colinmollenhour/cache-backend-redis/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..a1945d5
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/.php-cs-fixer.dist.php
@@ -0,0 +1,21 @@
+setRules([
+ '@PSR12' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in([
+ 'Cm/',
+ 'tests/',
+ ])
+ ->name('*.php')
+ ->ignoreDotFiles(true)
+ ->ignoreVCS(true)
+ );
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php b/system/vendor/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php
new file mode 100644
index 0000000..5d91303
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php
@@ -0,0 +1,1416 @@
+forceStandalone = isset($options['force_standalone']) && $options['force_standalone'];
+ $clientOptions->connectRetries = isset($options['connect_retries']) ? (int) $options['connect_retries'] : self::DEFAULT_CONNECT_RETRIES;
+ $clientOptions->readTimeout = isset($options['read_timeout']) ? (float) $options['read_timeout'] : null;
+ $clientOptions->password = $options['password'] ?? null;
+ $clientOptions->username = $options['username'] ?? null;
+ $clientOptions->database = isset($options['database']) ? (int) $options['database'] : 0;
+ $clientOptions->persistent = $options['persistent'] ?? '';
+ $clientOptions->timeout = $options['timeout'] ?? self::DEFAULT_CONNECT_TIMEOUT;
+ return $clientOptions;
+ }
+
+ /**
+ * Construct Zend_Cache Redis backend
+ * @param array $options
+ * @throws Zend_Cache_Exception
+ * @throws CredisException
+ * @noinspection PhpMissingParentConstructorInspection
+ */
+ public function __construct($options = array())
+ {
+ if (empty($options['server']) && empty($options['cluster'])) {
+ Zend_Cache::throwException('Redis \'server\' not specified.');
+ }
+
+ $this->_clientOptions = $this->getClientOptions($options);
+
+ // If 'sentinel_master' is specified then server is actually sentinel and master address should be fetched from server.
+ $sentinelMaster = empty($options['sentinel_master']) ? null : $options['sentinel_master'];
+ if ($sentinelMaster) {
+ $sentinelClientOptions = isset($options['sentinel']) && is_array($options['sentinel'])
+ ? $this->getClientOptions($options['sentinel'] + $options)
+ : $this->_clientOptions;
+ $servers = preg_split('/\s*,\s*/', trim($options['server']), -1, PREG_SPLIT_NO_EMPTY);
+ $sentinel = null;
+ $exception = null;
+ for ($i = 0; $i <= $sentinelClientOptions->connectRetries; $i++) { // Try each sentinel in round-robin fashion
+ foreach ($servers as $server) {
+ try {
+ $sentinelClient = new Credis_Client($server, null, $sentinelClientOptions->timeout, $sentinelClientOptions->persistent);
+ $sentinelClient->forceStandalone();
+ $sentinelClient->setMaxConnectRetries(0);
+ if ($sentinelClientOptions->readTimeout) {
+ $sentinelClient->setReadTimeout($sentinelClientOptions->readTimeout);
+ }
+ if ($sentinelClientOptions->password) {
+ $sentinelClient->auth($sentinelClientOptions->password) or Zend_Cache::throwException('Unable to authenticate with the redis sentinel.');
+ }
+ $sentinel = new Credis_Sentinel($sentinelClient);
+ $sentinel
+ ->setClientTimeout($this->_clientOptions->timeout)
+ ->setClientPersistent($this->_clientOptions->persistent);
+ $redisMaster = $sentinel->getMasterClient($sentinelMaster);
+ $this->_applyClientOptions($redisMaster);
+
+ // Verify connected server is actually master as per Sentinel client spec
+ if (! empty($options['sentinel_master_verify'])) {
+ $roleData = $redisMaster->role();
+ if (! $roleData || $roleData[0] != 'master') {
+ usleep(100000); // Sleep 100ms and try again
+ $redisMaster = $sentinel->getMasterClient($sentinelMaster);
+ $this->_applyClientOptions($redisMaster);
+ $roleData = $redisMaster->role();
+ if (! $roleData || $roleData[0] != 'master') {
+ Zend_Cache::throwException('Unable to determine master redis server.');
+ }
+ }
+ }
+
+ $this->_redis = $redisMaster;
+ break 2;
+ } catch (Exception $e) {
+ unset($sentinelClient);
+ $exception = $e;
+ }
+ }
+ }
+ if (! $this->_redis) {
+ Zend_Cache::throwException('Unable to connect to a redis sentinel: '.$exception->getMessage(), $exception);
+ }
+
+ // Optionally use read slaves - will only be used for 'load' operation
+ if (! empty($options['load_from_slaves'])) {
+ $slaves = $sentinel->getSlaveClients($sentinelMaster);
+ if ($slaves) {
+ if ($options['load_from_slaves'] == 2) {
+ $slaves[] = $this->_redis; // Also send reads to the master
+ }
+ $slaveSelect = isset($options['slave_select_callable']) && is_callable($options['slave_select_callable']) ? $options['slave_select_callable'] : null;
+ if ($slaveSelect) {
+ $slave = $slaveSelect($slaves, $this->_redis);
+ } else {
+ $slaveKey = array_rand($slaves);
+ $slave = $slaves[$slaveKey]; /* @var $slave Credis_Client */
+ }
+ if ($slave instanceof Credis_Client && $slave !== $this->_redis) {
+ try {
+ $this->_applyClientOptions($slave, true);
+ $this->_slave = $slave;
+ } catch (Exception $e) {
+ // If there is a problem with first slave then skip 'load_from_slaves' option
+ }
+ }
+ }
+ }
+ unset($sentinel);
+ }
+
+ // Instantiate Credis_Cluster
+ // DEPRECATED
+ elseif (! empty($options['cluster'])) {
+ $this->_setupReadWriteCluster($options);
+ }
+
+ // Direct connection to single Redis server and optional slaves
+ else {
+ $port = $options['port'] ?? 6379;
+ $this->_redis = new Credis_Client($options['server'], $port, $this->_clientOptions->timeout, $this->_clientOptions->persistent);
+ $this->_applyClientOptions($this->_redis);
+
+ // Support loading from a replication slave
+ if (isset($options['load_from_slave'])) {
+ if (is_array($options['load_from_slave'])) {
+ if (isset($options['load_from_slave']['server'])) { // Single slave
+ $server = $options['load_from_slave']['server'];
+ $port = $options['load_from_slave']['port'];
+ $clientOptions = $this->getClientOptions($options['load_from_slave'] + $options);
+ $totalServers = 2;
+ } else { // Multiple slaves
+ $slaveKey = array_rand($options['load_from_slave']);
+ $slave = $options['load_from_slave'][$slaveKey];
+ $server = $slave['server'];
+ $port = $slave['port'];
+ $clientOptions = $this->getClientOptions($slave + $options);
+ $totalServers = count($options['load_from_slave']) + 1;
+ }
+ } else { // String
+ $server = $options['load_from_slave'];
+ $port = 6379;
+ $clientOptions = $this->_clientOptions;
+
+ // If multiple addresses are given, split and choose a random one
+ if (strpos($server, ',') !== false) {
+ $slaves = preg_split('/\s*,\s*/', $server, -1, PREG_SPLIT_NO_EMPTY);
+ $slaveKey = array_rand($slaves);
+ $server = $slaves[$slaveKey];
+ $port = null;
+ $totalServers = count($slaves) + 1;
+ } else {
+ $totalServers = 2;
+ }
+ }
+ // Skip setting up slave if master is not write only, and it is randomly chosen to be the read server
+ $masterWriteOnly = isset($options['master_write_only']) ? (int) $options['master_write_only'] : false;
+ if (is_string($server) && $server && ! (!$masterWriteOnly && rand(1, $totalServers) === 1)) {
+ try {
+ $slave = new Credis_Client($server, $port, $clientOptions->timeout, $clientOptions->persistent);
+ $this->_applyClientOptions($slave, true, $clientOptions);
+ $this->_slave = $slave;
+ } catch (Exception $e) {
+ // Slave will not be used
+ }
+ }
+ }
+ }
+
+ if (isset($options['notMatchingTags'])) {
+ $this->_notMatchingTags = (bool) $options['notMatchingTags'];
+ }
+
+ if (isset($options['compress_tags'])) {
+ $this->_compressTags = (int) $options['compress_tags'];
+ }
+
+ if (isset($options['compress_data'])) {
+ $this->_compressData = (int) $options['compress_data'];
+ }
+
+ if (isset($options['lifetimelimit'])) {
+ $this->_lifetimelimit = (int) min($options['lifetimelimit'], self::MAX_LIFETIME);
+ }
+
+ if (isset($options['compress_threshold'])) {
+ $this->_compressThreshold = (int) $options['compress_threshold'];
+ if ($this->_compressThreshold < 1) {
+ $this->_compressThreshold = 1;
+ }
+ }
+
+ if (isset($options['automatic_cleaning_factor'])) {
+ $this->_options['automatic_cleaning_factor'] = (int) $options['automatic_cleaning_factor'];
+ } else {
+ $this->_options['automatic_cleaning_factor'] = 0;
+ }
+
+ if (isset($options['compression_lib'])) {
+ $this->_compressionLib = (string) $options['compression_lib'];
+ } elseif (function_exists('snappy_compress')) {
+ $this->_compressionLib = 'snappy';
+ } elseif (function_exists('lz4_compress')) {
+ $version = phpversion("lz4");
+ if (version_compare($version, "0.3.0") < 0) {
+ $this->_compressTags = $this->_compressTags > 1;
+ $this->_compressData = $this->_compressData > 1;
+ }
+ $this->_compressionLib = 'l4z';
+ } elseif (function_exists('zstd_compress')) {
+ $version = phpversion("zstd");
+ if (version_compare($version, "0.4.13") < 0) {
+ $this->_compressTags = $this->_compressTags > 1;
+ $this->_compressData = $this->_compressData > 1;
+ }
+ $this->_compressionLib = 'zstd';
+ } elseif (function_exists('lzf_compress')) {
+ $this->_compressionLib = 'lzf';
+ } else {
+ $this->_compressionLib = 'gzip';
+ }
+ $this->_compressPrefix = substr($this->_compressionLib, 0, 2).self::COMPRESS_PREFIX;
+
+ if (isset($options['sunion_chunk_size']) && $options['sunion_chunk_size'] > 0) {
+ $this->_sunionChunkSize = (int) $options['sunion_chunk_size'];
+ }
+
+ if (isset($options['remove_chunk_size']) && $options['remove_chunk_size'] > 0) {
+ $this->_removeChunkSize = (int) $options['remove_chunk_size'];
+ }
+
+ if (isset($options['use_lua'])) {
+ $this->_useLua = (bool) $options['use_lua'];
+ }
+
+ if (isset($options['lua_max_c_stack'])) {
+ $this->_luaMaxCStack = (int) $options['lua_max_c_stack'];
+ }
+
+ if (isset($options['retry_reads_on_master'])) {
+ $this->_retryReadsOnMaster = (bool) $options['retry_reads_on_master'];
+ }
+
+ if (isset($options['auto_expire_lifetime'])) {
+ $this->_autoExpireLifetime = (int) $options['auto_expire_lifetime'];
+ }
+
+ if (isset($options['auto_expire_pattern'])) {
+ $this->_autoExpirePattern = (string) $options['auto_expire_pattern'];
+ }
+
+ if (isset($options['auto_expire_refresh_on_load'])) {
+ $this->_autoExpireRefreshOnLoad = (bool) $options['auto_expire_refresh_on_load'];
+ }
+ }
+
+ /**
+ * Apply common configuration to client instances.
+ *
+ * @param Credis_Client $client
+ * @param bool $forceSelect
+ * @param null|stdClass $clientOptions
+ * @throws CredisException
+ * @throws Zend_Cache_Exception
+ */
+ protected function _applyClientOptions(Credis_Client $client, $forceSelect = false, $clientOptions = null)
+ {
+ if ($clientOptions === null) {
+ $clientOptions = $this->_clientOptions;
+ }
+
+ if ($clientOptions->forceStandalone) {
+ $client->forceStandalone();
+ }
+
+ $client->setMaxConnectRetries($clientOptions->connectRetries);
+
+ if ($clientOptions->readTimeout) {
+ $client->setReadTimeout($clientOptions->readTimeout);
+ }
+
+ if ($clientOptions->password) {
+ if ($clientOptions->username) {
+ $client->auth($clientOptions->password, $clientOptions->username) or Zend_Cache::throwException('Unable to authenticate with the redis server.');
+ } else {
+ $client->auth($clientOptions->password) or Zend_Cache::throwException('Unable to authenticate with the redis server.');
+ }
+ }
+
+ // Always select database when persistent is used in case connection is re-used by other clients
+ if ($forceSelect || $clientOptions->database || $client->getPersistence()) {
+ $client->select($clientOptions->database) or Zend_Cache::throwException('The redis database could not be selected.');
+ }
+ }
+
+ /**
+ * @param $options
+ * @throws CredisException
+ * @throws Zend_Cache_Exception
+ * @deprecated - Previously this setup an instance of Credis_Cluster but this class was not complete or flawed
+ */
+ protected function _setupReadWriteCluster($options)
+ {
+ if (!empty($options['cluster']['master'])) {
+ foreach ($options['cluster']['master'] as $masterNode) {
+ if (empty($masterNode['server']) || empty($masterNode['port'])) {
+ continue;
+ }
+
+ $this->_redis = new Credis_Client(
+ $masterNode['host'],
+ $masterNode['port'],
+ $masterNode['timeout'] ?? 2.5,
+ $masterNode['persistent'] ?? ''
+ );
+ $this->_applyClientOptions($this->_redis);
+ break;
+ }
+ }
+
+ if (!empty($options['cluster']['slave'])) {
+ $slaveKey = array_rand($options['cluster']['slave']);
+ $slave = $options['cluster']['slave'][$slaveKey];
+ $this->_slave = new Credis_Client(
+ $slave['host'],
+ $slave['port'],
+ $slave['timeout'] ?? 2.5,
+ $slave['persistent'] ?? ''
+ );
+ $this->_applyClientOptions($this->_redis, true);
+ }
+ }
+
+ /**
+ * Load value with given id from cache
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return bool|string
+ * @throws CredisException
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ if ($this->_slave) {
+ try {
+ $data = $this->_slave->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA);
+
+ // Prevent compounded effect of cache flood on asynchronously replicating master/slave setup
+ if ($this->_retryReadsOnMaster && $data === false) {
+ $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA);
+ }
+ } catch (CredisException $e) {
+ // Always retry reads on master when dataset is loading on slave
+ if ($e->getMessage() === 'LOADING Redis is loading the dataset in memory') {
+ $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA);
+ } else {
+ throw $e;
+ }
+ }
+ } else {
+ try {
+ $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA);
+ } catch (CredisException $e) {
+ // Retry once after 1 second when dataset is loading
+ if ($e->getMessage() === 'LOADING Redis is loading the dataset in memory') {
+ sleep(1);
+ $data = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_DATA);
+ } else {
+ throw $e;
+ }
+ }
+ }
+ if ($data === null || $data === false || is_object($data)) {
+ return false;
+ }
+
+ $decoded = $this->_decodeData($data);
+
+ if ($this->_autoExpireLifetime === 0 || !$this->_autoExpireRefreshOnLoad) {
+ return $decoded;
+ }
+
+ $matches = $this->_matchesAutoExpiringPattern($id);
+ if (!$matches) {
+ return $decoded;
+ }
+
+ $this->_redis->expire(self::PREFIX_KEY.$id, min($this->_autoExpireLifetime, self::MAX_LIFETIME));
+
+ return $decoded;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id Cache id
+ * @return bool|int False if record is not available or "last modified" timestamp of the available cache record
+ */
+ public function test($id)
+ {
+ // Don't use slave for this since `test` is usually used for locking
+ $mtime = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_MTIME);
+ return ($mtime ? (int)$mtime : false);
+ }
+
+ /**
+ * Get the lifetime
+ *
+ * if $specificLifetime is not false, the given specific lifetime is used
+ * else, the global lifetime is used
+ *
+ * @param int $specificLifetime
+ * @return int Cache lifetime
+ */
+ public function getLifetime($specificLifetime)
+ {
+ // Lifetimes set via Layout XMLs get parsed as string so bool(false) becomes string("false")
+ if ($specificLifetime === 'false') {
+ $specificLifetime = false;
+ }
+
+ return parent::getLifetime($specificLifetime);
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param bool|int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @throws CredisException
+ * @return boolean True if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ if (!is_array($tags)) {
+ $tags = $tags ? array($tags) : array();
+ } else {
+ $tags = array_flip(array_flip($tags));
+ }
+
+ $lifetime = $this->_getAutoExpiringLifetime($this->getLifetime($specificLifetime), $id);
+ $lifetime = $lifetime === null ? $lifetime : (int) $lifetime;
+
+ if ($this->_useLua) {
+ $sArgs = array(
+ self::PREFIX_KEY,
+ self::FIELD_DATA,
+ self::FIELD_TAGS,
+ self::FIELD_MTIME,
+ self::FIELD_INF,
+ self::SET_TAGS,
+ self::PREFIX_TAG_IDS,
+ self::SET_IDS,
+ $id,
+ $this->_encodeData($data, $this->_compressData),
+ $this->_encodeData(implode(',', $tags), $this->_compressTags),
+ time(),
+ $lifetime ? 0 : 1,
+ min($lifetime, self::MAX_LIFETIME),
+ $this->_notMatchingTags ? 1 : 0
+ );
+
+ $res = $this->_redis->evalSha(self::LUA_SAVE_SH1, $tags, $sArgs);
+ if (is_null($res)) {
+ $script =
+ "local oldTags = redis.call('HGET', ARGV[1]..ARGV[9], ARGV[3]) ".
+ "redis.call('HMSET', ARGV[1]..ARGV[9], ARGV[2], ARGV[10], ARGV[3], ARGV[11], ARGV[4], ARGV[12], ARGV[5], ARGV[13]) ".
+ "if (ARGV[13] == '0') then ".
+ "redis.call('EXPIRE', ARGV[1]..ARGV[9], ARGV[14]) ".
+ "end ".
+ "if next(KEYS) ~= nil then ".
+ "redis.call('SADD', ARGV[6], unpack(KEYS)) ".
+ "for _, tagname in ipairs(KEYS) do ".
+ "redis.call('SADD', ARGV[7]..tagname, ARGV[9]) ".
+ "end ".
+ "end ".
+ "if (ARGV[15] == '1') then ".
+ "redis.call('SADD', ARGV[8], ARGV[9]) ".
+ "end ".
+ "if (oldTags ~= false) then ".
+ "return oldTags ".
+ "else ".
+ "return '' ".
+ "end";
+ $res = $this->_redis->eval($script, $tags, $sArgs);
+ }
+
+ // Process removed tags if cache entry already existed
+ if ($res) {
+ $oldTags = explode(',', $this->_decodeData($res));
+ if ($remTags = ($oldTags ? array_diff($oldTags, $tags) : false)) {
+ // Update the id list for each tag
+ foreach ($remTags as $tag) {
+ $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // Get list of tags previously assigned
+ $oldTags = $this->_decodeData($this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_TAGS));
+ $oldTags = $oldTags ? explode(',', $oldTags) : array();
+
+ $this->_redis->pipeline()->multi();
+
+ // Set the data
+ $result = $this->_redis->hMSet(self::PREFIX_KEY.$id, array(
+ self::FIELD_DATA => $this->_encodeData($data, $this->_compressData),
+ self::FIELD_TAGS => $this->_encodeData(implode(',', $tags), $this->_compressTags),
+ self::FIELD_MTIME => time(),
+ self::FIELD_INF => is_null($lifetime) ? 1 : 0,
+ ));
+ if (! $result) {
+ throw new CredisException("Could not set cache key $id");
+ }
+
+ // Set expiration if specified
+ if ($lifetime !== false && !is_null($lifetime)) {
+ $this->_redis->expire(self::PREFIX_KEY.$id, min($lifetime, self::MAX_LIFETIME));
+ }
+
+ // Process added tags
+ if ($tags) {
+ // Update the list with all the tags
+ $this->_redis->sAdd(self::SET_TAGS, $tags);
+
+ // Update the id list for each tag
+ foreach ($tags as $tag) {
+ $this->_redis->sAdd(self::PREFIX_TAG_IDS . $tag, $id);
+ }
+ }
+
+ // Process removed tags
+ if ($remTags = ($oldTags ? array_diff($oldTags, $tags) : false)) {
+ // Update the id list for each tag
+ foreach ($remTags as $tag) {
+ $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id);
+ }
+ }
+
+ // Update the list with all the ids
+ if ($this->_notMatchingTags) {
+ $this->_redis->sAdd(self::SET_IDS, $id);
+ }
+
+ $this->_redis->exec();
+
+ return true;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id)
+ {
+ // Get list of tags for this id
+ $tags = explode(',', $this->_decodeData($this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_TAGS)));
+
+ $this->_redis->pipeline()->multi();
+
+ // Remove data
+ $this->_redis->unlink(self::PREFIX_KEY.$id);
+
+ // Remove id from list of all ids
+ if ($this->_notMatchingTags) {
+ $this->_redis->sRem(self::SET_IDS, $id);
+ }
+
+ // Update the id list for each tag
+ foreach ($tags as $tag) {
+ $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id);
+ }
+
+ $result = $this->_redis->exec();
+
+ return isset($result[0]) && (bool)$result[0];
+ }
+
+ /**
+ * @param array $tags
+ * @throws Zend_Cache_Exception
+ */
+ protected function _removeByNotMatchingTags($tags)
+ {
+ $ids = $this->getIdsNotMatchingTags($tags);
+ $this->_removeByIds($ids);
+ }
+
+ /**
+ * @param array $tags
+ */
+ protected function _removeByMatchingTags($tags)
+ {
+ $ids = $this->getIdsMatchingTags($tags);
+ $this->_removeByIds($ids);
+ }
+
+ /**
+ * @param array $ids
+ */
+ protected function _removeByIds($ids)
+ {
+ if ($ids) {
+ $ids = array_chunk($ids, $this->_removeChunkSize);
+ foreach ($ids as $idsChunk) {
+ $this->_redis->pipeline()->multi();
+
+ // Remove data
+ $this->_redis->unlink($this->_preprocessIds($idsChunk));
+
+ // Remove ids from list of all ids
+ if ($this->_notMatchingTags) {
+ $this->_redis->sRem(self::SET_IDS, $idsChunk);
+ }
+
+ $this->_redis->exec();
+ }
+ }
+ }
+
+ /**
+ * @param array $tags
+ */
+ protected function _removeByMatchingAnyTags($tags)
+ {
+ if ($this->_useLua) {
+ $tags = array_chunk($tags, $this->_sunionChunkSize);
+ foreach ($tags as $chunk) {
+ $args = array(self::PREFIX_TAG_IDS, self::PREFIX_KEY, self::SET_TAGS, self::SET_IDS, ($this->_notMatchingTags ? 1 : 0), (int) $this->_luaMaxCStack);
+ if (! $this->_redis->evalSha(self::LUA_CLEAN_SH1, $chunk, $args)) {
+ $script =
+ "for i = 1, #KEYS, ARGV[6] do " .
+ "local prefixedTags = {} " .
+ "for x, tag in ipairs(KEYS) do " .
+ "prefixedTags[x] = ARGV[1]..tag " .
+ "end " .
+ "local keysToDel = redis.call('SUNION', unpack(prefixedTags, i, math.min(#prefixedTags, i + ARGV[6] - 1))) " .
+ "for _, keyname in ipairs(keysToDel) do " .
+ "redis.call('UNLINK', ARGV[2]..keyname) " .
+ "if (ARGV[5] == '1') then " .
+ "redis.call('SREM', ARGV[4], keyname) " .
+ "end " .
+ "end " .
+ "redis.call('UNLINK', unpack(prefixedTags, i, math.min(#prefixedTags, i + ARGV[6] - 1))) " .
+ "redis.call('SREM', ARGV[3], unpack(KEYS, i, math.min(#KEYS, i + ARGV[6] - 1))) " .
+ "end " .
+ "return true";
+ $this->_redis->eval($script, $chunk, $args);
+ }
+ }
+ return;
+ }
+
+ $ids = $this->getIdsMatchingAnyTags($tags);
+
+ $this->_redis->pipeline()->multi();
+
+ if ($ids) {
+ $ids = array_chunk($ids, $this->_removeChunkSize);
+ foreach ($ids as $idsChunk) {
+ // Remove data
+ $this->_redis->unlink($this->_preprocessIds($idsChunk));
+
+ // Remove ids from list of all ids
+ if ($this->_notMatchingTags) {
+ $this->_redis->sRem(self::SET_IDS, $idsChunk);
+ }
+
+ // Commit each chunk in a separate transaction
+ if (count($ids) > 1) {
+ $this->_redis->pipeline()->exec();
+ $this->_redis->pipeline()->multi();
+ }
+ }
+ }
+
+ // Remove tag id lists
+ $this->_redis->unlink($this->_preprocessTagIds($tags));
+
+ // Remove tags from list of tags
+ $this->_redis->sRem(self::SET_TAGS, $tags);
+
+ $this->_redis->exec();
+ }
+
+ /**
+ * Clean up tag id lists since as keys expire the ids remain in the tag id lists
+ */
+ protected function _collectGarbage()
+ {
+ // Clean up expired keys from tag id set and global id set
+
+ if ($this->_useLua) {
+ $sArgs = array(self::PREFIX_KEY, self::SET_TAGS, self::SET_IDS, self::PREFIX_TAG_IDS, ($this->_notMatchingTags ? 1 : 0));
+ $allTags = (array) $this->_redis->sMembers(self::SET_TAGS);
+ $tagsCount = count($allTags);
+ $counter = 0;
+ $tagsBatch = array();
+ foreach ($allTags as $tag) {
+ $tagsBatch[] = $tag;
+ $counter++;
+ if (count($tagsBatch) == 10 || $counter == $tagsCount) {
+ if (! $this->_redis->evalSha(self::LUA_GC_SH1, $tagsBatch, $sArgs)) {
+ $script =
+ "local tagKeys = {} ".
+ "local expired = {} ".
+ "local expiredCount = 0 ".
+ "local notExpiredCount = 0 ".
+ "for _, tagName in ipairs(KEYS) do ".
+ "tagKeys = redis.call('SMEMBERS', ARGV[4]..tagName) ".
+ "for __, keyName in ipairs(tagKeys) do ".
+ "if (redis.call('EXISTS', ARGV[1]..keyName) == 0) then ".
+ "expiredCount = expiredCount + 1 ".
+ "expired[expiredCount] = keyName ".
+ /* Redis Lua scripts have a hard limit of 8000 parameters per command */
+ "if (expiredCount == 7990) then ".
+ "redis.call('SREM', ARGV[4]..tagName, unpack(expired)) ".
+ "if (ARGV[5] == '1') then ".
+ "redis.call('SREM', ARGV[3], unpack(expired)) ".
+ "end ".
+ "expiredCount = 0 ".
+ "expired = {} ".
+ "end ".
+ "else ".
+ "notExpiredCount = notExpiredCount + 1 ".
+ "end ".
+ "end ".
+ "if (expiredCount > 0) then ".
+ "redis.call('SREM', ARGV[4]..tagName, unpack(expired)) ".
+ "if (ARGV[5] == '1') then ".
+ "redis.call('SREM', ARGV[3], unpack(expired)) ".
+ "end ".
+ "end ".
+ "if (notExpiredCount == 0) then ".
+ "redis.call ('UNLINK', ARGV[4]..tagName) ".
+ "redis.call ('SREM', ARGV[2], tagName) ".
+ "end ".
+ "expired = {} ".
+ "expiredCount = 0 ".
+ "notExpiredCount = 0 ".
+ "end ".
+ "return true";
+ $this->_redis->eval($script, $tagsBatch, $sArgs);
+ }
+ $tagsBatch = array();
+ /* Give Redis some time to handle other requests */
+ usleep(20000);
+ }
+ }
+ return;
+ }
+
+ $exists = array();
+ $tags = (array) $this->_redis->sMembers(self::SET_TAGS);
+ foreach ($tags as $tag) {
+ // Get list of expired ids for each tag
+ $tagMembers = $this->_redis->sMembers(self::PREFIX_TAG_IDS . $tag);
+ $numTagMembers = count($tagMembers);
+ $expired = array();
+ $numExpired = $numNotExpired = 0;
+ if ($numTagMembers) {
+ while ($id = array_pop($tagMembers)) {
+ if (! isset($exists[$id])) {
+ $exists[$id] = $this->_redis->exists(self::PREFIX_KEY.$id);
+ }
+ if ($exists[$id]) {
+ $numNotExpired++;
+ } else {
+ $numExpired++;
+ $expired[] = $id;
+
+ // Remove incrementally to reduce memory usage
+ if (count($expired) % 100 == 0 && $numNotExpired > 0) {
+ $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $expired);
+ if ($this->_notMatchingTags) { // Clean up expired ids from ids set
+ $this->_redis->sRem(self::SET_IDS, $expired);
+ }
+ $expired = array();
+ }
+ }
+ }
+ if (! count($expired)) {
+ continue;
+ }
+ }
+
+ // Remove empty tags or completely expired tags
+ if ($numExpired == $numTagMembers) {
+ $this->_redis->unlink(self::PREFIX_TAG_IDS . $tag);
+ $this->_redis->sRem(self::SET_TAGS, $tag);
+ }
+ // Clean up expired ids from tag ids set
+ elseif (count($expired)) {
+ $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $expired);
+ if ($this->_notMatchingTags) { // Clean up expired ids from ids set
+ $this->_redis->sRem(self::SET_IDS, $expired);
+ }
+ }
+ unset($expired);
+ }
+
+ // TODO
+ // Clean up global list of ids for ids with no tag
+// if ($this->_notMatchingTags) {
+// }
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => runs _collectGarbage()
+ * 'matchingTag' => supported
+ * 'notMatchingTag' => supported
+ * 'matchingAnyTag' => supported
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean True if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ if ($tags && ! is_array($tags)) {
+ $tags = array($tags);
+ }
+
+ try {
+ if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
+ return $this->_redis->flushDb();
+ }
+ if ($mode == Zend_Cache::CLEANING_MODE_OLD) {
+ $this->_collectGarbage();
+ return true;
+ }
+ if (! count($tags)) {
+ return true;
+ }
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+
+ $this->_removeByMatchingTags($tags);
+ break;
+
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+
+ $this->_removeByNotMatchingTags($tags);
+ break;
+
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+
+ $this->_removeByMatchingAnyTags($tags);
+ break;
+
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method: '.$mode);
+ }
+ } catch (CredisException $e) {
+ Zend_Cache::throwException('Error cleaning cache by mode '.$mode.': '.$e->getMessage(), $e);
+ }
+ return true;
+ }
+
+ /**
+ * Return true if the automatic cleaning is available for the backend
+ *
+ * @return boolean
+ */
+ public function isAutomaticCleaningAvailable()
+ {
+ return true;
+ }
+
+ /**
+ * Set the frontend directives
+ *
+ * @param array $directives Assoc of directives
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setDirectives($directives)
+ {
+ parent::setDirectives($directives);
+ $lifetime = $this->getLifetime(false);
+ if ($lifetime > self::MAX_LIFETIME) {
+ Zend_Cache::throwException('Redis backend has a limit of 30 days (2592000 seconds) for the lifetime');
+ }
+ }
+
+ /**
+ * Get the auto expiring lifetime.
+ *
+ * Mainly a workaround for the issues that arise due to the fact that
+ * Magento's Enterprise_PageCache module doesn't set any expiry.
+ *
+ * @param int $lifetime
+ * @param string $id
+ * @return int Cache lifetime
+ */
+ protected function _getAutoExpiringLifetime($lifetime, $id)
+ {
+ if ($lifetime || !$this->_autoExpireLifetime) {
+ // If it's already truthy, or there's no auto expire go with it.
+ return $lifetime;
+ }
+
+ $matches = $this->_matchesAutoExpiringPattern($id);
+ if (!$matches) {
+ // Only apply auto expire for keys that match the pattern
+ return $lifetime;
+ }
+
+ if ($this->_autoExpireLifetime > 0) {
+ // Return the auto expire lifetime if set
+ return $this->_autoExpireLifetime;
+ }
+
+ // Return whatever it was set to.
+ return $lifetime;
+ }
+
+ protected function _matchesAutoExpiringPattern($id)
+ {
+ $matches = array();
+ preg_match($this->_autoExpirePattern, $id, $matches);
+
+ return !empty($matches);
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ if ($this->_notMatchingTags) {
+ return (array) $this->_redis->sMembers(self::SET_IDS);
+ } else {
+ $keys = $this->_redis->keys(self::PREFIX_KEY . '*');
+ $prefixLen = strlen(self::PREFIX_KEY);
+ foreach ($keys as $index => $key) {
+ $keys[$index] = substr($key, $prefixLen);
+ }
+ return $keys;
+ }
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ return (array) $this->_redis->sMembers(self::SET_TAGS);
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ if ($tags) {
+ return (array) $this->_redis->sInter($this->_preprocessTagIds($tags));
+ }
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a negated logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ * @throws Zend_Cache_Exception
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ if (! $this->_notMatchingTags) {
+ Zend_Cache::throwException("notMatchingTags is currently disabled.");
+ }
+ if ($tags) {
+ return (array) $this->_redis->sDiff(self::SET_IDS, $this->_preprocessTagIds($tags));
+ }
+ return (array) $this->_redis->sMembers(self::SET_IDS);
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ $result = array();
+ if ($tags) {
+ $chunks = array_chunk($tags, $this->_sunionChunkSize);
+ foreach ($chunks as $chunk) {
+ $result = array_merge($result, (array) $this->_redis->sUnion($this->_preprocessTagIds($chunk)));
+ }
+ if (count($chunks) > 1) {
+ $result = array_unique($result); // since we are chunking requests, we must de-duplicate member names
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Return redis server info and stats
+ *
+ * @return array
+ */
+ public function getInfo()
+ {
+ return $this->_redis->info();
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @throws Zend_Cache_Exception
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ try {
+ $maxMem = $this->_redis->config('GET', 'maxmemory');
+ } catch (CredisException $e) {
+ throw new Zend_Cache_Exception($e->getMessage(), 0, $e);
+ }
+ if (0 == (int) $maxMem['maxmemory']) {
+ return 1;
+ }
+ $info = $this->_redis->info();
+ return (int) round(
+ ($info['used_memory']/$maxMem['maxmemory']*100)
+ );
+ }
+
+ /**
+ * Return the keyspace hit/miss percentage of the backend storage
+ *
+ * @throws Zend_Cache_Exception
+ * @return int integer between 0 and 100
+ */
+ public function getHitMissPercentage()
+ {
+ try {
+ $info = $this->_redis->info();
+ } catch (CredisException $e) {
+ throw new Zend_Cache_Exception($e->getMessage(), 0, $e);
+ }
+ $hits = $info['keyspace_hits'];
+ $misses = $info['keyspace_misses'];
+ $total = $misses+$hits;
+ $percentage = 0;
+ if ($total > 0) {
+ $percentage = round($hits*100/$total);
+ }
+ return $percentage;
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array|bool array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ list($tags, $mtime, $inf) = array_values(
+ $this->_redis->hMGet(self::PREFIX_KEY.$id, array(self::FIELD_TAGS, self::FIELD_MTIME, self::FIELD_INF))
+ );
+ if (! $mtime) {
+ return false;
+ }
+ $tags = explode(',', $this->_decodeData($tags));
+ $expire = $inf === '1' ? false : time() + $this->_redis->ttl(self::PREFIX_KEY.$id);
+
+ return array(
+ 'expire' => $expire,
+ 'tags' => $tags,
+ 'mtime' => $mtime,
+ );
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ $inf = $this->_redis->hGet(self::PREFIX_KEY.$id, self::FIELD_INF);
+ if ($inf === '0') {
+ $expireAt = time() + $this->_redis->ttl(self::PREFIX_KEY.$id) + $extraLifetime;
+ return (bool) $this->_redis->expireAt(self::PREFIX_KEY.$id, $expireAt);
+ }
+ return false;
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => ($this->_options['automatic_cleaning_factor'] > 0),
+ 'tags' => true,
+ 'expired_read' => false,
+ 'priority' => false,
+ 'infinite_lifetime' => true,
+ 'get_list' => true,
+ );
+ }
+
+ /**
+ * @param string $data
+ * @param int $level
+ * @throws CredisException
+ * @return string
+ */
+ protected function _encodeData($data, $level)
+ {
+ if ($this->_compressionLib && $level !== 0 && strlen($data) >= $this->_compressThreshold) {
+ switch($this->_compressionLib) {
+ case 'snappy': $data = snappy_compress($data);
+ break;
+ case 'lzf': $data = lzf_compress($data);
+ break;
+ case 'l4z': $data = lz4_compress($data, $level);
+ break;
+ case 'zstd': $data = zstd_compress($data, $level);
+ break;
+ case 'gzip': $data = gzcompress($data, $level);
+ break;
+ default: throw new CredisException("Unrecognized 'compression_lib'.");
+ }
+ if (! $data) {
+ throw new CredisException("Could not compress cache data.");
+ }
+ return $this->_compressPrefix.$data;
+ }
+ return $data;
+ }
+
+ /**
+ * @param bool|string $data
+ * @return string
+ */
+ protected function _decodeData($data)
+ {
+ try {
+ if (substr($data, 2, 3) == self::COMPRESS_PREFIX) {
+ switch(substr($data, 0, 2)) {
+ case 'sn': return snappy_uncompress(substr($data, 5));
+ case 'lz': return lzf_decompress(substr($data, 5));
+ case 'l4': return lz4_uncompress(substr($data, 5));
+ case 'zs': return zstd_uncompress(substr($data, 5));
+ case 'gz': case 'zc': return gzuncompress(substr($data, 5));
+ }
+ }
+ } catch(Exception $e) {
+ // Some applications will capture the php error that these functions can sometimes generate and throw it as an Exception
+ $data = false;
+ }
+ return $data;
+ }
+
+ /**
+ * @param $item
+ * @param $index
+ * @param $prefix
+ */
+ protected function _preprocess(&$item, $index, $prefix)
+ {
+ $item = $prefix . $item;
+ }
+
+ /**
+ * @param $ids
+ * @return array
+ */
+ protected function _preprocessIds($ids)
+ {
+ array_walk($ids, array($this, '_preprocess'), self::PREFIX_KEY);
+ return $ids;
+ }
+
+ /**
+ * @param $tags
+ * @return array
+ */
+ protected function _preprocessTagIds($tags)
+ {
+ array_walk($tags, array($this, '_preprocess'), self::PREFIX_TAG_IDS);
+ return $tags;
+ }
+
+ /**
+ * Required to pass unit tests
+ *
+ * @param string $id
+ * @return void
+ */
+ public function ___expire($id)
+ {
+ $this->_redis->unlink(self::PREFIX_KEY.$id);
+ }
+
+ /**
+ * Only for unit tests
+ */
+ public function ___scriptFlush()
+ {
+ $this->_redis->script('flush');
+ }
+
+ /**
+ * @return array
+ */
+ public function ___checkScriptsExist()
+ {
+ $scripts = [];
+ $result = $this->_redis->script('exists', self::LUA_SAVE_SH1, self::LUA_CLEAN_SH1, self::LUA_GC_SH1);
+ if ($result[0] ?? false) {
+ $scripts[] = 'save';
+ }
+ if ($result[1] ?? false) {
+ $scripts[] = 'clean';
+ }
+ if ($result[2] ?? false) {
+ $scripts[] = 'garbage';
+ }
+ return $scripts;
+ }
+}
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/Dockerfile b/system/vendor/colinmollenhour/cache-backend-redis/Dockerfile
new file mode 100644
index 0000000..024fb99
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/Dockerfile
@@ -0,0 +1,2 @@
+FROM php:8.1-alpine
+COPY --from=composer /usr/bin/composer /usr/bin/composer
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/ISSUE_TEMPLATE.md b/system/vendor/colinmollenhour/cache-backend-redis/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..e0e5c33
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/ISSUE_TEMPLATE.md
@@ -0,0 +1,2 @@
+
+
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/LICENSE b/system/vendor/colinmollenhour/cache-backend-redis/LICENSE
new file mode 100644
index 0000000..60f736f
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2013, Colin Mollenhour
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * The name of Colin Mollenhour may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ * If any files are modified, you must cause the modified files to carry prominent
+ notices stating that you changed the files and the date of any change.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/README.md b/system/vendor/colinmollenhour/cache-backend-redis/README.md
new file mode 100644
index 0000000..27ddd04
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/README.md
@@ -0,0 +1,241 @@
+This `Zend_Cache` backend allows you to use a Redis server as a central cache storage. Tags are fully supported
+without the use of `TwoLevels` cache so this backend is great for use on a single machine or in a cluster.
+Works with any Zend Framework project including all versions of Magento!
+
+# FEATURES
+
+ - Uses the [phpredis PECL extension](https://github.com/nicolasff/phpredis) for best performance (requires **master** branch or tagged version newer than Aug 19 2011).
+ - Falls back to standalone PHP if phpredis isn't available using the [Credis](https://github.com/colinmollenhour/credis) library.
+ - Tagging is fully supported, implemented using the Redis "set" and "hash" data types for efficient tag management.
+ - Key expiration is handled automatically by Redis.
+ - Supports unix socket connection for even better performance on a single machine.
+ - Supports configurable compression for memory savings. Can choose between gzip, lzf and snappy and can change configuration without flushing cache.
+ - Uses transactions to prevent race conditions between saves, cleans or removes causing unexpected results.
+ - Supports a configurable "auto expiry lifetime" which, if set, will be used as the TTL when the key otherwise wouldn't expire. In combination with "auto expiry refresh on load" offers a more sane cache management strategy for Magento's `Enterprise_PageCache` module.
+ - __Unit tested!__
+
+# REQUIREMENTS
+
+As this backend uses [Credis](https://github.com/colinmollenhour/credis) there are no additional requirements, but for improved performance you can install [phpredis](https://github.com/nicolasff/phpredis) which is a compiled extension.
+
+ * For 2.4 support you must use the "master" branch or a tagged version newer than Aug 19, 2011.
+ * phpredis is optional, but it is much faster than standalone mode
+ * phpredis does not support setting read timeouts at the moment (see pull request #260). If you receive read errors (“read error on connection”), this
+ might be the reason.
+
+# INSTALLATION
+
+Add the package as a dependency to your project with Composer.
+
+```shell
+composer require colinmollenhour/cache-backend-redis
+```
+
+### modman
+
+It is not the recommended method, but you may install via [modman](https://github.com/colinmollenhour/modman):
+
+```shell
+modman clone https://github.com/colinmollenhour/Cm_Cache_Backend_Redis
+```
+
+# CONFIGURATION
+
+These examples assume you are using Magento, but the configuration can just be passed to the constructor as a PHP
+array with the same key names as seen in the examples.
+
+Edit `app/etc/local.xml` to configure:
+
+
+
+ Cm_Cache_Backend_Redis
+
+ 127.0.0.1
+ 6379
+
+ 0
+
+ 0
+ 1
+ 10
+ 0
+ 1
+ 1
+ 20480
+ gzip
+ 0
+ tcp://redis-slave:6379
+
+
+
+
+
+ Cm_Cache_Backend_Redis
+
+ 127.0.0.1
+ 6379
+
+ 1
+
+ 0
+ 1
+ 57600
+ 0
+
+
+
+
+
+## High Availability and Load Balancing Support
+
+### Redis Sentinel
+
+You may achieve high availability and load balancing using [Redis Sentinel](http://redis.io/topics/sentinel). To enable use of Redis Sentinel the `server`
+specified should be a comma-separated list of Sentinel servers and the `sentinel_master` option should be specified
+to indicate the name of the sentinel master set (e.g. 'mymaster'). If using `sentinel_master` you may also specify
+`load_from_slaves` in which case a random slave will be chosen for performing reads in order to load balance across multiple Redis instances.
+Using the value '1' indicates to only load from slaves and '2' to include the master in the random read slave selection.
+
+Example configuration:
+
+
+
+ Cm_Cache_Backend_Redis
+
+ tcp://10.0.0.1:26379,tcp://10.0.0.2:26379,tcp://10.0.0.3:26379
+ 0.5
+ mymaster
+ 1
+ 1
+
+
+
+### Load Balancer or Service Discovery
+
+It is also possible to achieve high availability by using other methods where you can specify separate connection addresses for the
+master and slave(s). The `load_from_slave` option has been added for this purpose and this option does *not*
+connect to a Sentinel server as the example above, although you probably would benefit from still having a Sentinel setup purely for
+the easier replication and failover.
+
+Examples would be to use a TCP load balancer (e.g. HAProxy) with separate ports for master and slaves, or a DNS-based system that
+uses service discovery health checks to expose master and slaves via different DNS names.
+
+Example configuration:
+
+
+
+ Cm_Cache_Backend_Redis
+
+ tcp://redis-master:6379
+ tcp://redis-slaves:6379
+ 0
+ 0.5
+
+
+
+### Static Configuration
+
+You may also statically specify the master and slave servers by passing either an array to `load_from_slave` or a string
+with multiple addresses separated by a comma.
+
+
+
+ Cm_Cache_Backend_Redis
+
+ tcp://redis-master:6379
+ tcp://redis-slave1:6379,tcp://redis-slave2:6379
+ 0
+ 0.5
+
+
+
+### ElastiCache
+
+The following example configuration lets you use ElastiCache Redis (cluster mode disabled) where the writes are sent to
+the Primary node and reads are sent to the replicas. This lets you distribute the read traffic between the different nodes.
+
+The instructions to find the primary and read replica endpoints are [here](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/Endpoints.html#Endpoints.Find.Redis).
+
+
+
+ primary-endpoint.0001.euw1.cache.amazonaws.com
+ 6379
+ 0
+ 1
+
+
+ replica-endpoint-1.jwbaun.0001.euw1.cache.amazonaws.com
+ 6379
+
+
+ replica-endpoint-2.jwbaun.0001.euw1.cache.amazonaws.com
+ 6379
+
+
+
+
+#### DEPRECATION NOTICE
+
+Previously the ElastiCache config instructions suggested setting up a `` node but this functionality was flawed
+and is no longer supported. The config is still parsed and loaded for backwards-compatibility but chooses a random slave
+to read from rather than using md5 hash of the keys.
+
+# TUNING
+
+ - The recommended "maxmemory-policy" is "volatile-lru". All tag metadata is non-volatile, so it is
+ recommended to use key expirations unless non-volatile keys are absolutely necessary so that tag
+ data cannot get evicted. So, be sure that the "maxmemory" is high enough to accommodate all
+ the tag data and non-volatile data with enough room left for the volatile key data as well.
+ - Automatic cleaning is optional and not recommended since it is slow and uses lots of memory.
+ - Occasional (e.g. once a day) garbage collection is recommended if the entire cache is infrequently cleared and
+ automatic cleaning is not enabled. The best solution is to run a cron job which does the garbage collection.
+ (See "Example Garbage Collection Script" below.)
+ - Compression will have additional CPU overhead but may be worth it for memory savings and reduced traffic.
+ For high-latency networks it may even improve performance. Use the
+ [Magento Cache Benchmark](https://github.com/colinmollenhour/magento-cache-benchmark) to analyze your real-world
+ compression performance and test your system's performance with different compression libraries.
+ - gzip — Slowest but highest compression. Most likely you will not want to use above level 1 compression.
+ - lzf — Fastest compress, fast decompress. Install: `sudo pecl install lzf`
+ - snappy — Fastest decompress, fast compress. Download and install: [snappy](http://code.google.com/p/snappy/) and [php-snappy](http://code.google.com/p/php-snappy/)
+ - Monitor your redis cache statistics with my modified [munin plugin](https://gist.github.com/1177716).
+ - Enable persistent connections. Make sure that if you have multiple configurations connecting the persistent
+ string is unique for each configuration so that "select" commands don't cause conflicts.
+ - Increase your server's `lua-time-limit` if you are getting "BUSY" errors. This setting can also cause Redis Sentinel
+ to invoke fail-overs when you would probably prefer to let the Lua script finish and have clients wait a little longer.
+ - Use the `stats.php` script to inspect your cache to find over-sized or wasteful cache tags.
+
+### Example Garbage Collection Script (Magento)
+
+ :P');
+ ini_set('memory_limit','1024M');
+ set_time_limit(0);
+ error_reporting(E_ALL | E_STRICT);
+ require_once 'app/Mage.php';
+ Mage::app()->getCache()->getBackend()->clean('old');
+ // uncomment this for Magento Enterprise Edition
+ // Enterprise_PageCache_Model_Cache::getCacheInstance()->getFrontend()->getBackend()->clean('old');
+
+
+# DEVELOPMENT
+
+Please feel free to send Pull Requests to give back your improvements to the community!
+
+You can run the unit tests locally with just Docker installed using a simple alias:
+
+```shell
+alias cm-cache-backend-redis='docker run --rm -it -e REDIS_SERVER=host.docker.internal -u $(id -u):$(id -g) -v ${COMPOSER_HOME:-$HOME/.composer}:/tmp -v $(pwd):/app --workdir /app cm-cache-backend-redis'
+docker build . -t cm-cache-backend-redis
+```
+
+Then start a Redis server, install Composer dependencies and run tests like so:
+```shell
+ docker run --rm -d -p 6379 --name cm-cache-backend-redis redis
+ cm-cache-backend-redis composer install
+ cm-cache-backend-redis composer run-script test
+ cm-cache-backend-redis composer run-script php-cs-fixer -- --dry-run
+```
+
+```
+@copyright Copyright (c) 2022 Colin Mollenhour
+This project is licensed under the "New BSD" license (see source).
+```
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/composer.json b/system/vendor/colinmollenhour/cache-backend-redis/composer.json
new file mode 100644
index 0000000..4d2e3ea
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/composer.json
@@ -0,0 +1,29 @@
+{
+ "name":"colinmollenhour/cache-backend-redis",
+ "type":"magento-module",
+ "license":"BSD-3-Clause-Modification",
+ "homepage":"https://github.com/colinmollenhour/Cm_Cache_Backend_Redis",
+ "description":"Zend_Cache backend using Redis with full support for tags.",
+ "authors":[
+ {
+ "name":"Colin Mollenhour"
+ }
+ ],
+ "require":{
+ "colinmollenhour/credis": "^1.14"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.4",
+ "phpunit/phpunit": "^9",
+ "zf1s/zend-cache": "~1.15"
+ },
+ "autoload": {
+ "classmap": [
+ "Cm/Cache/Backend/Redis.php"
+ ]
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit tests",
+ "php-cs-fixer": "vendor/bin/php-cs-fixer fix --diff"
+ }
+}
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/modman b/system/vendor/colinmollenhour/cache-backend-redis/modman
new file mode 100644
index 0000000..adce619
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/modman
@@ -0,0 +1,2 @@
+Cm/Cache/Backend/Redis.php app/code/community/Cm/Cache/Backend/Redis.php
+lib/* lib/
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/stats.php b/system/vendor/colinmollenhour/cache-backend-redis/stats.php
new file mode 100644
index 0000000..901948b
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/stats.php
@@ -0,0 +1,95 @@
+select($db);
+
+$tagStats = array();
+foreach ($client->sMembers(Cm_Cache_Backend_Redis::SET_TAGS) as $tag) {
+ if (preg_match('/^\w{3}_MAGE$/', $tag)) {
+ continue;
+ }
+ $ids = $client->sMembers(Cm_Cache_Backend_Redis::PREFIX_TAG_IDS . $tag);
+ $tagSizes = array();
+ $missing = 0;
+ foreach ($ids as $id) {
+ $data = $client->hGet(Cm_Cache_Backend_Redis::PREFIX_KEY.$id, Cm_Cache_Backend_Redis::FIELD_DATA);
+ $size = strlen($data);
+ if ($size) {
+ $tagSizes[] = $size;
+ } else {
+ $missing++;
+ }
+ }
+ if ($tagSizes) {
+ $tagStats[$tag] = array(
+ 'count' => count($tagSizes),
+ 'min' => min($tagSizes),
+ 'max' => max($tagSizes),
+ 'avg size' => array_sum($tagSizes) / count($tagSizes),
+ 'total size' => array_sum($tagSizes),
+ 'missing' => $missing,
+ );
+ }
+}
+
+function _format_bytes($a_bytes)
+{
+ if ($a_bytes < 1024) {
+ return $a_bytes .' B';
+ } elseif ($a_bytes < 1048576) {
+ return round($a_bytes / 1024, 4) .' KB';
+ } else {
+ return round($a_bytes / 1048576, 4) . ' MB';
+ }
+}
+
+function printStats($data, $key, $limit)
+{
+ echo "Top $limit tags by ".ucwords($key)."\n";
+ echo "------------------------------------------------------------------------------------\n";
+ $sort = array();
+ foreach ($data as $tag => $stats) {
+ $sort[$tag] = $stats[$key];
+ }
+ array_multisort($sort, SORT_DESC, $data);
+ $i = 0;
+ $fmt = "%-40s| %-8s| %-15s| %-15s\n";
+ printf($fmt, 'Tag', 'Count', 'Avg Size', 'Total Size');
+ foreach ($data as $tag => $stats) {
+ $tag = substr($tag, 4);
+ if (++$i > $limit) {
+ break;
+ }
+ $avg = _format_bytes($stats['avg size']);
+ $total = _format_bytes($stats['total size']);
+ printf($fmt, $tag, $stats['count'], $avg, $total);
+ }
+ echo "\n";
+}
+
+// Top 20 by total size
+printStats($tagStats, 'total size', $limit);
+
+// Top 20 by average size
+printStats($tagStats, 'avg size', $limit);
+
+// Top 20 by count
+printStats($tagStats, 'count', $limit);
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonBackendTest.php b/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonBackendTest.php
new file mode 100644
index 0000000..c57a2b5
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonBackendTest.php
@@ -0,0 +1,186 @@
+_className = $name;
+ date_default_timezone_set('UTC');
+ parent::__construct($name, $data, $dataName);
+ }
+
+ public function setUp($noTag = false): void
+ {
+ $this->_instance->setDirectives(array('logging' => false));
+ if ($noTag) {
+ $this->_instance->save('bar : data to cache', 'bar');
+ $this->_instance->save('bar2 : data to cache', 'bar2');
+ $this->_instance->save('bar3 : data to cache', 'bar3');
+ } else {
+ $this->_instance->save('bar : data to cache', 'bar', array('tag3', 'tag4'));
+ $this->_instance->save('bar2 : data to cache', 'bar2', array('tag3', 'tag1'));
+ $this->_instance->save('bar3 : data to cache', 'bar3', array('tag2', 'tag3'));
+ }
+ }
+
+ public function tearDown(): void
+ {
+ $this->_instance->clean();
+ }
+
+ public function testConstructorBadOption(): void
+ {
+ $this->expectException('Zend_Cache_Exception');
+ new Cm_Cache_Backend_Redis(array(1 => 'bar'));
+ }
+
+ public function testSetDirectivesBadArgument(): void
+ {
+ $this->expectException('Zend_Cache_Exception');
+ $this->_instance->setDirectives('foo');
+ }
+
+ public function testSetDirectivesBadDirective(): void
+ {
+ // A bad directive (not known by a specific backend) is possible
+ // => so no exception here
+ $this->expectNotToPerformAssertions();
+ $this->_instance->setDirectives(array('foo' => true, 'lifetime' => 3600));
+ }
+
+ public function testSetDirectivesBadDirective2(): void
+ {
+ $this->expectException('Zend_Cache_Exception');
+ $this->_instance->setDirectives(array('foo' => true, 12 => 3600));
+ }
+
+ public function testSaveCorrectCall(): void
+ {
+ $res = $this->_instance->save('data to cache', 'foo', array('tag1', 'tag2'));
+ $this->assertTrue($res);
+ }
+
+ public function testSaveWithNullLifeTime(): void
+ {
+ $this->_instance->setDirectives(array('lifetime' => null));
+ $res = $this->_instance->save('data to cache', 'foo', array('tag1', 'tag2'));
+ $this->assertTrue($res);
+ }
+
+ public function testSaveWithSpecificLifeTime(): void
+ {
+ $this->_instance->setDirectives(array('lifetime' => 3600));
+ $res = $this->_instance->save('data to cache', 'foo', array('tag1', 'tag2'), 10);
+ $this->assertTrue($res);
+ }
+
+ public function testRemoveCorrectCall(): void
+ {
+ $this->assertTrue($this->_instance->remove('bar'));
+ $this->assertFalse($this->_instance->test('bar'));
+ $this->assertFalse($this->_instance->remove('barbar'));
+ $this->assertFalse($this->_instance->test('barbar'));
+ }
+
+ public function testTestWithAnExistingCacheId(): void
+ {
+ $res = $this->_instance->test('bar');
+ $this->assertNotEmpty($res);
+ $this->assertGreaterThan(999999, $res);
+ }
+
+ public function testTestWithANonExistingCacheId(): void
+ {
+ $this->assertFalse($this->_instance->test('barbar'));
+ }
+
+ public function testTestWithAnExistingCacheIdAndANullLifeTime(): void
+ {
+ $this->_instance->setDirectives(array('lifetime' => null));
+ $res = $this->_instance->test('bar');
+ $this->assertNotEmpty($res);
+ $this->assertGreaterThan(999999, $res);
+ }
+
+ public function testGetWithANonExistingCacheId(): void
+ {
+ $this->assertFalse($this->_instance->load('barbar'));
+ }
+
+ public function testGetWithAnExistingCacheId(): void
+ {
+ $this->assertEquals('bar : data to cache', $this->_instance->load('bar'));
+ }
+
+ public function testGetWithAnExistingCacheIdAndUTFCharacters(): void
+ {
+ $data = '"""""' . "'" . '\n' . 'ééééé';
+ $this->_instance->save($data, 'foo');
+ $this->assertEquals($data, $this->_instance->load('foo'));
+ }
+
+ public function testGetWithAnExpiredCacheId(): void
+ {
+ $this->_instance->___expire('bar');
+ $this->_instance->setDirectives(array('lifetime' => -1));
+ $this->assertFalse($this->_instance->load('bar'));
+ $this->assertEquals('bar : data to cache', $this->_instance->load('bar', true));
+ }
+
+ public function testCleanModeAll(): void
+ {
+ $this->assertTrue($this->_instance->clean('all'));
+ $this->assertFalse($this->_instance->test('bar'));
+ $this->assertFalse($this->_instance->test('bar2'));
+ }
+
+ public function testCleanModeOld(): void
+ {
+ $this->_instance->___expire('bar2');
+ $this->assertTrue($this->_instance->clean('old'));
+ $this->assertTrue($this->_instance->test('bar') > 999999);
+ $this->assertFalse($this->_instance->test('bar2'));
+ }
+
+ public function testCleanModeMatchingTags(): void
+ {
+ $this->assertTrue($this->_instance->clean('matchingTag', array('tag3')));
+ $this->assertFalse($this->_instance->test('bar'));
+ $this->assertFalse($this->_instance->test('bar2'));
+ }
+
+ public function testCleanModeMatchingTags2(): void
+ {
+ $this->assertTrue($this->_instance->clean('matchingTag', array('tag3', 'tag4')));
+ $this->assertFalse($this->_instance->test('bar'));
+ $this->assertTrue($this->_instance->test('bar2') > 999999);
+ }
+
+ public function testCleanModeNotMatchingTags(): void
+ {
+ $this->assertTrue($this->_instance->clean('notMatchingTag', array('tag3')));
+ $this->assertTrue($this->_instance->test('bar') > 999999);
+ $this->assertTrue($this->_instance->test('bar2') > 999999);
+ }
+
+ public function testCleanModeNotMatchingTags2(): void
+ {
+ $this->assertTrue($this->_instance->clean('notMatchingTag', array('tag4')));
+ $this->assertTrue($this->_instance->test('bar') > 999999);
+ $this->assertFalse($this->_instance->test('bar2'));
+ }
+
+ public function testCleanModeNotMatchingTags3(): void
+ {
+ $this->assertTrue($this->_instance->clean('notMatchingTag', array('tag4', 'tag1')));
+ $this->assertTrue($this->_instance->test('bar') > 999999);
+ $this->assertTrue($this->_instance->test('bar2') > 999999);
+ $this->assertFalse($this->_instance->test('bar3'));
+ }
+}
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonExtendedBackendTest.php b/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonExtendedBackendTest.php
new file mode 100644
index 0000000..d00532c
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/tests/CommonExtendedBackendTest.php
@@ -0,0 +1,177 @@
+_capabilities = $this->_instance->getCapabilities();
+ }
+
+ public function testGetFillingPercentage(): void
+ {
+ $res = $this->_instance->getFillingPercentage();
+ $this->assertTrue(is_integer($res));
+ $this->assertTrue($res >= 0);
+ $this->assertTrue($res <= 100);
+ }
+
+ public function testGetFillingPercentageOnEmptyBackend(): void
+ {
+ $this->_instance->clean();
+ $res = $this->_instance->getFillingPercentage();
+ $this->assertTrue(is_integer($res));
+ $this->assertTrue($res >= 0);
+ $this->assertTrue($res <= 100);
+ }
+
+ public function testGetIds(): void
+ {
+ if (!($this->_capabilities['get_list'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getIds();
+ $this->assertTrue(count($res) == 3);
+ $this->assertTrue(in_array('bar', $res));
+ $this->assertTrue(in_array('bar2', $res));
+ $this->assertTrue(in_array('bar3', $res));
+ }
+
+ public function testGetTags(): void
+ {
+ if (!($this->_capabilities['tags'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getTags();
+ $this->assertCount(4, $res);
+ $this->assertTrue(in_array('tag1', $res));
+ $this->assertTrue(in_array('tag2', $res));
+ $this->assertTrue(in_array('tag3', $res));
+ $this->assertTrue(in_array('tag4', $res));
+ }
+
+ public function testGetIdsMatchingTags(): void
+ {
+ if (!($this->_capabilities['tags'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getIdsMatchingTags(array('tag3'));
+ $this->assertTrue(count($res) == 3);
+ $this->assertTrue(in_array('bar', $res));
+ $this->assertTrue(in_array('bar2', $res));
+ $this->assertTrue(in_array('bar3', $res));
+ }
+
+ public function testGetIdsMatchingTags2(): void
+ {
+ if (!($this->_capabilities['tags'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getIdsMatchingTags(array('tag2'));
+ $this->assertTrue(count($res) == 1);
+ $this->assertTrue(in_array('bar3', $res));
+ }
+
+ public function testGetIdsMatchingTags3(): void
+ {
+ if (!($this->_capabilities['tags'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getIdsMatchingTags(array('tag9999'));
+ $this->assertEmpty($res);
+ }
+
+
+ public function testGetIdsMatchingTags4(): void
+ {
+ if (!($this->_capabilities['tags'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getIdsMatchingTags(array('tag3', 'tag4'));
+ $this->assertTrue(count($res) == 1);
+ $this->assertTrue(in_array('bar', $res));
+ }
+
+ public function testGetIdsNotMatchingTags(): void
+ {
+ if (!($this->_capabilities['tags'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getIdsNotMatchingTags(array('tag3'));
+ $this->assertCount(0, $res);
+ }
+
+ public function testGetIdsNotMatchingTags2(): void
+ {
+ if (!($this->_capabilities['tags'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getIdsNotMatchingTags(array('tag1'));
+ $this->assertTrue(count($res) == 2);
+ $this->assertTrue(in_array('bar', $res));
+ $this->assertTrue(in_array('bar3', $res));
+ }
+
+ public function testGetIdsNotMatchingTags3(): void
+ {
+ if (!($this->_capabilities['tags'])) {
+ # unsupported by this backend
+ return;
+ }
+ $res = $this->_instance->getIdsNotMatchingTags(array('tag1', 'tag4'));
+ $this->assertTrue(count($res) == 1);
+ $this->assertTrue(in_array('bar3', $res));
+ }
+
+ public function testGetMetadatas($noTag = false)
+ {
+ $res = $this->_instance->getMetadatas('bar');
+ $this->assertTrue(isset($res['tags']));
+ $this->assertTrue(isset($res['mtime']));
+ $this->assertTrue(isset($res['expire']));
+ if ($noTag) {
+ $this->assertEmpty($res['tags']);
+ } else {
+ $this->assertTrue(count($res['tags']) == 2);
+ $this->assertTrue(in_array('tag3', $res['tags']));
+ $this->assertTrue(in_array('tag4', $res['tags']));
+ }
+ $this->assertTrue($res['expire'] > time());
+ $this->assertTrue($res['mtime'] <= time());
+ }
+
+ public function testTouch(): void
+ {
+ $res = $this->_instance->getMetadatas('bar');
+ $this->assertGreaterThan(time(), $res['expire']);
+ $bool = $this->_instance->touch('bar', 30);
+ $this->assertTrue($bool);
+ $res2 = $this->_instance->getMetadatas('bar');
+ $this->assertGreaterThanOrEqual(29, $res2['expire'] - $res['expire']);
+ $this->assertTrue(($res2['mtime'] >= $res['mtime']));
+ }
+
+ public function testGetCapabilities(): void
+ {
+ $res = $this->_instance->getCapabilities();
+ $this->assertTrue(isset($res['tags']));
+ $this->assertTrue(isset($res['automatic_cleaning']));
+ $this->assertTrue(isset($res['expired_read']));
+ $this->assertTrue(isset($res['priority']));
+ $this->assertTrue(isset($res['infinite_lifetime']));
+ $this->assertTrue(isset($res['get_list']));
+ }
+}
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendAutoExpiryTest.php b/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendAutoExpiryTest.php
new file mode 100644
index 0000000..36ee54f
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendAutoExpiryTest.php
@@ -0,0 +1,58 @@
+_instance->save($data, $id, $tags, null);
+ $metadata = $this->_instance->getMetadatas($id);
+ $this->assertGreaterThan(1, $metadata['expire']);
+ sleep(1);
+ $this->_instance->load($id);
+ $nextMetadata = $this->_instance->getMetadatas($id);
+ $this->assertGreaterThan($metadata['expire'], $nextMetadata['expire']);
+ }
+}
diff --git a/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendStandaloneTest.php b/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendStandaloneTest.php
new file mode 100644
index 0000000..ae58292
--- /dev/null
+++ b/system/vendor/colinmollenhour/cache-backend-redis/tests/RedisBackendStandaloneTest.php
@@ -0,0 +1,41 @@
+_instance = new Cm_Cache_Backend_Redis(array(
+ 'server' => getenv('REDIS_SERVER') ?: 'localhost',
+ 'port' => getenv('REDIS_PORT') ?: '6379',
+ 'database' => '1',
+ 'notMatchingTags' => true,
+ 'force_standalone' => $this->forceStandalone,
+ 'compress_threshold' => 100,
+ 'compression_lib' => 'gzip',
+ 'use_lua' => true,
+ 'lua_max_c_stack' => self::LUA_MAX_C_STACK,
+ 'auto_expire_lifetime' => $this->autoExpireLifetime,
+ 'auto_expire_refresh_on_load' => $this->autoExpireRefreshOnLoad,
+ ));
+ $this->_instance->clean();
+ $this->_instance->___scriptFlush();
+ parent::setUp($noTag);
+ }
+
+ public function tearDown(): void
+ {
+ parent::tearDown();
+ unset($this->_instance);
+ }
+
+ public function testGetWithAnExpiredCacheId(): void
+ {
+ $this->markTestSkipped('Getting expired data is unsupported by Redis');
+ }
+
+ public function testCompression(): void
+ {
+ $longString = str_repeat(md5('asd')."\r\n", 50);
+ $this->assertTrue($this->_instance->save($longString, 'long', array('long')));
+ $this->assertTrue($this->_instance->load('long') == $longString);
+ }
+
+ public function testExpiredCleanup(): void
+ {
+ $this->assertTrue($this->_instance->clean());
+ $this->assertTrue($this->_instance->save('BLAH', 'foo', array('TAG1', 'TAG2'), 1));
+ $this->assertTrue($this->_instance->save('BLAH', 'bar', array('TAG1', 'TAG3'), 1));
+ $ids = $this->_instance->getIdsMatchingAnyTags(array('TAG1','TAG2','TAG3'));
+ sort($ids);
+ $this->assertEquals(array('bar','foo'), $ids);
+
+ // sleep(2);
+ $this->_instance->___expire('foo');
+ $this->_instance->___expire('bar');
+
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_OLD);
+ $this->assertEquals(array(), $this->_instance->getIdsMatchingAnyTags(array('TAG1','TAG2','TAG3')));
+ $this->assertEquals(array(), $this->_instance->getTags());
+ }
+
+ /**
+ $this->_instance->save('bar : data to cache', 'bar', array('tag3', 'tag4'));
+ $this->_instance->save('bar2 : data to cache', 'bar2', array('tag3', 'tag1'));
+ $this->_instance->save('bar3 : data to cache', 'bar3', array('tag2', 'tag3'));
+ */
+ public function testGetIdsMatchingAnyTags(): void
+ {
+ $res = $this->_instance->getIdsMatchingAnyTags(array('tag999'));
+ $this->assertCount(0, $res);
+ }
+
+ public function testGetIdsMatchingAnyTags2(): void
+ {
+ $res = $this->_instance->getIdsMatchingAnyTags(array('tag1', 'tag999'));
+ $this->assertCount(1, $res);
+ $this->assertTrue(in_array('bar2', $res));
+ }
+
+ public function testGetIdsMatchingAnyTags3(): void
+ {
+ $res = $this->_instance->getIdsMatchingAnyTags(array('tag3', 'tag999'));
+ $this->assertCount(3, $res);
+ $this->assertTrue(in_array('bar', $res));
+ $this->assertTrue(in_array('bar2', $res));
+ $this->assertTrue(in_array('bar3', $res));
+ }
+
+ public function testGetIdsMatchingAnyTags4(): void
+ {
+ $res = $this->_instance->getIdsMatchingAnyTags(array('tag1', 'tag4'));
+ $this->assertCount(2, $res);
+ $this->assertTrue(in_array('bar', $res));
+ $this->assertTrue(in_array('bar2', $res));
+ }
+
+ public function testCleanModeMatchingAnyTags(): void
+ {
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array('tag999'));
+ $this->assertTrue(!!$this->_instance->load('bar'));
+ $this->assertTrue(!!$this->_instance->load('bar2'));
+ $this->assertTrue(!!$this->_instance->load('bar3'));
+ }
+
+ public function testCleanModeMatchingAnyTags2(): void
+ {
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array('tag1', 'tag999'));
+ $this->assertTrue(!!$this->_instance->load('bar'));
+ $this->assertFalse(!!$this->_instance->load('bar2'));
+ $this->assertTrue(!!$this->_instance->load('bar3'));
+ }
+
+ public function testCleanModeMatchingAnyTags3(): void
+ {
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array('tag3', 'tag999'));
+ $this->assertFalse(!!$this->_instance->load('bar'));
+ $this->assertFalse(!!$this->_instance->load('bar2'));
+ $this->assertFalse(!!$this->_instance->load('bar3'));
+ }
+
+ public function testCleanModeMatchingAnyTags4(): void
+ {
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array('tag1', 'tag4'));
+ $this->assertFalse(!!$this->_instance->load('bar'));
+ $this->assertFalse(!!$this->_instance->load('bar2'));
+ $this->assertTrue(!!$this->_instance->load('bar3'));
+ }
+
+ public function testCleanModeMatchingAnyTags5(): void
+ {
+ $tags = array('tag1', 'tag4');
+ for ($i = 0; $i < self::LUA_MAX_C_STACK*5; $i++) {
+ $this->_instance->save('foo', 'foo'.$i, $tags);
+ }
+ $this->assertGreaterThan(self::LUA_MAX_C_STACK, count($this->_instance->getIdsMatchingAnyTags($tags)));
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags);
+ $this->assertCount(0, $this->_instance->getIdsMatchingAnyTags($tags));
+ }
+
+ public function testCleanModeMatchingAnyTags6(): void
+ {
+ $tags = array();
+ for ($i = 0; $i < self::LUA_MAX_C_STACK*5; $i++) {
+ $tags[] = 'baz'.$i;
+ }
+ $this->_instance->save('foo', 'foo', $tags);
+ $_tags = array(end($tags));
+ $this->assertCount(1, $this->_instance->getIdsMatchingAnyTags($_tags));
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $_tags);
+ $this->assertCount(0, $this->_instance->getIdsMatchingAnyTags($_tags));
+ }
+
+ public function testScriptsCaching(): void
+ {
+ $this->_instance->___scriptFlush();
+ $this->assertEquals([], $this->_instance->___checkScriptsExist());
+
+ $this->_instance->save('foo', 'bar', ['x','y']);
+ $this->assertEquals(['save'], $this->_instance->___checkScriptsExist());
+
+ $this->_instance->___scriptFlush();
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_OLD);
+ $this->assertEquals(['garbage'], $this->_instance->___checkScriptsExist());
+
+ $this->_instance->___scriptFlush();
+ $this->_instance->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, ['x']);
+ $this->assertEquals(['clean'], $this->_instance->___checkScriptsExist());
+ }
+}
diff --git a/system/vendor/colinmollenhour/credis/.editorconfig b/system/vendor/colinmollenhour/credis/.editorconfig
new file mode 100644
index 0000000..e13e644
--- /dev/null
+++ b/system/vendor/colinmollenhour/credis/.editorconfig
@@ -0,0 +1,6 @@
+[*.php]
+charset=utf-8
+end_of_line=lf
+insert_final_newline=true
+indent_style=space
+indent_size=4
\ No newline at end of file
diff --git a/system/vendor/colinmollenhour/credis/.php-cs-fixer.dist.php b/system/vendor/colinmollenhour/credis/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..6a41e79
--- /dev/null
+++ b/system/vendor/colinmollenhour/credis/.php-cs-fixer.dist.php
@@ -0,0 +1,19 @@
+setRules([
+ '@PSR12' => true,
+ 'visibility_required' => false, // php 5.6 doesn't support "public const ..."
+ ])
+ ->setFinder(PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ ->name('*.php')
+ ->ignoreDotFiles(true)
+ ->ignoreVCS(true)
+ )
+;
diff --git a/system/vendor/colinmollenhour/credis/Client.php b/system/vendor/colinmollenhour/credis/Client.php
index 4a253da..0107191 100644
--- a/system/vendor/colinmollenhour/credis/Client.php
+++ b/system/vendor/colinmollenhour/credis/Client.php
@@ -20,25 +20,22 @@
* @package Credis_Client
*/
-if( ! defined('CRLF')) define('CRLF', sprintf('%s%s', chr(13), chr(10)));
/**
* Credis-specific errors, wraps native Redis errors
*/
class CredisException extends Exception
{
-
const CODE_TIMED_OUT = 1;
const CODE_DISCONNECTED = 2;
- public function __construct($message, $code = 0, $exception = NULL)
+ public function __construct($message, $code = 0, $exception = null)
{
- if ($exception && get_class($exception) == 'RedisException' && strpos($message,'read error on connection') === 0) {
+ if ($exception && get_class($exception) == 'RedisException' && strpos($message, 'read error on connection') === 0) {
$code = CredisException::CODE_DISCONNECTED;
}
parent::__construct($message, $code, $exception);
}
-
}
/**
@@ -59,7 +56,7 @@ class CredisException extends Exception
* @method int|Credis_Client dbsize()
*
* Keys:
- * @method int|Credis_Client del(string $key)
+ * @method int|Credis_Client del(string|array ...$keys)
* @method int|Credis_Client exists(string $key)
* @method int|Credis_Client expire(string $key, int $seconds)
* @method int|Credis_Client expireAt(string $key, int $timestamp)
@@ -70,18 +67,19 @@ class CredisException extends Exception
* @method array|Credis_Client sort(string $key, string $arg1, string $valueN = null)
* @method int|Credis_Client ttl(string $key)
* @method string|Credis_Client type(string $key)
+ * @method string|Credis_Client unlink(string|array ...$keys)
*
* Scalars:
* @method int|Credis_Client append(string $key, string $value)
* @method int|Credis_Client decr(string $key)
* @method int|Credis_Client decrBy(string $key, int $decrement)
- * @method bool|string|Credis_Client get(string $key)
+ * @method false|string|Credis_Client get(string $key)
* @method int|Credis_Client getBit(string $key, int $offset)
* @method string|Credis_Client getRange(string $key, int $start, int $end)
* @method string|Credis_Client getSet(string $key, string $value)
* @method int|Credis_Client incr(string $key)
* @method int|Credis_Client incrBy(string $key, int $decrement)
- * @method array|Credis_Client mGet(array $keys)
+ * @method false|array|Credis_Client mGet(array $keys)
* @method bool|Credis_Client mSet(array $keysValues)
* @method int|Credis_Client mSetNx(array $keysValues)
* @method bool|Credis_Client set(string $key, string $value, int | array $options = null)
@@ -113,7 +111,7 @@ class CredisException extends Exception
* @method bool|string|Credis_Client hGet(string $key, string $field)
* @method bool|int|Credis_Client hLen(string $key)
* @method bool|Credis_Client hDel(string $key, string $field)
- * @method array|Credis_Client hKeys(string $key, string $field)
+ * @method array|Credis_Client hKeys(string $key)
* @method array|Credis_Client hVals(string $key)
* @method array|Credis_Client hGetAll(string $key)
* @method bool|Credis_Client hExists(string $key, string $field)
@@ -167,18 +165,14 @@ class CredisException extends Exception
* @method string|int|array|bool|Credis_Client eval(string $script, array $keys = null, array $args = null)
* @method string|int|array|bool|Credis_Client evalSha(string $script, array $keys = null, array $args = null)
*/
-class Credis_Client {
-
- const VERSION = '1.11.4';
-
- const TYPE_STRING = 'string';
- const TYPE_LIST = 'list';
- const TYPE_SET = 'set';
- const TYPE_ZSET = 'zset';
- const TYPE_HASH = 'hash';
- const TYPE_NONE = 'none';
-
- const FREAD_BLOCK_SIZE = 8192;
+class Credis_Client
+{
+ const TYPE_STRING = 'string';
+ const TYPE_LIST = 'list';
+ const TYPE_SET = 'set';
+ const TYPE_ZSET = 'zset';
+ const TYPE_HASH = 'hash';
+ const TYPE_NONE = 'none';
/**
* Socket connection to the Redis server or Redis library instance
@@ -194,26 +188,32 @@ class Credis_Client {
protected $host;
/**
- * Scheme of the Redis server (tcp, tls, unix)
- * @var string
+ * Scheme of the Redis server (tcp, tls, tlsv1.2, unix)
+ * @var string|null
*/
protected $scheme;
+ /**
+ * SSL Meta information
+ * @var array|null
+ */
+ protected $sslMeta;
+
/**
* Port on which the Redis server is running
- * @var integer|null
+ * @var int|null
*/
protected $port;
/**
* Timeout for connecting to Redis server
- * @var float
+ * @var float|null
*/
protected $timeout;
/**
* Timeout for reading response from Redis server
- * @var float
+ * @var float|null
*/
protected $readTimeout;
@@ -226,12 +226,12 @@ class Credis_Client {
/**
* @var bool
*/
- protected $closeOnDestruct = TRUE;
+ protected $closeOnDestruct = true;
/**
* @var bool
*/
- protected $connected = FALSE;
+ protected $connected = false;
/**
* @var bool
@@ -251,7 +251,7 @@ class Credis_Client {
/**
* @var bool
*/
- protected $usePipeline = FALSE;
+ protected $usePipeline = false;
/**
* @var array
@@ -266,20 +266,20 @@ class Credis_Client {
/**
* @var bool
*/
- protected $isMulti = FALSE;
+ protected $isMulti = false;
/**
* @var bool
*/
- protected $isWatching = FALSE;
+ protected $isWatching = false;
/**
- * @var string
+ * @var string|null
*/
protected $authUsername;
/**
- * @var string
+ * @var string|null
*/
protected $authPassword;
@@ -295,7 +295,7 @@ class Credis_Client {
protected $wrapperMethods = array('delete' => 'del', 'getkeys' => 'keys', 'sremove' => 'srem');
/**
- * @var array
+ * @var array|callable|null
*/
protected $renamedCommands;
@@ -309,37 +309,66 @@ class Credis_Client {
*/
protected $subscribed = false;
+ /** @var bool */
+ protected $oldPhpRedis = false;
+
+ /** @var array */
+ protected $tlsOptions = [];
+
/**
- * Creates a Redisent connection to the Redis server on host {@link $host} and port {@link $port}.
+ * @var bool
+ */
+ protected $isTls = false;
+
+ /**
+ * Gets Useful Meta debug information about the SSL
+ *
+ * @return array|null
+ */
+ public function getSslMeta()
+ {
+ return $this->sslMeta;
+ }
+
+ /**
+ * Creates a connection to the Redis server on host {@link $host} and port {@link $port}.
* $host may also be a path to a unix socket or a string in the form of tcp://[hostname]:[port] or unix://[path]
*
* @param string $host The hostname of the Redis server
- * @param integer $port The port number of the Redis server
- * @param float $timeout Timeout period in seconds
- * @param string $persistent Flag to establish persistent connection
- * @param int $db The selected datbase of the Redis server
- * @param string $password The authentication password of the Redis server
- * @param string $username The authentication username of the Redis server
- */
- public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '', $db = 0, $password = null, $username = null)
+ * @param int|null $port The port number of the Redis server
+ * @param float|null $timeout Timeout period in seconds
+ * @param string $persistent Flag to establish persistent connection
+ * @param int $db The selected database of the Redis server
+ * @param string|null $password The authentication password of the Redis server
+ * @param string|null $username The authentication username of the Redis server
+ * @param array|null $tlsOptions The TLS/SSL context options. See https://www.php.net/manual/en/context.ssl.php for details
+ * @throws CredisException
+ */
+ public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '', $db = 0, $password = null, $username = null, array $tlsOptions = null)
{
- $this->host = (string) $host;
- $this->port = (int) $port;
+ $this->host = (string)$host;
+ if ($port !== null) {
+ $this->port = (int)$port;
+ }
$this->scheme = null;
$this->timeout = $timeout;
- $this->persistent = (string) $persistent;
- $this->standalone = ! extension_loaded('redis');
+ $this->persistent = (string)$persistent;
+ $this->standalone = !extension_loaded('redis');
$this->authPassword = $password;
$this->authUsername = $username;
$this->selectedDb = (int)$db;
$this->convertHost();
+ if ($tlsOptions) {
+ $this->setTlsOptions($tlsOptions);
+ }
// PHP Redis extension support TLS/ACL AUTH since 5.3.0
+ $this->oldPhpRedis = (bool)version_compare(phpversion('redis'), '5.3.0', '<');
if ((
- $this->scheme === 'tls'
- || $this->authUsername !== null
- )
- && !$this->standalone && version_compare(phpversion('redis'),'5.3.0','<')){
+ $this->isTls
+ || $this->authUsername !== null
+ )
+ && !$this->standalone && $this->oldPhpRedis) {
$this->standalone = true;
}
}
@@ -356,7 +385,7 @@ class Credis_Client {
*/
public function isSubscribed()
{
- return $this->subscribed;
+ return $this->subscribed;
}
/**
@@ -367,6 +396,7 @@ class Credis_Client {
{
return $this->host;
}
+
/**
* Return the port of the Redis instance
* @return int|null
@@ -376,6 +406,14 @@ class Credis_Client {
return $this->port;
}
+ /**
+ * @return bool
+ */
+ public function isTls()
+ {
+ return $this->isTls;
+ }
+
/**
* Return the selected database
* @return int
@@ -384,6 +422,7 @@ class Credis_Client {
{
return $this->selectedDb;
}
+
/**
* @return string
*/
@@ -391,19 +430,20 @@ class Credis_Client {
{
return $this->persistent;
}
+
/**
- * @throws CredisException
* @return Credis_Client
+ * @throws CredisException
*/
public function forceStandalone()
{
if ($this->standalone) {
return $this;
}
- if($this->connected) {
+ if ($this->connected) {
throw new CredisException('Cannot force Credis_Client to use standalone PHP driver after a connection has already been established.');
}
- $this->standalone = TRUE;
+ $this->standalone = true;
return $this;
}
@@ -426,37 +466,54 @@ class Credis_Client {
$this->closeOnDestruct = $flag;
return $this;
}
+
+ /**
+ * @throws CredisException
+ */
+ public function setTlsOptions(array $tlsOptions)
+ {
+ if ($this->connected) {
+ throw new CredisException('Cannot change TLS options after a connection has already been established.');
+ }
+ $this->tlsOptions = $tlsOptions;
+ }
+
+ /**
+ * @throws CredisException
+ */
protected function convertHost()
{
- if (preg_match('#^(tcp|tls|unix)://(.*)$#', $this->host, $matches)) {
- if($matches[1] == 'tcp' || $matches[1] == 'tls') {
+ if (preg_match('#^(tcp|tls|ssl|tlsv\d(?:\.\d)?|unix)://(.+)$#', $this->host, $matches)) {
+ $this->isTls = strpos($matches[1], 'tls') === 0 || strpos($matches[1], 'ssl') === 0;
+ if ($this->isTls || $matches[1] === 'tcp') {
$this->scheme = $matches[1];
- if ( ! preg_match('#^([^:]+)(:([0-9]+))?(/(.+))?$#', $matches[2], $matches)) {
- throw new CredisException('Invalid host format; expected '.$this->scheme.'://host[:port][/persistence_identifier]');
+ if (!preg_match('#^([^:]+)(:([0-9]+))?(/(.+))?$#', $matches[2], $matches)) {
+ throw new CredisException('Invalid host format; expected ' . $this->scheme . '://host[:port][/persistence_identifier]');
}
$this->host = $matches[1];
- $this->port = (int) (isset($matches[3]) ? $matches[3] : $this->port);
+ $this->port = (int)(isset($matches[3]) ? $matches[3] : $this->port);
$this->persistent = isset($matches[5]) ? $matches[5] : $this->persistent;
} else {
$this->host = $matches[2];
- $this->port = NULL;
+ $this->port = null;
$this->scheme = 'unix';
- if (substr($this->host,0,1) != '/') {
+ if (substr($this->host, 0, 1) != '/') {
throw new CredisException('Invalid unix socket format; expected unix:///path/to/redis.sock');
}
}
}
- if ($this->port !== NULL && substr($this->host,0,1) == '/') {
- $this->port = NULL;
+ if ($this->port !== null && substr($this->host, 0, 1) == '/') {
+ $this->port = null;
$this->scheme = 'unix';
}
if (!$this->scheme) {
$this->scheme = 'tcp';
}
}
+
/**
- * @throws CredisException
* @return Credis_Client
+ * @throws CredisException
*/
public function connect()
{
@@ -464,32 +521,56 @@ class Credis_Client {
return $this;
}
$this->close(true);
-
+ $tlsOptions = $this->isTls ? $this->tlsOptions : [];
if ($this->standalone) {
$flags = STREAM_CLIENT_CONNECT;
- $remote_socket = $this->port === NULL
- ? $this->scheme.'://'.$this->host
- : $this->scheme.'://'.$this->host.':'.$this->port;
- if ($this->persistent && $this->port !== NULL) {
+ $remote_socket = $this->port === null
+ ? $this->scheme . '://' . $this->host
+ : $this->scheme . '://' . $this->host . ':' . $this->port;
+ if ($this->persistent && $this->port !== null) {
// Persistent connections to UNIX sockets are not supported
- $remote_socket .= '/'.$this->persistent;
+ $remote_socket .= '/' . $this->persistent;
$flags = $flags | STREAM_CLIENT_PERSISTENT;
}
- $result = $this->redis = @stream_socket_client($remote_socket, $errno, $errstr, $this->timeout !== null ? $this->timeout : 2.5, $flags);
- }
- else {
- if ( ! $this->redis) {
- $this->redis = new Redis;
+ if ($this->isTls) {
+ $tlsOptions = array_merge($tlsOptions, [
+ 'capture_peer_cert' => true,
+ 'capture_peer_cert_chain' => true,
+ 'capture_session_meta' => true,
+ ]);
+ }
+
+ // passing $context as null errors before php 8.0
+ $context = stream_context_create(['ssl' => $tlsOptions]);
+
+ $result = $this->redis = @stream_socket_client($remote_socket, $errno, $errstr, $this->timeout !== null ? $this->timeout : 2.5, $flags, $context);
+
+ if ($result && $this->isTls) {
+ $this->sslMeta = stream_context_get_options($context);
}
- $socketTimeout = $this->timeout ? $this->timeout : 0.0;
- try
- {
- $result = $this->persistent
- ? $this->redis->pconnect($this->host, (int)$this->port, $socketTimeout, $this->persistent)
- : $this->redis->connect($this->host, (int)$this->port, $socketTimeout);
+ } else {
+ if (!$this->redis) {
+ $this->redis = new Redis();
}
- catch(Exception $e)
- {
+ $socketTimeout = $this->timeout ?: 0.0;
+ try {
+ if ($this->oldPhpRedis) {
+ $result = $this->persistent
+ ? $this->redis->pconnect($this->host, (int)$this->port, $socketTimeout, $this->persistent)
+ : $this->redis->connect($this->host, (int)$this->port, $socketTimeout);
+ } else {
+ // 7th argument is non-documented TLS options. But it only exists on the newer versions of phpredis
+ if ($tlsOptions) {
+ $context = ['stream' => $tlsOptions];
+ } else {
+ $context = [];
+ }
+ /** @noinspection PhpMethodParametersCountMismatchInspection */
+ $result = $this->persistent
+ ? $this->redis->pconnect($this->scheme . '://' . $this->host, (int)$this->port, $socketTimeout, $this->persistent, 0, 0.0, $context)
+ : $this->redis->connect($this->scheme . '://' . $this->host, (int)$this->port, $socketTimeout, null, 0, 0.0, $context);
+ }
+ } catch (Exception $e) {
// Some applications will capture the php error that phpredis can sometimes generate and throw it as an Exception
$result = false;
$errno = 1;
@@ -498,31 +579,39 @@ class Credis_Client {
}
// Use recursion for connection retries
- if ( ! $result) {
+ if (!$result) {
$this->connectFailures++;
if ($this->connectFailures <= $this->maxConnectRetries) {
return $this->connect();
}
$failures = $this->connectFailures;
$this->connectFailures = 0;
- throw new CredisException("Connection to Redis {$this->host}:{$this->port} failed after $failures failures." . (isset($errno) && isset($errstr) ? "Last Error : ({$errno}) {$errstr}" : ""));
+ throw new CredisException(sprintf(
+ "Connection to Redis%s %s://%s failed after %s failures.%s",
+ $this->standalone ? ' standalone' : '',
+ $this->scheme,
+ $this->host . ($this->port ? ':' . $this->port : ''),
+ $failures,
+ (isset($errno) && isset($errstr) ? "Last Error : ({$errno}) {$errstr}" : "")
+ ));
}
$this->connectFailures = 0;
- $this->connected = TRUE;
+ $this->connected = true;
// Set read timeout
if ($this->readTimeout) {
$this->setReadTimeout($this->readTimeout);
}
- if($this->authPassword) {
+ if ($this->authPassword) {
$this->auth($this->authPassword, $this->authUsername);
}
- if($this->selectedDb !== 0) {
+ if ($this->selectedDb !== 0) {
$this->select($this->selectedDb);
}
return $this;
}
+
/**
* @return bool
*/
@@ -530,13 +619,14 @@ class Credis_Client {
{
return $this->connected;
}
+
/**
* Set the read timeout for the connection. Use 0 to disable timeouts entirely (or use a very long timeout
* if not supported).
*
- * @param int $timeout 0 (or -1) for no timeout, otherwise number of seconds
- * @throws CredisException
+ * @param float $timeout 0 (or -1) for no timeout, otherwise number of seconds
* @return Credis_Client
+ * @throws CredisException
*/
public function setReadTimeout($timeout)
{
@@ -547,13 +637,17 @@ class Credis_Client {
if ($this->isConnected()) {
if ($this->standalone) {
$timeout = $timeout <= 0 ? 315360000 : $timeout; // Ten-year timeout
- stream_set_blocking($this->redis, TRUE);
- stream_set_timeout($this->redis, (int) floor($timeout), ($timeout - floor($timeout)) * 1000000);
- } else if (defined('Redis::OPT_READ_TIMEOUT')) {
+ stream_set_blocking($this->redis, true);
+ stream_set_timeout($this->redis, (int)floor($timeout), ($timeout - floor($timeout)) * 1000000);
+ } elseif (defined('Redis::OPT_READ_TIMEOUT')) {
// supported in phpredis 2.2.3
- // a timeout value of -1 means reads will not timeout
+ // a timeout value of -1 means reads will not time out
$timeout = $timeout == 0 ? -1 : $timeout;
- $this->redis->setOption(Redis::OPT_READ_TIMEOUT, $timeout);
+ try {
+ $this->redis->setOption(Redis::OPT_READ_TIMEOUT, $timeout);
+ } catch (RedisException $e) {
+ throw new CredisException($e->getMessage(), $e->getCode(), $e);
+ }
}
}
return $this;
@@ -562,10 +656,10 @@ class Credis_Client {
/**
* @return bool
*/
- public function close($force = FALSE)
+ public function close($force = false)
{
- $result = TRUE;
- if ($this->redis && ($force || $this->connected && ! $this->persistent)) {
+ $result = true;
+ if ($this->redis && ($force || $this->connected && !$this->persistent)) {
try {
if (is_callable(array($this->redis, 'close'))) {
$this->redis->close();
@@ -574,9 +668,10 @@ class Credis_Client {
$this->redis = null;
}
} catch (Exception $e) {
- ; // Ignore exceptions on close
+ // Ignore exceptions on close
+ $result = false;
}
- $this->connected = $this->usePipeline = $this->isMulti = $this->isWatching = FALSE;
+ $this->connected = $this->usePipeline = $this->isMulti = $this->isWatching = false;
}
return $result;
}
@@ -592,16 +687,17 @@ class Credis_Client {
* @param string|callable|array $command
* @param string|null $alias
* @return $this
+ * @throws CredisException
*/
- public function renameCommand($command, $alias = NULL)
+ public function renameCommand($command, $alias = null)
{
- if ( ! $this->standalone) {
+ if (!$this->standalone) {
$this->forceStandalone();
}
- if ($alias === NULL) {
+ if ($alias === null) {
$this->renamedCommands = $command;
} else {
- if ( ! $this->renamedCommands) {
+ if (!$this->renamedCommands) {
$this->renamedCommands = array();
}
$this->renamedCommands[$command] = $alias;
@@ -618,12 +714,12 @@ class Credis_Client {
static $map;
// Command renaming not enabled
- if ($this->renamedCommands === NULL) {
+ if ($this->renamedCommands === null) {
return $command;
}
// Initialize command map
- if ($map === NULL) {
+ if ($map === null) {
if (is_array($this->renamedCommands)) {
$map = $this->renamedCommands;
} else {
@@ -632,17 +728,15 @@ class Credis_Client {
}
// Generate and return cached result
- if ( ! isset($map[$command])) {
+ if (!isset($map[$command])) {
// String means all commands are hashed with salted md5
if (is_string($this->renamedCommands)) {
- $map[$command] = md5($this->renamedCommands.$command);
- }
- // Would already be set in $map if it was intended to be renamed
- else if (is_array($this->renamedCommands)) {
+ $map[$command] = md5($this->renamedCommands . $command);
+ } // Would already be set in $map if it was intended to be renamed
+ elseif (is_array($this->renamedCommands)) {
return $command;
- }
- // User-supplied function
- else if (is_callable($this->renamedCommands)) {
+ } // User-supplied function
+ elseif (is_callable($this->renamedCommands)) {
$map[$command] = call_user_func($this->renamedCommands, $command);
}
}
@@ -653,12 +747,13 @@ class Credis_Client {
* @param string $password
* @param string|null $username
* @return bool
+ * @throws CredisException
*/
public function auth($password, $username = null)
{
if ($username !== null) {
$response = $this->__call('auth', array($username, $password));
- $this->authUsername= $username;
+ $this->authUsername = $username;
} else {
$response = $this->__call('auth', array($password));
}
@@ -669,69 +764,93 @@ class Credis_Client {
/**
* @param int $index
* @return bool
+ * @throws CredisException
*/
public function select($index)
{
$response = $this->__call('select', array($index));
- $this->selectedDb = (int) $index;
+ $this->selectedDb = (int)$index;
return $response;
}
/**
- * @param string|array $pattern
+ * @param string $caller
+ * @return void
+ * @throws CredisException
+ */
+ protected function assertNotPipelineOrMulti($caller)
+ {
+ if ($this->standalone && ($this->isMulti || $this->usePipeline) ||
+ // phpredis triggers a php fatal error, so do the check before
+ !$this->standalone && ($this->redis->getMode() === Redis::MULTI || $this->redis->getMode() === Redis::PIPELINE)) {
+ throw new CredisException('multi()/pipeline() mode can not be used with '.$caller);
+ }
+ }
+
+ /**
+ * @param string|array ...$args
* @return array
+ * @throws CredisException
*/
- public function pUnsubscribe()
+ public function pUnsubscribe(...$args)
{
- list($command, $channel, $subscribedChannels) = $this->__call('punsubscribe', func_get_args());
- $this->subscribed = $subscribedChannels > 0;
- return array($command, $channel, $subscribedChannels);
+ list($command, $channel, $subscribedChannels) = $this->__call('punsubscribe', $args);
+ $this->subscribed = $subscribedChannels > 0;
+ return array($command, $channel, $subscribedChannels);
}
/**
- * @param int $Iterator
+ * @param ?int $Iterator
* @param string $pattern
* @param int $count
* @return bool|array
+ * @throws CredisException
*/
public function scan(&$Iterator, $pattern = null, $count = null)
{
+ $this->assertNotPipelineOrMulti(__METHOD__);
return $this->__call('scan', array(&$Iterator, $pattern, $count));
}
/**
- * @param int $Iterator
- * @param string $field
- * @param string $pattern
- * @param int $count
- * @return bool|array
- */
- public function hscan(&$Iterator, $field, $pattern = null, $count = null)
- {
- return $this->__call('hscan', array($field, &$Iterator, $pattern, $count));
- }
+ * @param ?int $Iterator
+ * @param string $field
+ * @param string $pattern
+ * @param int $count
+ * @return bool|array
+ * @throws CredisException
+ */
+ public function hscan(&$Iterator, $field, $pattern = null, $count = null)
+ {
+ $this->assertNotPipelineOrMulti(__METHOD__);
+ return $this->__call('hscan', array($field, &$Iterator, $pattern, $count));
+ }
/**
- * @param int $Iterator
+ * @param ?int $Iterator
* @param string $field
* @param string $pattern
- * @param int $Iterator
+ * @param ?int $count
* @return bool|array
+ * @throws CredisException
*/
public function sscan(&$Iterator, $field, $pattern = null, $count = null)
{
+ $this->assertNotPipelineOrMulti(__METHOD__);
return $this->__call('sscan', array($field, &$Iterator, $pattern, $count));
}
/**
- * @param int $Iterator
+ * @param ?int $Iterator
* @param string $field
* @param string $pattern
- * @param int $Iterator
+ * @param ?int $count
* @return bool|array
+ * @throws CredisException
*/
public function zscan(&$Iterator, $field, $pattern = null, $count = null)
{
+ $this->assertNotPipelineOrMulti(__METHOD__);
return $this->__call('zscan', array($field, &$Iterator, $pattern, $count));
}
@@ -743,7 +862,7 @@ class Credis_Client {
*/
public function pSubscribe($patterns, $callback)
{
- if ( ! $this->standalone) {
+ if (!$this->standalone) {
return $this->__call('pSubscribe', array((array)$patterns, $callback));
}
@@ -756,7 +875,7 @@ class Credis_Client {
list($command, $pattern, $status) = $this->__call('psubscribe', array($patterns));
}
$this->subscribed = $status > 0;
- if ( ! $status) {
+ if (!$status) {
throw new CredisException('Invalid pSubscribe response.');
}
}
@@ -771,25 +890,26 @@ class Credis_Client {
}
/**
- * @param string|array $pattern
+ * @param string|array ...$args
* @return array
+ * @throws CredisException
*/
- public function unsubscribe()
+ public function unsubscribe(...$args)
{
- list($command, $channel, $subscribedChannels) = $this->__call('unsubscribe', func_get_args());
- $this->subscribed = $subscribedChannels > 0;
- return array($command, $channel, $subscribedChannels);
+ list($command, $channel, $subscribedChannels) = $this->__call('unsubscribe', $args);
+ $this->subscribed = $subscribedChannels > 0;
+ return array($command, $channel, $subscribedChannels);
}
/**
* @param string|array $channels
* @param $callback
- * @throws CredisException
* @return $this|array|bool|Credis_Client|mixed|null|string
+ * @throws CredisException
*/
public function subscribe($channels, $callback)
{
- if ( ! $this->standalone) {
+ if (!$this->standalone) {
return $this->__call('subscribe', array((array)$channels, $callback));
}
@@ -802,7 +922,7 @@ class Credis_Client {
list($command, $channel, $status) = $this->__call('subscribe', array($channels));
}
$this->subscribed = $status > 0;
- if ( ! $status) {
+ if (!$status) {
throw new CredisException('Invalid subscribe response.');
}
}
@@ -819,31 +939,33 @@ class Credis_Client {
/**
* @param string|null $name
* @return string|Credis_Client
+ * @throws CredisException
*/
public function ping($name = null)
{
- return $this->__call('ping', $name ? array($name) : array());
+ return $this->__call('ping', $name ? array($name) : array());
}
- /**
- * @param string $command
- * @param array $args
- *
- * @return array|Credis_Client
- */
- public function rawCommand($command, array $args)
- {
- if($this->standalone)
- {
- return $this->__call($command, $args);
- }
- else
- {
- \array_unshift($args, $command);
- return $this->__call('rawCommand', $args);
- }
- }
+ /**
+ * @param string $command
+ * @param array $args
+ *
+ * @return array|Credis_Client
+ * @throws CredisException
+ */
+ public function rawCommand($command, array $args)
+ {
+ if ($this->standalone) {
+ return $this->__call($command, $args);
+ } else {
+ \array_unshift($args, $command);
+ return $this->__call('rawCommand', $args);
+ }
+ }
+ /**
+ * @throws CredisException
+ */
public function __call($name, $args)
{
// Lazy connection
@@ -852,26 +974,26 @@ class Credis_Client {
$name = strtolower($name);
// Send request via native PHP
- if($this->standalone)
- {
+ if ($this->standalone) {
+ // Early returns should verify how phpredis behaves!
$trackedArgs = array();
switch ($name) {
case 'eval':
case 'evalsha':
$script = array_shift($args);
- $keys = (array) array_shift($args);
- $eArgs = (array) array_shift($args);
+ $keys = (array)array_shift($args);
+ $eArgs = (array)array_shift($args);
$args = array($script, count($keys), $keys, $eArgs);
break;
case 'zinterstore':
case 'zunionstore':
$dest = array_shift($args);
- $keys = (array) array_shift($args);
+ $keys = (array)array_shift($args);
$weights = array_shift($args);
$aggregate = array_shift($args);
$args = array($dest, count($keys), $keys);
if ($weights) {
- $args[] = (array) $weights;
+ $args[] = (array)$weights;
}
if ($aggregate) {
$args[] = $aggregate;
@@ -885,9 +1007,9 @@ class Credis_Client {
} elseif (count($args) === 3 && is_array($args[2])) {
$tmp_args = $args;
$args = array($tmp_args[0], $tmp_args[1]);
- foreach ($tmp_args[2] as $k=>$v) {
+ foreach ($tmp_args[2] as $k => $v) {
if (is_string($k)) {
- $args[] = array($k,$v);
+ $args[] = array($k, $v);
} elseif (is_int($k)) {
$args[] = $v;
}
@@ -897,18 +1019,17 @@ class Credis_Client {
break;
case 'scan':
$trackedArgs = array(&$args[0]);
- if (empty($trackedArgs[0]))
- {
+ if ($trackedArgs[0] === null) {
$trackedArgs[0] = 0;
+ } elseif ($trackedArgs[0] === 0) {
+ return false;
}
$eArgs = array($trackedArgs[0]);
- if (!empty($args[1]))
- {
+ if (!empty($args[1])) {
$eArgs[] = 'MATCH';
$eArgs[] = $args[1];
}
- if (!empty($args[2]))
- {
+ if (!empty($args[2])) {
$eArgs[] = 'COUNT';
$eArgs[] = $args[2];
}
@@ -917,24 +1038,23 @@ class Credis_Client {
case 'sscan':
case 'zscan':
case 'hscan':
- $trackedArgs = array(&$args[1]);
- if (empty($trackedArgs[0]))
- {
- $trackedArgs[0] = 0;
- }
- $eArgs = array($args[0],$trackedArgs[0]);
- if (!empty($args[2]))
- {
- $eArgs[] = 'MATCH';
- $eArgs[] = $args[2];
- }
- if (!empty($args[3]))
- {
- $eArgs[] = 'COUNT';
- $eArgs[] = $args[3];
- }
- $args = $eArgs;
- break;
+ $trackedArgs = array(&$args[1]);
+ if ($trackedArgs[0] === null) {
+ $trackedArgs[0] = 0;
+ } elseif ($trackedArgs[0] === 0) {
+ return false;
+ }
+ $eArgs = array($args[0], $trackedArgs[0]);
+ if (!empty($args[2])) {
+ $eArgs[] = 'MATCH';
+ $eArgs[] = $args[2];
+ }
+ if (!empty($args[3])) {
+ $eArgs[] = 'COUNT';
+ $eArgs[] = $args[3];
+ }
+ $args = $eArgs;
+ break;
case 'zrangebyscore':
case 'zrevrangebyscore':
case 'zrange':
@@ -953,17 +1073,17 @@ class Credis_Client {
}
break;
case 'mget':
- if (isset($args[0]) && is_array($args[0]))
- {
+ if (isset($args[0]) && is_array($args[0])) {
$args = array_values($args[0]);
}
+ if (is_array($args) && count($args) === 0) {
+ return ($this->isMulti || $this->usePipeline) ? $this : false;
+ }
break;
case 'hmset':
- if (isset($args[1]) && is_array($args[1]))
- {
+ if (isset($args[1]) && is_array($args[1])) {
$cArgs = array();
- foreach($args[1] as $id => $value)
- {
+ foreach ($args[1] as $id => $value) {
$cArgs[] = $id;
$cArgs[] = $value;
}
@@ -978,121 +1098,148 @@ class Credis_Client {
break;
case 'hmget':
// hmget needs to track the keys for rehydrating the results
- if (isset($args[1]))
- {
+ if (isset($args[1])) {
$trackedArgs = $args[1];
}
break;
+ case 'multi':
+ // calling multi() multiple times is a no-op
+ if ($this->isMulti) {
+ return $this;
+ }
+ break;
}
// Flatten arguments
$args = self::_flattenArguments($args);
// In pipeline mode
- if($this->usePipeline)
- {
- if($name === 'pipeline') {
+ if ($this->usePipeline) {
+ if ($name === 'pipeline') {
throw new CredisException('A pipeline is already in use and only one pipeline is supported.');
- }
- else if($name === 'exec') {
- if($this->isMulti) {
- $this->commandNames[] = array($name, $trackedArgs);
+ } elseif ($name === 'exec') {
+ if ($this->isMulti) {
+ $this->commandNames[] = array($name, $trackedArgs, true);
$this->commands .= self::_prepare_command(array($this->getRenamedCommand($name)));
}
- // Write request
- if($this->commands) {
- $this->write_command($this->commands);
- }
- $this->commands = NULL;
-
- // Read response
- $queuedResponses = array();
- $response = array();
- foreach($this->commandNames as $command) {
- list($name, $arguments) = $command;
- $result = $this->read_reply($name, true);
- if ($result !== null)
- {
- $result = $this->decode_reply($name, $result, $arguments);
+ try {
+ // Write request
+ if ($this->commands) {
+ $this->write_command($this->commands);
}
- else
- {
- $queuedResponses[] = $command;
+
+ // Read response
+ $queuedResponses = array();
+ $response = array();
+ foreach ($this->commandNames as $command) {
+ list($name, $arguments, $requireDispatch) = $command;
+ if (!$requireDispatch) {
+ $queuedResponses[] = $command;
+ continue;
+ }
+ $result = $this->read_reply($name, true);
+ if ($result !== null) {
+ if ($name === 'multi') {
+ continue;
+ }
+ $result = $this->decode_reply($name, $result, $arguments);
+ $response[] = $result;
+ } else {
+ $queuedResponses[] = $command;
+ }
}
- $response[] = $result;
- }
- if($this->isMulti) {
- $response = array_pop($response);
- foreach($queuedResponses as $key => $command)
- {
- list($name, $arguments) = $command;
- $response[$key] = $this->decode_reply($name, $response[$key], $arguments);
+ if ($this->isMulti) {
+ $execResponse = array_pop($response);
+ foreach ($queuedResponses as $key => $command) {
+ list($name, $arguments) = $command;
+ $response[] = $this->decode_reply($name, $execResponse[$key], $arguments);
+ }
}
+ } catch (CredisException $e) {
+ // the connection on redis's side is likely in a bad state, force it closed to abort the pipeline/transaction
+ $this->close(true);
+ throw $e;
+ } finally {
+ $this->commands = $this->commandNames = null;
+ $this->isMulti = $this->usePipeline = false;
}
-
- $this->commandNames = NULL;
- $this->usePipeline = $this->isMulti = FALSE;
return $response;
- }
- else if ($name === 'discard')
- {
- $this->commands = NULL;
- $this->commandNames = NULL;
- $this->usePipeline = $this->isMulti = FALSE;
- }
- else {
- if($name === 'multi') {
- $this->isMulti = TRUE;
+ } elseif ($name === 'discard') {
+ $this->commands = null;
+ $this->commandNames = null;
+ $this->usePipeline = $this->isMulti = false;
+ } else {
+ if ($name === 'multi') {
+ $this->isMulti = true;
}
array_unshift($args, $this->getRenamedCommand($name));
- $this->commandNames[] = array($name, $trackedArgs);
+ $this->commandNames[] = array($name, $trackedArgs, true);
$this->commands .= self::_prepare_command($args);
return $this;
}
}
// Start pipeline mode
- if($name === 'pipeline')
- {
- $this->usePipeline = TRUE;
- $this->commandNames = array();
+ if ($name === 'pipeline') {
+ $this->usePipeline = true;
+ if (!$this->isMulti) {
+ $this->commandNames = [];
+ }
$this->commands = '';
return $this;
}
// If unwatching, allow reconnect with no error thrown
- if($name === 'unwatch') {
- $this->isWatching = FALSE;
+ if ($name === 'unwatch') {
+ $this->isWatching = false;
}
// Non-pipeline mode
array_unshift($args, $this->getRenamedCommand($name));
$command = self::_prepare_command($args);
- $this->write_command($command);
- $response = $this->read_reply($name);
- $response = $this->decode_reply($name, $response, $trackedArgs);
+ // transaction mode needs to track commands
+ if ($this->isMulti) {
+ try {
+ if ($name === 'exec' || $name === 'discard') {
+ try {
+ $this->write_command($command);
+ $response = $this->read_reply($name);
+ $response = $this->decode_reply($name, $response, $trackedArgs);
+ } finally {
+ $this->isMulti = false;
+ $this->commandNames = [];
+ }
+ } else {
+ $this->commandNames[] = array($name, $trackedArgs, false);
+ $this->write_command($command);
+ $response = $this->read_reply($name);
+ }
+ } catch (CredisException $e) {
+ // the connection on redis's side is likely in a bad state, force it closed to abort the transaction
+ $this->isMulti = false;
+ $this->commandNames = [];
+ $this->close(true);
+ throw $e;
+ }
+ } else {
+ $this->write_command($command);
+ $response = $this->read_reply($name);
+ $response = $this->decode_reply($name, $response, $trackedArgs);
+ }
// Watch mode disables reconnect so error is thrown
- if($name == 'watch') {
- $this->isWatching = TRUE;
- }
- // Transaction mode
- else if($this->isMulti && ($name == 'exec' || $name == 'discard')) {
- $this->isMulti = FALSE;
- }
- // Started transaction
- else if($this->isMulti || $name == 'multi') {
- $this->isMulti = TRUE;
+ if ($name === 'watch') {
+ $this->isWatching = true;
+ } // Started transaction
+ elseif ($this->isMulti || $name === 'multi') {
+ $this->isMulti = true;
$response = $this;
}
- }
-
- // Send request via phpredis client
- else
- {
+ } // Send request via phpredis client
+ else {
// Tweak arguments
- switch($name) {
+ switch ($name) {
case 'get': // optimize common cases
case 'set':
case 'hget':
@@ -1102,14 +1249,12 @@ class Credis_Client {
case 'msetnx':
case 'hmset':
case 'hmget':
- case 'del':
case 'zrangebyscore':
case 'zrevrangebyscore':
- break;
+ break;
case 'zrange':
case 'zrevrange':
- if (isset($args[3]) && is_array($args[3]))
- {
+ if (isset($args[3]) && is_array($args[3])) {
$cArgs = $args[3];
$args[3] = !empty($cArgs['withscores']);
}
@@ -1120,18 +1265,18 @@ class Credis_Client {
$cArgs = array();
$cArgs[] = array_shift($args); // destination
$cArgs[] = array_shift($args); // keys
- if(isset($args[0]) and isset($args[0]['weights'])) {
- $cArgs[] = (array) $args[0]['weights'];
+ if (isset($args[0]) and isset($args[0]['weights'])) {
+ $cArgs[] = (array)$args[0]['weights'];
} else {
$cArgs[] = null;
}
- if(isset($args[0]) and isset($args[0]['aggregate'])) {
+ if (isset($args[0]) and isset($args[0]['aggregate'])) {
$cArgs[] = strtoupper($args[0]['aggregate']);
}
$args = $cArgs;
break;
case 'mget':
- if(isset($args[0]) && ! is_array($args[0])) {
+ if (isset($args[0]) && !is_array($args[0])) {
$args = array($args);
}
break;
@@ -1168,7 +1313,7 @@ class Credis_Client {
break;
case 'auth':
// For phpredis pre-v5.3, the type signature is string, not array|string
- $args = (is_array($args) && count($args) === 1) ? $args : array($args);
+ $args = $this->oldPhpRedis ? $args : array($args);
break;
default:
// Flatten arguments
@@ -1177,30 +1322,26 @@ class Credis_Client {
try {
// Proxy pipeline mode to the phpredis library
- if($name == 'pipeline' || $name == 'multi') {
- if($this->isMulti) {
- return $this;
- } else {
- $this->isMulti = TRUE;
+ if ($name == 'pipeline' || $name == 'multi') {
+ if (!$this->isMulti) {
+ $this->isMulti = true;
$this->redisMulti = call_user_func_array(array($this->redis, $name), $args);
- return $this;
}
- }
- else if($name == 'exec' || $name == 'discard') {
- $this->isMulti = FALSE;
+ return $this;
+ } elseif ($name == 'exec' || $name == 'discard') {
+ $this->isMulti = false;
$response = $this->redisMulti->$name();
- $this->redisMulti = NULL;
- #echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n";
+ $this->redisMulti = null;
return $response;
}
// Use aliases to be compatible with phpredis wrapper
- if(isset($this->wrapperMethods[$name])) {
+ if (isset($this->wrapperMethods[$name])) {
$name = $this->wrapperMethods[$name];
}
// Multi and pipeline return self for chaining
- if($this->isMulti) {
+ if ($this->isMulti) {
call_user_func_array(array($this->redisMulti, $name), $args);
return $this;
}
@@ -1219,13 +1360,16 @@ class Credis_Client {
throw $e;
}
}
- }
- // Wrap exceptions
- catch(RedisException $e) {
+ } // Wrap exceptions
+ catch (RedisException $e) {
$code = 0;
- if ( ! ($result = $this->redis->IsConnected())) {
- $this->close(true);
- $code = CredisException::CODE_DISCONNECTED;
+ try {
+ if (!($result = $this->redis->IsConnected())) {
+ $this->close(true);
+ $code = CredisException::CODE_DISCONNECTED;
+ }
+ } catch (RedisException $e2) {
+ throw new CredisException($e2->getMessage(), $e2->getCode(), $e2);
}
throw new CredisException($e->getMessage(), $code, $e);
}
@@ -1233,87 +1377,93 @@ class Credis_Client {
#echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n";
// change return values where it is too difficult to minim in standalone mode
- switch($name)
- {
- case 'type':
- $typeMap = array(
- self::TYPE_NONE,
- self::TYPE_STRING,
- self::TYPE_SET,
- self::TYPE_LIST,
- self::TYPE_ZSET,
- self::TYPE_HASH,
- );
- $response = $typeMap[$response];
- break;
-
- // Handle scripting errors
- case 'eval':
- case 'evalsha':
- case 'script':
- $error = $this->redis->getLastError();
- $this->redis->clearLastError();
- if ($error && substr($error,0,8) == 'NOSCRIPT') {
- $response = NULL;
- } else if ($error) {
- throw new CredisException($error);
- }
- break;
- case 'exists':
- // smooth over phpredis-v4 vs earlier difference to match documented credis return results
- $response = (int) $response;
- break;
- case 'ping':
- if ($response) {
- if ($response === true) {
- $response = isset($args[0]) ? $args[0] : "PONG";
- } else if ($response[0] === '+') {
- $response = substr($response, 1);
- }
- }
- break;
- case 'auth':
- if (is_bool($response) && $response === true){
+ try {
+ switch ($name) {
+ case 'type':
+ $typeMap = array(
+ self::TYPE_NONE,
+ self::TYPE_STRING,
+ self::TYPE_SET,
+ self::TYPE_LIST,
+ self::TYPE_ZSET,
+ self::TYPE_HASH,
+ );
+ $response = $typeMap[$response];
+ break;
+
+ case 'eval':
+ case 'evalsha':
+ case 'script':
+ $error = $this->redis->getLastError();
$this->redis->clearLastError();
- }
- default:
- $error = $this->redis->getLastError();
- $this->redis->clearLastError();
- if ($error) {
- throw new CredisException(rtrim($error));
- }
- break;
+ if ($error && substr($error, 0, 8) == 'NOSCRIPT') {
+ $response = null;
+ } elseif ($error) {
+ throw new CredisException($error);
+ }
+ break;
+ case 'exists':
+ // smooth over phpredis-v4 vs earlier difference to match documented credis return results
+ $response = (int)$response;
+ break;
+ case 'ping':
+ if ($response) {
+ if ($response === true) {
+ $response = isset($args[0]) ? $args[0] : "PONG";
+ } elseif ($response[0] === '+') {
+ $response = substr($response, 1);
+ }
+ }
+ break;
+ case 'auth':
+ if (is_bool($response) && $response === true) {
+ $this->redis->clearLastError();
+ }
+ // no break
+ default:
+ $error = $this->redis->getLastError();
+ $this->redis->clearLastError();
+ if ($error) {
+ throw new CredisException(rtrim($error));
+ }
+ break;
+ }
+ } catch (RedisException $e) {
+ throw new CredisException($e->getMessage(), $e->getCode(), $e);
}
}
return $response;
}
+ /**
+ * @throws CredisException
+ */
protected function write_command($command)
{
// Reconnect on lost connection (Redis server "timeout" exceeded since last command)
- if(feof($this->redis)) {
+ if (feof($this->redis)) {
// If a watch or transaction was in progress and connection was lost, throw error rather than reconnect
// since transaction/watch state will be lost.
- if(($this->isMulti && ! $this->usePipeline) || $this->isWatching) {
+ if (($this->isMulti && !$this->usePipeline) || $this->isWatching) {
$this->close(true);
throw new CredisException('Lost connection to Redis server during watch or transaction.');
}
$this->close(true);
$this->connect();
- if($this->authPassword) {
+ if ($this->authPassword) {
$this->auth($this->authPassword);
}
- if($this->selectedDb != 0) {
+ if ($this->selectedDb != 0) {
$this->select($this->selectedDb);
}
}
$commandLen = strlen($command);
- $lastFailed = FALSE;
+ $lastFailed = false;
for ($written = 0; $written < $commandLen; $written += $fwrite) {
$fwrite = fwrite($this->redis, substr($command, $written));
- if ($fwrite === FALSE || ($fwrite == 0 && $lastFailed)) {
+ if ($fwrite === false || ($fwrite == 0 && $lastFailed)) {
$this->close(true);
throw new CredisException('Failed to write entire command to stream');
}
@@ -1321,10 +1471,13 @@ class Credis_Client {
}
}
+ /**
+ * @throws CredisException
+ */
protected function read_reply($name = '', $returnQueued = false)
{
$reply = fgets($this->redis);
- if($reply === FALSE) {
+ if ($reply === false) {
$info = stream_get_meta_data($this->redis);
$this->close(true);
if ($info['timed_out']) {
@@ -1333,87 +1486,89 @@ class Credis_Client {
throw new CredisException('Lost connection to Redis server.', CredisException::CODE_DISCONNECTED);
}
}
- $reply = rtrim($reply, CRLF);
+ $reply = rtrim($reply, "\r\n");
#echo "> $name: $reply\n";
$replyType = substr($reply, 0, 1);
switch ($replyType) {
/* Error reply */
case '-':
- if($this->isMulti || $this->usePipeline) {
- $response = FALSE;
- } else if ($name == 'evalsha' && substr($reply,0,9) == '-NOSCRIPT') {
- $response = NULL;
+ if ($this->isMulti || $this->usePipeline) {
+ $response = false;
+ } elseif ($name == 'evalsha' && substr($reply, 0, 9) == '-NOSCRIPT') {
+ $response = null;
} else {
- throw new CredisException(substr($reply,0,4) == '-ERR' ? 'ERR '.substr($reply, 5) : substr($reply,1));
+ throw new CredisException(substr($reply, 0, 4) == '-ERR' ? 'ERR ' . substr($reply, 5) : substr($reply, 1));
}
break;
- /* Inline reply */
+ /* Inline reply */
case '+':
$response = substr($reply, 1);
- if($response == 'OK') {
- return TRUE;
+ if ($response == 'OK') {
+ return true;
}
- if($response == 'QUEUED') {
+ if ($response == 'QUEUED') {
return $returnQueued ? null : true;
}
break;
- /* Bulk reply */
+ /* Bulk reply */
case '$':
- if ($reply == '$-1') return FALSE;
- $size = (int) substr($reply, 1);
+ if ($reply == '$-1') {
+ return false;
+ }
+ $size = (int)substr($reply, 1);
$response = stream_get_contents($this->redis, $size + 2);
- if( ! $response) {
+ if (!$response) {
$this->close(true);
throw new CredisException('Error reading reply.');
}
$response = substr($response, 0, $size);
break;
- /* Multi-bulk reply */
+ /* Multi-bulk reply */
case '*':
$count = substr($reply, 1);
- if ($count == '-1') return FALSE;
+ if ($count == '-1') {
+ return false;
+ }
$response = array();
for ($i = 0; $i < $count; $i++) {
- $response[] = $this->read_reply();
+ $response[] = $this->read_reply();
}
break;
- /* Integer reply */
+ /* Integer reply */
case ':':
$response = intval(substr($reply, 1));
break;
default:
- throw new CredisException('Invalid response: '.print_r($reply, TRUE));
- break;
+ throw new CredisException('Invalid response: ' . print_r($reply, true));
}
return $response;
}
- protected function decode_reply($name, $response, array &$arguments = array() )
+ /**
+ * @throws CredisException
+ */
+ protected function decode_reply($name, $response, array &$arguments = array())
{
// Smooth over differences between phpredis and standalone response
- switch ($name)
- {
+ switch ($name) {
case '': // Minor optimization for multi-bulk replies
break;
case 'config':
case 'hgetall':
$keys = $values = array();
- while ($response)
- {
+ while ($response) {
$keys[] = array_shift($response);
$values[] = array_shift($response);
}
$response = count($keys) ? array_combine($keys, $values) : array();
break;
case 'info':
- $lines = explode(CRLF, trim($response, CRLF));
+ $lines = explode("\r\n", trim($response, "\r\n"));
$response = array();
- foreach ($lines as $line)
- {
- if (!$line || substr($line, 0, 1) == '#')
- {
+ foreach ($lines as $line) {
+ if (!$line || substr($line, 0, 1) == '#') {
continue;
}
list($key, $value) = explode(':', $line, 2);
@@ -1421,17 +1576,16 @@ class Credis_Client {
}
break;
case 'ttl':
- if ($response === -1)
- {
+ if ($response === -1) {
$response = false;
}
break;
case 'hmget':
- if (count($arguments) != count($response))
- {
+ if (count($arguments) != count($response)) {
throw new CredisException(
'hmget arguments and response do not match: ' . print_r($arguments, true) . ' ' . print_r(
- $response, true
+ $response,
+ true
)
);
}
@@ -1448,12 +1602,10 @@ class Credis_Client {
case 'zscan':
$arguments[0] = intval(array_shift($response));
$response = empty($response[0]) ? array() : $response[0];
- if (!empty($response) && is_array($response))
- {
+ if (!empty($response) && is_array($response)) {
$count = count($response);
$out = array();
- for ($i = 0; $i < $count; $i += 2)
- {
+ for ($i = 0; $i < $count; $i += 2) {
$out[$response[$i]] = $response[$i + 1];
}
$response = $out;
@@ -1463,19 +1615,14 @@ class Credis_Client {
case 'zrevrangebyscore':
case 'zrange':
case 'zrevrange':
- if (in_array('withscores', $arguments, true))
- {
+ if (in_array('withscores', $arguments, true)) {
// Map array of values into key=>score list like phpRedis does
$item = null;
$out = array();
- foreach ($response as $value)
- {
- if ($item == null)
- {
+ foreach ($response as $value) {
+ if ($item == null) {
$item = $value;
- }
- else
- {
+ } else {
// 2nd value is the score
$out[$item] = (float)$value;
$item = null;
@@ -1497,12 +1644,12 @@ class Credis_Client {
*/
private static function _prepare_command($args)
{
- return sprintf('*%d%s%s%s', count($args), CRLF, implode(CRLF, array_map(array('self', '_map'), $args)), CRLF);
+ return sprintf('*%d%s%s%s', count($args), "\r\n", implode("\r\n", array_map([static::class, '_map'], $args)), "\r\n");
}
private static function _map($arg)
{
- return sprintf('$%d%s%s', strlen($arg), CRLF, $arg);
+ return sprintf('$%d%s%s', strlen((string)$arg), "\r\n", $arg);
}
/**
@@ -1513,7 +1660,8 @@ class Credis_Client {
* becomes
* array('zrangebyscore', '-inf', 123, 'limit', '0', '1')
*
- * @param array $in
+ * @param array $arguments
+ * @param array $out
* @return array
*/
private static function _flattenArguments(array $arguments, &$out = array())
diff --git a/system/vendor/colinmollenhour/credis/Cluster.php b/system/vendor/colinmollenhour/credis/Cluster.php
index eda43b8..108fdd5 100644
--- a/system/vendor/colinmollenhour/credis/Cluster.php
+++ b/system/vendor/colinmollenhour/credis/Cluster.php
@@ -15,329 +15,324 @@
*/
class Credis_Cluster
{
- /**
- * Collection of Credis_Client objects attached to Redis servers
- * @var Credis_Client[]
- */
- protected $clients;
- /**
- * If a server is set as master, all write commands go to that one
- * @var Credis_Client
- */
- protected $masterClient;
- /**
- * Aliases of Credis_Client objects attached to Redis servers, used to route commands to specific servers
- * @see Credis_Cluster::to
- * @var array
- */
- protected $aliases;
+ /**
+ * Collection of Credis_Client objects attached to Redis servers
+ * @var Credis_Client[]
+ */
+ protected $clients;
+ /**
+ * If a server is set as master, all write commands go to that one
+ * @var Credis_Client
+ */
+ protected $masterClient;
+ /**
+ * Aliases of Credis_Client objects attached to Redis servers, used to route commands to specific servers
+ * @see Credis_Cluster::to
+ * @var array
+ */
+ protected $aliases;
- /**
- * Hash ring of Redis server nodes
- * @var array
- */
- protected $ring;
+ /**
+ * Hash ring of Redis server nodes
+ * @var array
+ */
+ protected $ring;
- /**
- * Individual nodes of pointers to Redis servers on the hash ring
- * @var array
- */
- protected $nodes;
+ /**
+ * Individual nodes of pointers to Redis servers on the hash ring
+ * @var array
+ */
+ protected $nodes;
- /**
- * The commands that are not subject to hashing
- * @var array
- * @access protected
- */
- protected $dont_hash;
+ /**
+ * The commands that are not subject to hashing
+ * @var array
+ * @access protected
+ */
+ protected $dont_hash;
- /**
- * Currently working cluster-wide database number.
- * @var int
- */
- protected $selectedDb = 0;
+ /**
+ * Currently working cluster-wide database number.
+ * @var int
+ */
+ protected $selectedDb = 0;
- /**
- * Creates an interface to a cluster of Redis servers
- * Each server should be in the format:
- * array(
- * 'host' => hostname,
- * 'port' => port,
- * 'db' => db,
- * 'password' => password,
- * 'timeout' => timeout,
- * 'alias' => alias,
- * 'persistent' => persistence_identifier,
- * 'master' => master
- * 'write_only'=> true/false
- * )
- *
- * @param array $servers The Redis servers in the cluster.
- * @param int $replicas
- * @param bool $standAlone
- * @throws CredisException
- */
- public function __construct($servers, $replicas = 128, $standAlone = false)
- {
- $this->clients = array();
- $this->masterClient = null;
- $this->aliases = array();
- $this->ring = array();
- $this->replicas = (int)$replicas;
- $client = null;
- foreach ($servers as $server)
+ /**
+ * Creates an interface to a cluster of Redis servers
+ * Each server should be in the format:
+ * array(
+ * 'host' => hostname,
+ * 'port' => port,
+ * 'db' => db,
+ * 'password' => password,
+ * 'timeout' => timeout,
+ * 'alias' => alias,
+ * 'persistent' => persistence_identifier,
+ * 'master' => master
+ * 'write_only'=> true/false
+ * )
+ *
+ * @param array $servers The Redis servers in the cluster.
+ * @param int $replicas
+ * @param bool $standAlone
+ * @throws CredisException
+ */
+ public function __construct($servers, $replicas = 128, $standAlone = false)
{
- if(is_array($server)){
- $client = new Credis_Client(
- $server['host'],
- $server['port'],
- isset($server['timeout']) ? $server['timeout'] : 2.5,
- isset($server['persistent']) ? $server['persistent'] : '',
- isset($server['db']) ? $server['db'] : 0,
- isset($server['password']) ? $server['password'] : null
- );
- if (isset($server['alias'])) {
- $this->aliases[$server['alias']] = $client;
- }
- if(isset($server['master']) && $server['master'] === true){
- $this->masterClient = $client;
- if(isset($server['write_only']) && $server['write_only'] === true){
- continue;
+ $this->clients = array();
+ $this->masterClient = null;
+ $this->aliases = array();
+ $this->ring = array();
+ $this->replicas = (int)$replicas;
+ $client = null;
+ foreach ($servers as $server) {
+ if (is_array($server)) {
+ $client = new Credis_Client(
+ $server['host'],
+ $server['port'],
+ isset($server['timeout']) ? $server['timeout'] : 2.5,
+ isset($server['persistent']) ? $server['persistent'] : '',
+ isset($server['db']) ? $server['db'] : 0,
+ isset($server['password']) ? $server['password'] : null
+ );
+ if (isset($server['alias'])) {
+ $this->aliases[$server['alias']] = $client;
+ }
+ if (isset($server['master']) && $server['master'] === true) {
+ $this->masterClient = $client;
+ if (isset($server['write_only']) && $server['write_only'] === true) {
+ continue;
+ }
+ }
+ } elseif ($server instanceof Credis_Client) {
+ $client = $server;
+ } else {
+ throw new CredisException('Server should either be an array or an instance of Credis_Client');
+ }
+ if ($standAlone) {
+ $client->forceStandalone();
+ }
+ $this->clients[] = $client;
+ for ($replica = 0; $replica <= $this->replicas; $replica++) {
+ $md5num = hexdec(substr(md5($client->getHost() . ':' . $client->getPort() . '-' . $replica), 0, 7));
+ $this->ring[$md5num] = count($this->clients) - 1;
}
- }
- } elseif($server instanceof Credis_Client){
- $client = $server;
- } else {
- throw new CredisException('Server should either be an array or an instance of Credis_Client');
- }
- if($standAlone) {
- $client->forceStandalone();
- }
- $this->clients[] = $client;
- for ($replica = 0; $replica <= $this->replicas; $replica++) {
- $md5num = hexdec(substr(md5($client->getHost().':'.$client->getPort().'-'.$replica),0,7));
- $this->ring[$md5num] = count($this->clients)-1;
- }
- }
- ksort($this->ring, SORT_NUMERIC);
- $this->nodes = array_keys($this->ring);
- $this->dont_hash = array_flip(array(
- 'RANDOMKEY', 'DBSIZE', 'PIPELINE', 'EXEC',
- 'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL',
- 'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN',
- 'INFO', 'MONITOR', 'SLAVEOF'
- ));
- if($this->masterClient !== null && count($this->clients()) == 0){
- $this->clients[] = $this->masterClient;
- for ($replica = 0; $replica <= $this->replicas; $replica++) {
- $md5num = hexdec(substr(md5($this->masterClient->getHost().':'.$this->masterClient->getHost().'-'.$replica),0,7));
- $this->ring[$md5num] = count($this->clients)-1;
}
+ ksort($this->ring, SORT_NUMERIC);
$this->nodes = array_keys($this->ring);
+ $this->dont_hash = array_flip(array(
+ 'RANDOMKEY', 'DBSIZE', 'PIPELINE', 'EXEC',
+ 'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL',
+ 'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN',
+ 'INFO', 'MONITOR', 'SLAVEOF'
+ ));
+ if ($this->masterClient !== null && count($this->clients()) == 0) {
+ $this->clients[] = $this->masterClient;
+ for ($replica = 0; $replica <= $this->replicas; $replica++) {
+ $md5num = hexdec(substr(md5($this->masterClient->getHost() . ':' . $this->masterClient->getHost() . '-' . $replica), 0, 7));
+ $this->ring[$md5num] = count($this->clients) - 1;
+ }
+ $this->nodes = array_keys($this->ring);
+ }
}
- }
- /**
- * @param Credis_Client $masterClient
- * @param bool $writeOnly
- * @return Credis_Cluster
- */
- public function setMasterClient(Credis_Client $masterClient, $writeOnly=false)
- {
- if(!$masterClient instanceof Credis_Client){
- throw new CredisException('Master client should be an instance of Credis_Client');
- }
- $this->masterClient = $masterClient;
- if (!isset($this->aliases['master'])) {
- $this->aliases['master'] = $masterClient;
- }
- if(!$writeOnly){
- $this->clients[] = $this->masterClient;
- for ($replica = 0; $replica <= $this->replicas; $replica++) {
- $md5num = hexdec(substr(md5($this->masterClient->getHost().':'.$this->masterClient->getHost().'-'.$replica),0,7));
- $this->ring[$md5num] = count($this->clients)-1;
+ /**
+ * @param Credis_Client $masterClient
+ * @param bool $writeOnly
+ * @return Credis_Cluster
+ */
+ public function setMasterClient(Credis_Client $masterClient, $writeOnly = false)
+ {
+ if (!$masterClient instanceof Credis_Client) {
+ throw new CredisException('Master client should be an instance of Credis_Client');
}
- $this->nodes = array_keys($this->ring);
- }
- return $this;
- }
- /**
- * Get a client by index or alias.
- *
- * @param string|int $alias
- * @throws CredisException
- * @return Credis_Client
- */
- public function client($alias)
- {
- if (is_int($alias) && isset($this->clients[$alias])) {
- return $this->clients[$alias];
- }
- else if (isset($this->aliases[$alias])) {
- return $this->aliases[$alias];
+ $this->masterClient = $masterClient;
+ if (!isset($this->aliases['master'])) {
+ $this->aliases['master'] = $masterClient;
+ }
+ if (!$writeOnly) {
+ $this->clients[] = $this->masterClient;
+ for ($replica = 0; $replica <= $this->replicas; $replica++) {
+ $md5num = hexdec(substr(md5($this->masterClient->getHost() . ':' . $this->masterClient->getHost() . '-' . $replica), 0, 7));
+ $this->ring[$md5num] = count($this->clients) - 1;
+ }
+ $this->nodes = array_keys($this->ring);
+ }
+ return $this;
}
- throw new CredisException("Client $alias does not exist.");
- }
- /**
- * Get an array of all clients
- *
- * @return array|Credis_Client[]
- */
- public function clients()
- {
- return $this->clients;
- }
-
- /**
- * Execute a command on all clients
- *
- * @return array
- */
- public function all()
- {
- $args = func_get_args();
- $name = array_shift($args);
- $results = array();
- foreach($this->clients as $client) {
- $results[] = call_user_func_array([$client, $name], $args);
+ /**
+ * Get a client by index or alias.
+ *
+ * @param string|int $alias
+ * @return Credis_Client
+ * @throws CredisException
+ */
+ public function client($alias)
+ {
+ if (is_int($alias) && isset($this->clients[$alias])) {
+ return $this->clients[$alias];
+ } elseif (isset($this->aliases[$alias])) {
+ return $this->aliases[$alias];
+ }
+ throw new CredisException("Client $alias does not exist.");
}
- return $results;
- }
- /**
- * Get the client that the key would hash to.
- *
- * @param string $key
- * @return \Credis_Client
- */
- public function byHash($key)
- {
- return $this->clients[$this->hash($key)];
- }
+ /**
+ * Get an array of all clients
+ *
+ * @return array|Credis_Client[]
+ */
+ public function clients()
+ {
+ return $this->clients;
+ }
- /**
- * @param int $index
- * @return void
- */
- public function select($index)
- {
- $this->selectedDb = (int) $index;
- }
+ /**
+ * Execute a command on all clients
+ *
+ * @return array
+ */
+ public function all()
+ {
+ $args = func_get_args();
+ $name = array_shift($args);
+ $results = array();
+ foreach ($this->clients as $client) {
+ $results[] = call_user_func_array([$client, $name], $args);
+ }
+ return $results;
+ }
- /**
- * Execute a Redis command on the cluster with automatic consistent hashing and read/write splitting
- *
- * @param string $name
- * @param array $args
- * @return mixed
- */
- public function __call($name, $args)
- {
- if($this->masterClient !== null && !$this->isReadOnlyCommand($name)){
- $client = $this->masterClient;
- }elseif (count($this->clients()) == 1 || isset($this->dont_hash[strtoupper($name)]) || !isset($args[0])) {
- $client = $this->clients[0];
+ /**
+ * Get the client that the key would hash to.
+ *
+ * @param string $key
+ * @return \Credis_Client
+ */
+ public function byHash($key)
+ {
+ return $this->clients[$this->hash($key)];
}
- else {
- $hashKey = $args[0];
- if (is_array($hashKey)) {
- $hashKey = join('|', $hashKey);
- }
- $client = $this->byHash($hashKey);
+
+ /**
+ * @param int $index
+ * @return void
+ */
+ public function select($index)
+ {
+ $this->selectedDb = (int)$index;
}
- // Ensure that current client is working on the same database as expected.
- if ($client->getSelectedDb() != $this->selectedDb) {
- $client->select($this->selectedDb);
+
+ /**
+ * Execute a Redis command on the cluster with automatic consistent hashing and read/write splitting
+ *
+ * @param string $name
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($name, $args)
+ {
+ if ($this->masterClient !== null && !$this->isReadOnlyCommand($name)) {
+ $client = $this->masterClient;
+ } elseif (count($this->clients()) == 1 || isset($this->dont_hash[strtoupper($name)]) || !isset($args[0])) {
+ $client = $this->clients[0];
+ } else {
+ $hashKey = $args[0];
+ if (is_array($hashKey)) {
+ $hashKey = join('|', $hashKey);
+ }
+ $client = $this->byHash($hashKey);
+ }
+ // Ensure that current client is working on the same database as expected.
+ if ($client->getSelectedDb() != $this->selectedDb) {
+ $client->select($this->selectedDb);
+ }
+ return call_user_func_array([$client, $name], $args);
}
- return call_user_func_array([$client, $name], $args);
- }
- /**
- * Get client index for a key by searching ring with binary search
- *
- * @param string $key The key to hash
- * @return int The index of the client object associated with the hash of the key
- */
- public function hash($key)
- {
- $needle = hexdec(substr(md5($key),0,7));
- $server = $min = 0;
- $max = count($this->nodes) - 1;
- while ($max >= $min) {
- $position = (int) (($min + $max) / 2);
- $server = $this->nodes[$position];
- if ($needle < $server) {
- $max = $position - 1;
- }
- else if ($needle > $server) {
- $min = $position + 1;
- }
- else {
- break;
- }
+ /**
+ * Get client index for a key by searching ring with binary search
+ *
+ * @param string $key The key to hash
+ * @return int The index of the client object associated with the hash of the key
+ */
+ public function hash($key)
+ {
+ $needle = hexdec(substr(md5($key), 0, 7));
+ $server = $min = 0;
+ $max = count($this->nodes) - 1;
+ while ($max >= $min) {
+ $position = (int)(($min + $max) / 2);
+ $server = $this->nodes[$position];
+ if ($needle < $server) {
+ $max = $position - 1;
+ } elseif ($needle > $server) {
+ $min = $position + 1;
+ } else {
+ break;
+ }
+ }
+ return $this->ring[$server];
}
- return $this->ring[$server];
- }
- public function isReadOnlyCommand($command)
- {
- static $readOnlyCommands = array(
- 'DBSIZE' => true,
- 'INFO' => true,
- 'MONITOR' => true,
- 'EXISTS' => true,
- 'TYPE' => true,
- 'KEYS' => true,
- 'SCAN' => true,
- 'RANDOMKEY' => true,
- 'TTL' => true,
- 'GET' => true,
- 'MGET' => true,
- 'SUBSTR' => true,
- 'STRLEN' => true,
- 'GETRANGE' => true,
- 'GETBIT' => true,
- 'LLEN' => true,
- 'LRANGE' => true,
- 'LINDEX' => true,
- 'SCARD' => true,
- 'SISMEMBER' => true,
- 'SINTER' => true,
- 'SUNION' => true,
- 'SDIFF' => true,
- 'SMEMBERS' => true,
- 'SSCAN' => true,
- 'SRANDMEMBER' => true,
- 'ZRANGE' => true,
- 'ZREVRANGE' => true,
- 'ZRANGEBYSCORE' => true,
- 'ZREVRANGEBYSCORE' => true,
- 'ZCARD' => true,
- 'ZSCORE' => true,
- 'ZCOUNT' => true,
- 'ZRANK' => true,
- 'ZREVRANK' => true,
- 'ZSCAN' => true,
- 'HGET' => true,
- 'HMGET' => true,
- 'HEXISTS' => true,
- 'HLEN' => true,
- 'HKEYS' => true,
- 'HVALS' => true,
- 'HGETALL' => true,
- 'HSCAN' => true,
- 'PING' => true,
- 'AUTH' => true,
- 'SELECT' => true,
- 'ECHO' => true,
- 'QUIT' => true,
- 'OBJECT' => true,
- 'BITCOUNT' => true,
- 'TIME' => true,
- 'SORT' => true,
- );
- return array_key_exists(strtoupper($command), $readOnlyCommands);
- }
+ public function isReadOnlyCommand($command)
+ {
+ static $readOnlyCommands = array(
+ 'DBSIZE' => true,
+ 'INFO' => true,
+ 'MONITOR' => true,
+ 'EXISTS' => true,
+ 'TYPE' => true,
+ 'KEYS' => true,
+ 'SCAN' => true,
+ 'RANDOMKEY' => true,
+ 'TTL' => true,
+ 'GET' => true,
+ 'MGET' => true,
+ 'SUBSTR' => true,
+ 'STRLEN' => true,
+ 'GETRANGE' => true,
+ 'GETBIT' => true,
+ 'LLEN' => true,
+ 'LRANGE' => true,
+ 'LINDEX' => true,
+ 'SCARD' => true,
+ 'SISMEMBER' => true,
+ 'SINTER' => true,
+ 'SUNION' => true,
+ 'SDIFF' => true,
+ 'SMEMBERS' => true,
+ 'SSCAN' => true,
+ 'SRANDMEMBER' => true,
+ 'ZRANGE' => true,
+ 'ZREVRANGE' => true,
+ 'ZRANGEBYSCORE' => true,
+ 'ZREVRANGEBYSCORE' => true,
+ 'ZCARD' => true,
+ 'ZSCORE' => true,
+ 'ZCOUNT' => true,
+ 'ZRANK' => true,
+ 'ZREVRANK' => true,
+ 'ZSCAN' => true,
+ 'HGET' => true,
+ 'HMGET' => true,
+ 'HEXISTS' => true,
+ 'HLEN' => true,
+ 'HKEYS' => true,
+ 'HVALS' => true,
+ 'HGETALL' => true,
+ 'HSCAN' => true,
+ 'PING' => true,
+ 'AUTH' => true,
+ 'SELECT' => true,
+ 'ECHO' => true,
+ 'QUIT' => true,
+ 'OBJECT' => true,
+ 'BITCOUNT' => true,
+ 'TIME' => true,
+ 'SORT' => true,
+ );
+ return array_key_exists(strtoupper($command), $readOnlyCommands);
+ }
}
-
diff --git a/system/vendor/colinmollenhour/credis/Module.php b/system/vendor/colinmollenhour/credis/Module.php
index 70b9ba9..f85a699 100644
--- a/system/vendor/colinmollenhour/credis/Module.php
+++ b/system/vendor/colinmollenhour/credis/Module.php
@@ -47,7 +47,7 @@ class Credis_Module
*/
public function setModule($moduleName)
{
- $this->moduleName = (string) $moduleName;
+ $this->moduleName = (string)$moduleName;
return $this;
}
diff --git a/system/vendor/colinmollenhour/credis/README.markdown b/system/vendor/colinmollenhour/credis/README.markdown
index bf1c8e9..0a9f09a 100644
--- a/system/vendor/colinmollenhour/credis/README.markdown
+++ b/system/vendor/colinmollenhour/credis/README.markdown
@@ -44,6 +44,14 @@ $redis = new Credis_Client(/* connection string */);
`tls://host[:port][/persistence_identifier]`
+or
+
+`tlsv1.2://host[:port][/persistence_identifier]`
+
+Before php 7.2, `tls://` only supports TLSv1.0, either `ssl://` or `tlsv1.2` can be used to force TLSv1.2 support.
+
+Recent versions of redis do not support the protocols/cyphers that older versions of php default to, which may result in cryptic connection failures.
+
#### Enable transport level security (TLS)
Use TLS connection string `tls://127.0.0.1:6379` instead of TCP connection `tcp://127.0.0.1:6379` string in order to enable transport level security.
diff --git a/system/vendor/colinmollenhour/credis/Sentinel.php b/system/vendor/colinmollenhour/credis/Sentinel.php
index 5a7c2a4..995d218 100644
--- a/system/vendor/colinmollenhour/credis/Sentinel.php
+++ b/system/vendor/colinmollenhour/credis/Sentinel.php
@@ -52,6 +52,31 @@ class Credis_Sentinel
* @var string
*/
protected $_password = '';
+ /**
+ * Store the AUTH username used by Credis_Client instances (Redis v6+)
+ * @var string
+ */
+ protected $_username = '';
+ /**
+ * @var null|float
+ */
+ protected $_timeout;
+ /**
+ * @var string
+ */
+ protected $_persistent;
+ /**
+ * @var int
+ */
+ protected $_db;
+ /**
+ * @var string|null
+ */
+ protected $_replicaCmd = null;
+ /**
+ * @var string|null
+ */
+ protected $_redisVersion = null;
/**
* Connect with a Sentinel node. Sentinel will do the master and slave discovery
@@ -60,17 +85,15 @@ class Credis_Sentinel
* @param string $password (deprecated - use setClientPassword)
* @throws CredisException
*/
- public function __construct(Credis_Client $client, $password = NULL)
+ public function __construct(Credis_Client $client, $password = null, $username = null)
{
- if(!$client instanceof Credis_Client){
- throw new CredisException('Sentinel client should be an instance of Credis_Client');
- }
$client->forceStandalone(); // SENTINEL command not currently supported by phpredis
- $this->_client = $client;
- $this->_password = $password;
- $this->_timeout = NULL;
+ $this->_client = $client;
+ $this->_password = $password;
+ $this->_username = $username;
+ $this->_timeout = null;
$this->_persistent = '';
- $this->_db = 0;
+ $this->_db = 0;
}
/**
@@ -121,6 +144,37 @@ class Credis_Sentinel
return $this;
}
+ /**
+ * @param null|string $username
+ * @return $this
+ */
+ public function setClientUsername($username)
+ {
+ $this->_username = $username;
+ return $this;
+ }
+
+ /**
+ * @param null|string $replicaCmd
+ * @return $this
+ */
+ public function setReplicaCommand($replicaCmd)
+ {
+ $this->_replicaCmd = $replicaCmd;
+ return $this;
+ }
+
+ public function detectRedisVersion()
+ {
+ if ($this->_redisVersion !== null && $this->_replicaCmd !== null) {
+ return;
+ }
+ $serverInfo = $this->info('server');
+ $this->_redisVersion = $serverInfo['redis_version'];
+ // Redis v7+ renames the replica command to 'replicas' instead of 'slaves'
+ $this->_replicaCmd = version_compare($this->_redisVersion, '7.0.0', '>=') ? 'replicas' : 'slaves';
+ }
+
/**
* @return Credis_Sentinel
* @deprecated
@@ -141,10 +195,10 @@ class Credis_Sentinel
public function createMasterClient($name)
{
$master = $this->getMasterAddressByName($name);
- if(!isset($master[0]) || !isset($master[1])){
+ if (!isset($master[0]) || !isset($master[1])) {
throw new CredisException('Master not found');
}
- return new Credis_Client($master[0], $master[1], $this->_timeout, $this->_persistent, $this->_db, $this->_password);
+ return new Credis_Client($master[0], $master[1], $this->_timeout, $this->_persistent, $this->_db, $this->_password, $this->_username);
}
/**
@@ -154,7 +208,7 @@ class Credis_Sentinel
*/
public function getMasterClient($name)
{
- if(!isset($this->_master[$name])){
+ if (!isset($this->_master[$name])) {
$this->_master[$name] = $this->createMasterClient($name);
}
return $this->_master[$name];
@@ -171,12 +225,12 @@ class Credis_Sentinel
{
$slaves = $this->slaves($name);
$workingSlaves = array();
- foreach($slaves as $slave) {
- if(!isset($slave[9])){
+ foreach ($slaves as $slave) {
+ if (!isset($slave[9])) {
throw new CredisException('Can\' retrieve slave status');
}
- if(!strstr($slave[9],'s_down') && !strstr($slave[9],'disconnected')) {
- $workingSlaves[] = new Credis_Client($slave[3], $slave[5], $this->_timeout, $this->_persistent, $this->_db, $this->_password);
+ if (!strstr($slave[9], 's_down') && !strstr($slave[9], 'disconnected')) {
+ $workingSlaves[] = new Credis_Client($slave[3], $slave[5], $this->_timeout, $this->_persistent, $this->_db, $this->_password, $this->_username);
}
}
return $workingSlaves;
@@ -189,7 +243,7 @@ class Credis_Sentinel
*/
public function getSlaveClients($name)
{
- if(!isset($this->_slaves[$name])){
+ if (!isset($this->_slaves[$name])) {
$this->_slaves[$name] = $this->createSlaveClients($name);
}
return $this->_slaves[$name];
@@ -211,27 +265,27 @@ class Credis_Sentinel
* @throws CredisException
* @deprecated
*/
- public function createCluster($name, $db=0, $replicas=128, $selectRandomSlave=true, $writeOnly=false, $masterOnly=false)
+ public function createCluster($name, $db = 0, $replicas = 128, $selectRandomSlave = true, $writeOnly = false, $masterOnly = false)
{
$clients = array();
$workingClients = array();
$master = $this->master($name);
- if(strstr($master[9],'s_down') || strstr($master[9],'disconnected')) {
+ if (strstr($master[9], 's_down') || strstr($master[9], 'disconnected')) {
throw new CredisException('The master is down');
}
if (!$masterOnly) {
$slaves = $this->slaves($name);
- foreach($slaves as $slave){
- if(!strstr($slave[9],'s_down') && !strstr($slave[9],'disconnected')) {
- $workingClients[] = array('host'=>$slave[3],'port'=>$slave[5],'master'=>false,'db'=>$db,'password'=>$this->_password);
+ foreach ($slaves as $slave) {
+ if (!strstr($slave[9], 's_down') && !strstr($slave[9], 'disconnected')) {
+ $workingClients[] = array('host' => $slave[3], 'port' => $slave[5], 'master' => false, 'db' => $db, 'password' => $this->_password);
}
}
- if(count($workingClients)>0){
- if($selectRandomSlave){
- if(!$writeOnly){
- $workingClients[] = array('host'=>$master[3],'port'=>$master[5],'master'=>false,'db'=>$db,'password'=>$this->_password);
+ if (count($workingClients) > 0) {
+ if ($selectRandomSlave) {
+ if (!$writeOnly) {
+ $workingClients[] = array('host' => $master[3], 'port' => $master[5], 'master' => false, 'db' => $db, 'password' => $this->_password);
}
- $clients[] = $workingClients[rand(0,count($workingClients)-1)];
+ $clients[] = $workingClients[rand(0, count($workingClients) - 1)];
} else {
$clients = $workingClients;
}
@@ -239,8 +293,8 @@ class Credis_Sentinel
} else {
$writeOnly = false;
}
- $clients[] = array('host'=>$master[3],'port'=>$master[5], 'db'=>$db ,'master'=>true,'write_only'=>$writeOnly,'password'=>$this->_password);
- return new Credis_Cluster($clients,$replicas,$this->_standAlone);
+ $clients[] = array('host' => $master[3], 'port' => $master[5], 'db' => $db, 'master' => true, 'write_only' => $writeOnly, 'password' => $this->_password);
+ return new Credis_Cluster($clients, $replicas, $this->_standAlone);
}
/**
@@ -255,9 +309,9 @@ class Credis_Sentinel
* @throws CredisException
* @deprecated
*/
- public function getCluster($name, $db=0, $replicas=128, $selectRandomSlave=true, $writeOnly=false, $masterOnly=false)
+ public function getCluster($name, $db = 0, $replicas = 128, $selectRandomSlave = true, $writeOnly = false, $masterOnly = false)
{
- if(!isset($this->_cluster[$name])){
+ if (!isset($this->_cluster[$name])) {
$this->_cluster[$name] = $this->createCluster($name, $db, $replicas, $selectRandomSlave, $writeOnly, $masterOnly);
}
return $this->_cluster[$name];
@@ -271,8 +325,8 @@ class Credis_Sentinel
*/
public function __call($name, $args)
{
- array_unshift($args,$name);
- return call_user_func(array($this->_client,'sentinel'),$args);
+ array_unshift($args, $name);
+ return call_user_func(array($this->_client, 'sentinel'), $args);
}
/**
@@ -284,8 +338,7 @@ class Credis_Sentinel
*/
public function info($section = null)
{
- if ($section)
- {
+ if ($section) {
return $this->_client->info($section);
}
return $this->_client->info();
@@ -307,7 +360,10 @@ class Credis_Sentinel
*/
public function slaves($name)
{
- return $this->_client->sentinel('slaves',$name);
+ if ($this->_replicaCmd === null) {
+ $this->detectRedisVersion();
+ }
+ return $this->_client->sentinel($this->_replicaCmd, $name);
}
/**
@@ -317,7 +373,7 @@ class Credis_Sentinel
*/
public function master($name)
{
- return $this->_client->sentinel('master',$name);
+ return $this->_client->sentinel('master', $name);
}
/**
@@ -327,7 +383,7 @@ class Credis_Sentinel
*/
public function getMasterAddressByName($name)
{
- return $this->_client->sentinel('get-master-addr-by-name',$name);
+ return $this->_client->sentinel('get-master-addr-by-name', $name);
}
/**
@@ -346,7 +402,7 @@ class Credis_Sentinel
*/
public function failover($name)
{
- return $this->_client->sentinel('failover',$name);
+ return $this->_client->sentinel('failover', $name);
}
/**
diff --git a/system/vendor/colinmollenhour/credis/testenv/docker-compose.yml b/system/vendor/colinmollenhour/credis/testenv/docker-compose.yml
index c527d8f..8b2b6bf 100644
--- a/system/vendor/colinmollenhour/credis/testenv/docker-compose.yml
+++ b/system/vendor/colinmollenhour/credis/testenv/docker-compose.yml
@@ -1,30 +1,5 @@
version: '2'
services:
-
- php-56:
- build: env/php-5.6/
- volumes:
- - ../:/src/
-
- php-70:
- build: env/php-7.0/
- volumes:
- - ../:/src/
-
- php-71:
- build: env/php-7.1/
- volumes:
- - ../:/src/
-
- php-72:
- build: env/php-7.2/
- volumes:
- - ../:/src/
-
- php-73:
- build: env/php-7.3/
- volumes:
- - ../:/src/
php-74:
build: env/php-7.4/
diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile
deleted file mode 100644
index c6378a4..0000000
--- a/system/vendor/colinmollenhour/credis/testenv/env/php-5.6/Dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-FROM php:5.6
-ENV phpunit_verison 5.7
-ENV redis_version 4.0.11
-
-RUN apt-get update && \
- apt-get install -y wget
-
-RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \
- chmod +x phpunit-${phpunit_verison}.phar && \
- mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit
-
-# install php extension
-RUN yes '' | pecl install -f redis-4.3.0 && \
- docker-php-ext-enable redis
-
-# install redis server
-RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \
- tar -xzf redis-${redis_version}.tar.gz && \
- make -s -C redis-${redis_version} -j
-
-CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \
- cp -rp /src /app && \
- cd /app && \
- phpunit
diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile
deleted file mode 100644
index 82dfef9..0000000
--- a/system/vendor/colinmollenhour/credis/testenv/env/php-7.0/Dockerfile
+++ /dev/null
@@ -1,25 +0,0 @@
-FROM php:7.0
-ENV phpunit_verison 6.5
-ENV redis_version 6.0.8
-
-RUN apt-get update && \
- apt-get install -y wget libssl-dev
-
-RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \
- chmod +x phpunit-${phpunit_verison}.phar && \
- mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit
-
-# install php extension
-RUN yes '' | pecl install -f redis && \
- docker-php-ext-enable redis
-
-# install redis server
-RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \
- tar -xzf redis-${redis_version}.tar.gz && \
- export BUILD_TLS=yes && \
- make -s -C redis-${redis_version} -j
-
-CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \
- cp -rp /src /app && \
- cd /app && \
- phpunit
diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile
deleted file mode 100644
index e75a4c0..0000000
--- a/system/vendor/colinmollenhour/credis/testenv/env/php-7.1/Dockerfile
+++ /dev/null
@@ -1,25 +0,0 @@
-FROM php:7.1
-ENV phpunit_verison 7.5
-ENV redis_version 6.0.8
-
-RUN apt-get update && \
- apt-get install -y wget libssl-dev
-
-RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \
- chmod +x phpunit-${phpunit_verison}.phar && \
- mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit
-
-# install php extension
-RUN yes '' | pecl install -f redis && \
- docker-php-ext-enable redis
-
-# install redis server
-RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \
- tar -xzf redis-${redis_version}.tar.gz && \
- export BUILD_TLS=yes && \
- make -s -C redis-${redis_version} -j
-
-CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \
- cp -rp /src /app && \
- cd /app && \
- phpunit
diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile
deleted file mode 100644
index 0e34aab..0000000
--- a/system/vendor/colinmollenhour/credis/testenv/env/php-7.2/Dockerfile
+++ /dev/null
@@ -1,25 +0,0 @@
-FROM php:7.2
-ENV phpunit_verison 7.5
-ENV redis_version 6.0.8
-
-RUN apt-get update && \
- apt-get install -y wget libssl-dev
-
-RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \
- chmod +x phpunit-${phpunit_verison}.phar && \
- mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit
-
-# install php extension
-RUN yes '' | pecl install -f redis && \
- docker-php-ext-enable redis
-
-# install redis server
-RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \
- tar -xzf redis-${redis_version}.tar.gz && \
- export BUILD_TLS=yes && \
- make -s -C redis-${redis_version} -j
-
-CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \
- cp -rp /src /app && \
- cd /app && \
- phpunit
diff --git a/system/vendor/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile b/system/vendor/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile
deleted file mode 100644
index 279ffa9..0000000
--- a/system/vendor/colinmollenhour/credis/testenv/env/php-7.3/Dockerfile
+++ /dev/null
@@ -1,25 +0,0 @@
-FROM php:7.3
-ENV phpunit_verison 7.5
-ENV redis_version 6.0.8
-
-RUN apt-get update && \
- apt-get install -y wget libssl-dev
-
-RUN wget https://phar.phpunit.de/phpunit-${phpunit_verison}.phar && \
- chmod +x phpunit-${phpunit_verison}.phar && \
- mv phpunit-${phpunit_verison}.phar /usr/local/bin/phpunit
-
-# install php extension
-RUN yes '' | pecl install -f redis && \
- docker-php-ext-enable redis
-
-# install redis server
-RUN wget http://download.redis.io/releases/redis-${redis_version}.tar.gz && \
- tar -xzf redis-${redis_version}.tar.gz && \
- export BUILD_TLS=yes && \
- make -s -C redis-${redis_version} -j
-
-CMD PATH=$PATH:/usr/local/bin/:/redis-${redis_version}/src/ && \
- cp -rp /src /app && \
- cd /app && \
- phpunit
diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/.gitignore b/system/vendor/colinmollenhour/php-redis-session-abstract/.gitignore
new file mode 100644
index 0000000..756b807
--- /dev/null
+++ b/system/vendor/colinmollenhour/php-redis-session-abstract/.gitignore
@@ -0,0 +1,4 @@
+vendor/
+.idea/
+phpunit.xml
+composer.lock
diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/ISSUE_TEMPLATE.md b/system/vendor/colinmollenhour/php-redis-session-abstract/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..e0e5c33
--- /dev/null
+++ b/system/vendor/colinmollenhour/php-redis-session-abstract/ISSUE_TEMPLATE.md
@@ -0,0 +1,2 @@
+
+
diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/LICENSE b/system/vendor/colinmollenhour/php-redis-session-abstract/LICENSE
new file mode 100644
index 0000000..058c73e
--- /dev/null
+++ b/system/vendor/colinmollenhour/php-redis-session-abstract/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2013, Colin Mollenhour
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * The name of Colin Mollenhour may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ * Redistributions in any form must not change the Cm_RedisSession namespace.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/README.md b/system/vendor/colinmollenhour/php-redis-session-abstract/README.md
new file mode 100644
index 0000000..22d6e3b
--- /dev/null
+++ b/system/vendor/colinmollenhour/php-redis-session-abstract/README.md
@@ -0,0 +1,68 @@
+# php-redis-session-abstract #
+
+### A Redis-based session handler with optimistic locking. ###
+
+#### Features: ####
+- When a session's data size exceeds the compression threshold the session data will be compressed.
+- Compression libraries supported are 'gzip', 'lzf', 'lz4', and 'snappy'.
+ - Gzip is the slowest but offers the best compression ratios.
+ - Lzf can be installed easily via PECL.
+ - Lz4 is supported by HHVM.
+- Compression can be enabled, disabled, or reconfigured on the fly with no loss of session data.
+- Expiration is handled by Redis; no garbage collection needed.
+- Logs when sessions are not written due to not having or losing their lock.
+- Limits the number of concurrent lock requests.
+- Detects inactive waiting processes to prevent false-positives in concurrency throttling.
+- Detects crashed processes to prevent session deadlocks (Linux only).
+- Gives shorter session lifetimes to bots and crawlers to reduce wasted resources.
+- Locking can be disabled entirely
+
+#### Locking Algorithm Properties: ####
+- Only one process may get a write lock on a session.
+- A process may lose it's lock if another process breaks it, in which case the session will not be written.
+- The lock may be broken after `BREAK_AFTER` seconds and the process that gets the lock is indeterminate.
+- Only `MAX_CONCURRENCY` processes may be waiting for a lock for the same session or else a ConcurrentConnectionsExceededException will be thrown.
+
+### Compression ##
+
+Session data compresses very well so using compression is a great way to increase your capacity without
+dedicating a ton of RAM to Redis and reducing network utilization.
+The default `compression threshold` is 2048 bytes so any session data equal to or larger than this size
+will be compressed with the chosen `compression_lib` which is `gzip` by default. Compression can be disabled by setting the `compression_lib` to `none`. However, both `lzf` and
+`snappy` offer much faster compression with comparable compression ratios so I definitely recommend using
+one of these if you have root. lzf is easy to install via pecl:
+
+ sudo pecl install lzf
+
+_NOTE:_ If using suhosin with session data encryption enabled (default is `suhosin.session.encrypt=on`), two things:
+
+1. You will probably get very poor compression ratios.
+2. Lzf fails to compress the encrypted data in my experience. No idea why...
+
+If any compression lib fails to compress the session data an error will be logged in `system.log` and the
+session will still be saved without compression. If you have `suhosin.session.encrypt=on` I would either
+recommend disabling it (unless you are on a shared host since Magento does it's own session validation already)
+or disable compression or at least don't use lzf with encryption enabled.
+
+## Bot Detection ##
+
+Bots and crawlers typically do not use cookies which means you may be storing thousands of sessions that
+serve no purpose. Even worse, an attacker could use your limited session storage against you by flooding
+your backend, thereby causing your legitimate sessions to get evicted. However, you don't want to misidentify
+a user as a bot and kill their session unintentionally. This module uses both a regex as well as a
+counter on the number of writes against the session to determine the session lifetime.
+
+## Using with [Cm_Cache_Backend_Redis](https://github.com/colinmollenhour/Cm_Cache_Backend_Redis) ##
+
+Using Cm_RedisSession alongside Cm_Cache_Backend_Redis should be no problem at all. However, it is strongly advised
+to run two separate Redis instances even if they are running on the same server. Running two instances will
+actually perform better since Redis is single-threaded so on a multi-core server is bound by the performance of
+a single core. Also it makes sense to allocate varying amounts of memory to cache and sessions and to enforce different
+"maxmemory" policies. If you absolutely must run one Redis instance for both then just don't use the same 'db' number.
+But again, just run two Redis instances.
+
+
+## License ##
+
+ @copyright Copyright (c) 2013 Colin Mollenhour (http://colin.mollenhour.com)
+ This project is licensed under the "New BSD" license (see source).
diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/composer.json b/system/vendor/colinmollenhour/php-redis-session-abstract/composer.json
new file mode 100644
index 0000000..2f53b3d
--- /dev/null
+++ b/system/vendor/colinmollenhour/php-redis-session-abstract/composer.json
@@ -0,0 +1,24 @@
+{
+ "name":"colinmollenhour/php-redis-session-abstract",
+ "type":"library",
+ "license":"BSD-3-Clause",
+ "homepage":"https://github.com/colinmollenhour/php-redis-session-abstract",
+ "description":"A Redis-based session handler with optimistic locking",
+ "authors":[
+ {
+ "name":"Colin Mollenhour"
+ }
+ ],
+ "require":{
+ "php": "^5.5 || ^7.0 || ^8.0",
+ "colinmollenhour/credis":"~1.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9"
+ },
+ "autoload": {
+ "psr-0": {
+ "Cm\\RedisSession\\": "src/"
+ }
+ }
+}
diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/phpunit.xml.dist b/system/vendor/colinmollenhour/php-redis-session-abstract/phpunit.xml.dist
new file mode 100644
index 0000000..8f7c20e
--- /dev/null
+++ b/system/vendor/colinmollenhour/php-redis-session-abstract/phpunit.xml.dist
@@ -0,0 +1,42 @@
+
+
+
+
+
+ tests
+
+
+
diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/ConcurrentConnectionsExceededException.php b/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/ConcurrentConnectionsExceededException.php
new file mode 100644
index 0000000..ae75f7b
--- /dev/null
+++ b/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/ConcurrentConnectionsExceededException.php
@@ -0,0 +1,35 @@
+config = $config;
+ $this->logger = $logger;
+
+ $this->logger->setLogLevel($this->config->getLogLevel() ?: self::DEFAULT_LOG_LEVEL);
+ $timeStart = microtime(true);
+
+ // Database config
+ $host = $this->config->getHost() ?: self::DEFAULT_HOST;
+ $port = $this->config->getPort() ?: self::DEFAULT_PORT;
+ $pass = $this->config->getPassword() ?: null;
+ $timeout = $this->config->getTimeout() ?: self::DEFAULT_TIMEOUT;
+ $persistent = $this->config->getPersistentIdentifier() ?: '';
+ $this->_dbNum = $this->config->getDatabase() ?: self::DEFAULT_DATABASE;
+
+ // General config
+ $this->_readOnly = $readOnly;
+ $this->_compressionThreshold = $this->config->getCompressionThreshold() ?: self::DEFAULT_COMPRESSION_THRESHOLD;
+ $this->_compressionLibrary = $this->config->getCompressionLibrary() ?: self::DEFAULT_COMPRESSION_LIBRARY;
+ $this->_maxConcurrency = $this->config->getMaxConcurrency() ?: self::DEFAULT_MAX_CONCURRENCY;
+ $this->_failAfter = $this->config->getFailAfter() ?: self::DEFAULT_FAIL_AFTER;
+ $this->_maxLifetime = $this->config->getMaxLifetime() ?: self::DEFAULT_MAX_LIFETIME;
+ $this->_minLifetime = $this->config->getMinLifetime() ?: self::DEFAULT_MIN_LIFETIME;
+ $this->_useLocking = ! $this->config->getDisableLocking();
+
+ // Use sleep time multiplier so fail after time is in seconds
+ $this->_failAfter = (int) round((1000000 / self::SLEEP_TIME) * $this->_failAfter);
+
+ // Sentinel config
+ $sentinelServers = $this->config->getSentinelServers();
+ $sentinelMaster = $this->config->getSentinelMaster();
+ $sentinelVerifyMaster = $this->config->getSentinelVerifyMaster();
+ $sentinelConnectRetries = $this->config->getSentinelConnectRetries();
+
+ // Connect and authenticate
+ if ($sentinelServers && $sentinelMaster) {
+ $servers = preg_split('/\s*,\s*/', trim($sentinelServers), NULL, PREG_SPLIT_NO_EMPTY);
+ $sentinel = NULL;
+ $exception = NULL;
+ for ($i = 0; $i <= $sentinelConnectRetries; $i++) // Try to connect to sentinels in round-robin fashion
+ foreach ($servers as $server) {
+ try {
+ $sentinelClient = new \Credis_Client($server, NULL, $timeout, $persistent);
+ $sentinelClient->forceStandalone();
+ $sentinelClient->setMaxConnectRetries(0);
+ if ($pass) {
+ try {
+ $sentinelClient->auth($pass);
+ } catch (\CredisException $e) {
+ // Prevent throwing exception if Sentinel has no password set
+ if($e->getCode() !== 0 || strpos($e->getMessage(),'ERR Client sent AUTH, but no password is set') === false) {
+ throw $e;
+ }
+ }
+ }
+
+ $sentinel = new \Credis_Sentinel($sentinelClient);
+ $sentinel
+ ->setClientTimeout($timeout)
+ ->setClientPersistent($persistent);
+ $redisMaster = $sentinel->getMasterClient($sentinelMaster);
+ if ($pass) $redisMaster->auth($pass);
+
+ // Verify connected server is actually master as per Sentinel client spec
+ if ($sentinelVerifyMaster) {
+ $roleData = $redisMaster->role();
+ if ( ! $roleData || $roleData[0] != 'master') {
+ usleep(100000); // Sleep 100ms and try again
+ $redisMaster = $sentinel->getMasterClient($sentinelMaster);
+ if ($pass) $redisMaster->auth($pass);
+ $roleData = $redisMaster->role();
+ if ( ! $roleData || $roleData[0] != 'master') {
+ throw new Exception('Unable to determine master redis server.');
+ }
+ }
+ }
+ if ($this->_dbNum || $persistent) $redisMaster->select(0);
+
+ $this->_redis = $redisMaster;
+ break 2;
+ } catch (Exception $e) {
+ unset($sentinelClient);
+ $exception = $e;
+ }
+ }
+ unset($sentinel);
+
+ if ( ! $this->_redis) {
+ throw new ConnectionFailedException('Unable to connect to a Redis: '.$exception->getMessage(), $exception);
+ }
+ }
+ else {
+ $this->_redis = new \Credis_Client($host, $port, $timeout, $persistent, 0, $pass);
+ if ($this->hasConnection() == false) {
+ throw new ConnectionFailedException('Unable to connect to Redis');
+ }
+ }
+
+ // Destructor order cannot be predicted
+ $this->_redis->setCloseOnDestruct(false);
+ $this->_log(
+ sprintf(
+ "%s initialized for connection to %s:%s after %.5f seconds",
+ get_class($this),
+ $this->_redis->getHost(),
+ $this->_redis->getPort(),
+ (microtime(true) - $timeStart)
+ )
+ );
+ }
+
+ /**
+ * Open session
+ *
+ * @param string $savePath ignored
+ * @param string $sessionName ignored
+ * @return bool
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ #[\ReturnTypeWillChange]
+ public function open($savePath, $sessionName)
+ {
+ return true;
+ }
+
+ /**
+ * @param $msg
+ * @param $level
+ */
+ protected function _log($msg, $level = LoggerInterface::DEBUG)
+ {
+ $this->logger->log("{$this->_getPid()}: $msg", $level);
+ }
+
+ /**
+ * Check Redis connection
+ *
+ * @return bool
+ */
+ protected function hasConnection()
+ {
+ try {
+ $this->_redis->connect();
+ $this->_log("Connected to Redis");
+ return true;
+ } catch (\Exception $e) {
+ $this->logger->logException($e);
+ $this->_log('Unable to connect to Redis');
+ return false;
+ }
+ }
+
+ /**
+ * Fetch session data
+ *
+ * @param string $sessionId
+ * @return string
+ * @throws ConcurrentConnectionsExceededException
+ */
+ #[\ReturnTypeWillChange]
+ public function read($sessionId)
+ {
+ // Get lock on session. Increment the "lock" field and if the new value is 1, we have the lock.
+ $sessionId = self::SESSION_PREFIX.$sessionId;
+ $tries = $waiting = $lock = 0;
+ $lockPid = $oldLockPid = null; // Restart waiting for lock when current lock holder changes
+ $detectZombies = false;
+ $breakAfter = $this->_getBreakAfter();
+ $timeStart = microtime(true);
+ $this->_log(sprintf("Attempting to take lock on ID %s", $sessionId));
+
+ $this->_redis->select($this->_dbNum);
+ while ($this->_useLocking && !$this->_readOnly)
+ {
+ // Increment lock value for this session and retrieve the new value
+ $oldLock = $lock;
+ $lock = $this->_redis->hIncrBy($sessionId, 'lock', 1);
+
+ // Get the pid of the process that has the lock
+ if ($lock != 1 && $tries + 1 >= $breakAfter) {
+ $lockPid = $this->_redis->hGet($sessionId, 'pid');
+ }
+
+ // If we got the lock, update with our pid and reset lock and expiration
+ if ( $lock == 1 // We actually do have the lock
+ || (
+ $tries >= $breakAfter // We are done waiting and want to start trying to break it
+ && $oldLockPid == $lockPid // Nobody else got the lock while we were waiting
+ )
+ ) {
+ $this->_hasLock = true;
+ break;
+ }
+
+ // Otherwise, add to "wait" counter and continue
+ else if ( ! $waiting) {
+ $i = 0;
+ do {
+ $waiting = $this->_redis->hIncrBy($sessionId, 'wait', 1);
+ } while (++$i < $this->_maxConcurrency && $waiting < 1);
+ }
+
+ // Handle overloaded sessions
+ else {
+ // Detect broken sessions (e.g. caused by fatal errors)
+ if ($detectZombies) {
+ $detectZombies = false;
+ // Lock shouldn't be less than old lock (another process broke the lock)
+ if ($lock > $oldLock
+ // Lock should be old+waiting, otherwise there must be a dead process
+ && $lock + 1 < $oldLock + $waiting
+ ) {
+ // Reset session to fresh state
+ $this->_log(
+ sprintf(
+ "Detected zombie waiter after %.5f seconds for ID %s (%d waiting)",
+ (microtime(true) - $timeStart),
+ $sessionId, $waiting
+ ),
+ LoggerInterface::INFO
+ );
+ $waiting = $this->_redis->hIncrBy($sessionId, 'wait', -1);
+ continue;
+ }
+ }
+
+ // Limit concurrent lock waiters to prevent server resource hogging
+ if ($waiting >= $this->_maxConcurrency) {
+ // Overloaded sessions get 503 errors
+ $this->_redis->hIncrBy($sessionId, 'wait', -1);
+ $this->_sessionWritten = true; // Prevent session from getting written
+ $writes = $this->_redis->hGet($sessionId, 'writes');
+ $this->_log(
+ sprintf(
+ 'Session concurrency exceeded for ID %s; displaying HTTP 503 (%s waiting, %s total '
+ . 'requests)',
+ $sessionId, $waiting, $writes
+ ),
+ LoggerInterface::WARNING
+ );
+ throw new ConcurrentConnectionsExceededException();
+ }
+ }
+
+ $tries++;
+ $oldLockPid = $lockPid;
+ $sleepTime = self::SLEEP_TIME;
+
+ // Detect dead lock waiters
+ if ($tries % self::DETECT_ZOMBIES == 1) {
+ $detectZombies = true;
+ $sleepTime += 10000; // sleep + 0.01 seconds
+ }
+ // Detect dead lock holder every 10 seconds (only works on same node as lock holder)
+ if ($tries % self::DETECT_ZOMBIES == 0) {
+ $this->_log(
+ sprintf(
+ "Checking for zombies after %.5f seconds of waiting...", (microtime(true) - $timeStart)
+ )
+ );
+
+ $pid = $this->_redis->hGet($sessionId, 'pid');
+ if ($pid && ! $this->_pidExists($pid)) {
+ // Allow a live process to get the lock
+ $this->_redis->hSet($sessionId, 'lock', 0);
+ $this->_log(
+ sprintf(
+ "Detected zombie process (%s) for %s (%s waiting)",
+ $pid, $sessionId, $waiting
+ ),
+ LoggerInterface::INFO
+ );
+ continue;
+ }
+ }
+ // Timeout
+ if ($tries >= $breakAfter + $this->_failAfter) {
+ $this->_hasLock = false;
+ $this->_log(
+ sprintf(
+ 'Giving up on read lock for ID %s after %.5f seconds (%d attempts)',
+ $sessionId,
+ (microtime(true) - $timeStart),
+ $tries
+ ),
+ LoggerInterface::NOTICE
+ );
+ break;
+ }
+ else {
+ $this->_log(
+ sprintf(
+ "Waiting %.2f seconds for lock on ID %s (%d tries, lock pid is %s, %.5f seconds elapsed)",
+ $sleepTime / 1000000,
+ $sessionId,
+ $tries,
+ $lockPid,
+ (microtime(true) - $timeStart)
+ )
+ );
+ usleep($sleepTime);
+ }
+ }
+ $this->failedLockAttempts = $tries;
+
+ // Session can be read even if it was not locked by this pid!
+ $timeStart2 = microtime(true);
+ list($sessionData, $sessionWrites) = array_values($this->_redis->hMGet($sessionId, array('data','writes')));
+ $this->_log(sprintf("Data read for ID %s in %.5f seconds", $sessionId, (microtime(true) - $timeStart2)));
+ $this->_sessionWrites = (int) $sessionWrites;
+
+ // This process is no longer waiting for a lock
+ if ($tries > 0) {
+ $this->_redis->hIncrBy($sessionId, 'wait', -1);
+ }
+
+ // This process has the lock, save the pid
+ if ($this->_hasLock) {
+ $setData = array(
+ 'pid' => $this->_getPid(),
+ 'lock' => 1,
+ );
+
+ // Save request data in session so if a lock is broken we can know which page it was for debugging
+ if (empty($_SERVER['REQUEST_METHOD'])) {
+ $setData['req'] = @$_SERVER['SCRIPT_NAME'];
+ } else {
+ $setData['req'] = $_SERVER['REQUEST_METHOD']." ".@$_SERVER['SERVER_NAME'].@$_SERVER['REQUEST_URI'];
+ }
+ if ($lock != 1) {
+ $this->_log(
+ sprintf(
+ "Successfully broke lock for ID %s after %.5f seconds (%d attempts). Lock: %d\nLast request of "
+ . "broken lock: %s",
+ $sessionId,
+ (microtime(true) - $timeStart),
+ $tries,
+ $lock,
+ $this->_redis->hGet($sessionId, 'req')
+ ),
+ LoggerInterface::INFO
+ );
+ }
+ }
+
+ // Set session data and expiration
+ $this->_redis->pipeline();
+ if ( ! empty($setData)) {
+ $this->_redis->hMSet($sessionId, $setData);
+ }
+ $this->_redis->expire($sessionId, 3600*6); // Expiration will be set to correct value when session is written
+ $this->_redis->exec();
+
+ // Reset flag in case of multiple session read/write operations
+ $this->_sessionWritten = false;
+
+ return $sessionData ? (string) $this->_decodeData($sessionData) : '';
+ }
+
+ /**
+ * Update session
+ *
+ * @param string $sessionId
+ * @param string $sessionData
+ * @return boolean
+ */
+ #[\ReturnTypeWillChange]
+ public function write($sessionId, $sessionData)
+ {
+ if ($this->_sessionWritten || $this->_readOnly) {
+ $this->_log(sprintf(($this->_sessionWritten ? "Repeated" : "Read-only") . " session write detected; skipping for ID %s", $sessionId));
+ return true;
+ }
+ $this->_sessionWritten = true;
+ $timeStart = microtime(true);
+
+ // Do not overwrite the session if it is locked by another pid
+ try {
+ if($this->_dbNum) $this->_redis->select($this->_dbNum); // Prevent conflicts with other connections?
+
+ if ( ! $this->_useLocking
+ || ( ! ($pid = $this->_redis->hGet('sess_'.$sessionId, 'pid')) || $pid == $this->_getPid())
+ ) {
+ $this->_writeRawSession($sessionId, $sessionData, $this->getLifeTime());
+ $this->_log(sprintf("Data written to ID %s in %.5f seconds", $sessionId, (microtime(true) - $timeStart)));
+
+ }
+ else {
+ if ($this->_hasLock) {
+ $this->_log(sprintf("Did not write session for ID %s: another process took the lock.",
+ $sessionId
+ ), LoggerInterface::WARNING);
+ } else {
+ $this->_log(sprintf("Did not write session for ID %s: unable to acquire lock.",
+ $sessionId
+ ), LoggerInterface::WARNING);
+ }
+ }
+ }
+ catch(\Exception $e) {
+ $this->logger->logException($e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Destroy session
+ *
+ * @param string $sessionId
+ * @return boolean
+ */
+ #[\ReturnTypeWillChange]
+ public function destroy($sessionId)
+ {
+ $this->_log(sprintf("Destroying ID %s", $sessionId));
+ $this->_redis->pipeline();
+ if($this->_dbNum) $this->_redis->select($this->_dbNum);
+ $this->_redis->del(self::SESSION_PREFIX.$sessionId);
+ $this->_redis->exec();
+ return true;
+ }
+
+ /**
+ * Overridden to prevent calling getLifeTime at shutdown
+ *
+ * @return bool
+ */
+ #[\ReturnTypeWillChange]
+ public function close()
+ {
+ $this->_log("Closing connection");
+ if ($this->_redis) $this->_redis->close();
+ return true;
+ }
+
+ /**
+ * Garbage collection
+ *
+ * @param int $maxLifeTime ignored
+ * @return boolean
+ */
+ #[\ReturnTypeWillChange]
+ public function gc($maxLifeTime)
+ {
+ return true;
+ }
+
+ /**
+ * Get the number of failed lock attempts
+ *
+ * @return int
+ */
+ public function getFailedLockAttempts()
+ {
+ return $this->failedLockAttempts;
+ }
+
+ static public function isBotAgent($userAgent)
+ {
+ $isBot = !$userAgent || preg_match(self::BOT_REGEX, $userAgent);
+
+ if (is_array(self::$_botCheckCallback) && isset(self::$_botCheckCallback[0]) && self::$_botCheckCallback[1] && method_exists(self::$_botCheckCallback[0], self::$_botCheckCallback[1])) {
+ $isBot = (bool) call_user_func_array(self::$_botCheckCallback, [$userAgent, $isBot]);
+ }
+
+ return $isBot;
+ }
+
+ /**
+ * Get lock lifetime
+ *
+ * @return int|mixed
+ */
+ protected function getLifeTime()
+ {
+ if (is_null($this->_lifeTime)) {
+ $lifeTime = null;
+
+ // Detect bots by user agent
+ $botLifetime = is_null($this->config->getBotLifetime()) ? self::DEFAULT_BOT_LIFETIME : $this->config->getBotLifetime();
+ if ($botLifetime) {
+ $userAgent = empty($_SERVER['HTTP_USER_AGENT']) ? false : $_SERVER['HTTP_USER_AGENT'];
+ if (self::isBotAgent($userAgent)) {
+ $this->_log(sprintf("Bot detected for user agent: %s", $userAgent));
+ $botFirstLifetime = is_null($this->config->getBotFirstLifetime()) ? self::DEFAULT_BOT_FIRST_LIFETIME : $this->config->getBotFirstLifetime();
+ if ($this->_sessionWrites <= 1 && $botFirstLifetime) {
+ $lifeTime = $botFirstLifetime * (1+$this->_sessionWrites);
+ } else {
+ $lifeTime = $botLifetime;
+ }
+ }
+ }
+
+ // Use different lifetime for first write
+ if ($lifeTime === null && $this->_sessionWrites <= 1) {
+ $firstLifetime = is_null($this->config->getFirstLifetime()) ? self::DEFAULT_FIRST_LIFETIME : $this->config->getFirstLifetime();
+ if ($firstLifetime) {
+ $lifeTime = $firstLifetime * (1+$this->_sessionWrites);
+ }
+ }
+
+ // Neither bot nor first write
+ if ($lifeTime === null) {
+ $lifeTime = $this->config->getLifetime();
+ }
+
+ $this->_lifeTime = $lifeTime;
+ if ($this->_lifeTime < $this->_minLifetime) {
+ $this->_lifeTime = $this->_minLifetime;
+ }
+ if ($this->_lifeTime > $this->_maxLifetime) {
+ $this->_lifeTime = $this->_maxLifetime;
+ }
+ }
+ return $this->_lifeTime;
+ }
+
+ /**
+ * Encode data
+ *
+ * @param string $data
+ * @return string
+ */
+ protected function _encodeData($data)
+ {
+ $originalDataSize = strlen($data);
+ if ($this->_compressionThreshold > 0 && $this->_compressionLibrary != 'none' && $originalDataSize >= $this->_compressionThreshold) {
+ $this->_log(sprintf("Compressing %s bytes with %s", $originalDataSize,$this->_compressionLibrary));
+ $timeStart = microtime(true);
+ $prefix = ':'.substr($this->_compressionLibrary,0,2).':';
+ switch($this->_compressionLibrary) {
+ case 'snappy': $data = snappy_compress($data); break;
+ case 'lzf': $data = lzf_compress($data); break;
+ case 'lz4': $data = lz4_compress($data); $prefix = ':l4:'; break;
+ case 'gzip': $data = gzcompress($data, 1); break;
+ }
+ if($data) {
+ $data = $prefix.$data;
+ $this->_log(
+ sprintf(
+ "Data compressed by %.1f percent in %.5f seconds",
+ ($originalDataSize == 0 ? 0 : (100 - (strlen($data) / $originalDataSize * 100))),
+ (microtime(true) - $timeStart)
+ )
+ );
+ } else {
+ $this->_log(
+ sprintf("Could not compress session data using %s", $this->_compressionLibrary),
+ LoggerInterface::WARNING
+ );
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Decode data
+ *
+ * @param string $data
+ * @return string
+ */
+ protected function _decodeData($data)
+ {
+ switch (substr($data,0,4)) {
+ // asking the data which library it uses allows for transparent changes of libraries
+ case ':sn:': $data = snappy_uncompress(substr($data,4)); break;
+ case ':lz:': $data = lzf_decompress(substr($data,4)); break;
+ case ':l4:': $data = lz4_uncompress(substr($data,4)); break;
+ case ':gz:': $data = gzuncompress(substr($data,4)); break;
+ }
+ return $data;
+ }
+
+ /**
+ * Write session data to Redis
+ *
+ * @param $id
+ * @param $data
+ * @param $lifetime
+ * @throws \Exception
+ */
+ protected function _writeRawSession($id, $data, $lifetime)
+ {
+ $sessionId = 'sess_' . $id;
+ $this->_redis->pipeline()
+ ->select($this->_dbNum)
+ ->hMSet($sessionId, array(
+ 'data' => $this->_encodeData($data),
+ 'lock' => 0, // 0 so that next lock attempt will get 1
+ ))
+ ->hIncrBy($sessionId, 'writes', 1)
+ ->expire($sessionId, min((int)$lifetime, (int)$this->_maxLifetime))
+ ->exec();
+ }
+
+ /**
+ * Get pid
+ *
+ * @return string
+ */
+ protected function _getPid()
+ {
+ return gethostname().'|'.getmypid();
+ }
+
+ /**
+ * Check if pid exists
+ *
+ * @param $pid
+ * @return bool
+ */
+ protected function _pidExists($pid)
+ {
+ list($host,$pid) = explode('|', $pid);
+ if (PHP_OS != 'Linux' || $host != gethostname()) {
+ return true;
+ }
+ return @file_exists('/proc/'.$pid);
+ }
+
+ /**
+ * Get break time, calculated later than other config settings due to requiring session name to be set
+ *
+ * @return int
+ */
+ protected function _getBreakAfter()
+ {
+ // Has break after already been calculated? Only fetch from config once, then reuse variable.
+ if (!$this->_breakAfter) {
+ // Fetch relevant setting from config using session name
+ $this->_breakAfter = (float)($this->config->getBreakAfter() ?: self::DEFAULT_BREAK_AFTER);
+ // Use sleep time multiplier so break time is in seconds
+ $this->_breakAfter = (int)round((1000000 / self::SLEEP_TIME) * $this->_breakAfter);
+ }
+
+ return $this->_breakAfter;
+ }
+}
diff --git a/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/Handler/ConfigInterface.php b/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/Handler/ConfigInterface.php
new file mode 100644
index 0000000..a315c20
--- /dev/null
+++ b/system/vendor/colinmollenhour/php-redis-session-abstract/src/Cm/RedisSession/Handler/ConfigInterface.php
@@ -0,0 +1,195 @@
+config = $this->createMock(ConfigInterface::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->handler = new Handler($this->config, $this->logger);
+ }
+
+ /**
+ * Smoke test: open and close connection
+ */
+ public function testOpenClose()
+ {
+ $this->assertTrue($this->handler->open('', ''));
+
+ $this->logger->expects($this->once())
+ ->method('log')
+ ->with($this->stringContains('Closing connection'), LoggerInterface::DEBUG);
+
+ $this->assertTrue($this->handler->close());
+ }
+
+ /**
+ * Test basic handler operations
+ */
+ public function testHandler()
+ {
+ $sessionId = 1;
+ $data = 'data';
+ $this->handler->destroy($sessionId);
+ $this->assertTrue($this->handler->write($sessionId, $data));
+ $this->assertEquals(0, $this->handler->getFailedLockAttempts());
+ $this->assertEquals($data, $this->handler->read($sessionId));
+ $this->handler->destroy($sessionId);
+ $this->assertEquals('', $this->handler->read($sessionId));
+ $this->assertTrue($this->handler->close());
+ }
+}
diff --git a/system/vendor/composer/autoload_classmap.php b/system/vendor/composer/autoload_classmap.php
index bcda3d9..4b9953c 100644
--- a/system/vendor/composer/autoload_classmap.php
+++ b/system/vendor/composer/autoload_classmap.php
@@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname(dirname($vendorDir));
return array(
+ 'Cm_Cache_Backend_Redis' => $vendorDir . '/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'CredisException' => $vendorDir . '/colinmollenhour/credis/Client.php',
'Credis_Client' => $vendorDir . '/colinmollenhour/credis/Client.php',
diff --git a/system/vendor/composer/autoload_namespaces.php b/system/vendor/composer/autoload_namespaces.php
index 74176c7..d2428f8 100644
--- a/system/vendor/composer/autoload_namespaces.php
+++ b/system/vendor/composer/autoload_namespaces.php
@@ -7,4 +7,5 @@ $baseDir = dirname(dirname($vendorDir));
return array(
'Mustache' => array($vendorDir . '/mustache/mustache/src'),
+ 'Cm\\RedisSession\\' => array($vendorDir . '/colinmollenhour/php-redis-session-abstract/src'),
);
diff --git a/system/vendor/composer/autoload_static.php b/system/vendor/composer/autoload_static.php
index 802bea0..188b79d 100644
--- a/system/vendor/composer/autoload_static.php
+++ b/system/vendor/composer/autoload_static.php
@@ -64,9 +64,17 @@ class ComposerStaticInit7bc5673f1c1ff265570d44e4bc291db0
0 => __DIR__ . '/..' . '/mustache/mustache/src',
),
),
+ 'C' =>
+ array (
+ 'Cm\\RedisSession\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/colinmollenhour/php-redis-session-abstract/src',
+ ),
+ ),
);
public static $classMap = array (
+ 'Cm_Cache_Backend_Redis' => __DIR__ . '/..' . '/colinmollenhour/cache-backend-redis/Cm/Cache/Backend/Redis.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'CredisException' => __DIR__ . '/..' . '/colinmollenhour/credis/Client.php',
'Credis_Client' => __DIR__ . '/..' . '/colinmollenhour/credis/Client.php',
diff --git a/system/vendor/composer/installed.json b/system/vendor/composer/installed.json
index 697ffc0..7ad679f 100644
--- a/system/vendor/composer/installed.json
+++ b/system/vendor/composer/installed.json
@@ -1,18 +1,66 @@
{
"packages": [
+ {
+ "name": "colinmollenhour/cache-backend-redis",
+ "version": "1.17.1",
+ "version_normalized": "1.17.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis.git",
+ "reference": "d403f4473e1b3cc616fa59d187e817543b6620c1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/colinmollenhour/Cm_Cache_Backend_Redis/zipball/d403f4473e1b3cc616fa59d187e817543b6620c1",
+ "reference": "d403f4473e1b3cc616fa59d187e817543b6620c1",
+ "shasum": ""
+ },
+ "require": {
+ "colinmollenhour/credis": "^1.14"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.4",
+ "phpunit/phpunit": "^9",
+ "zf1s/zend-cache": "~1.15"
+ },
+ "time": "2023-12-21T21:56:18+00:00",
+ "type": "magento-module",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "Cm/Cache/Backend/Redis.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause-Modification"
+ ],
+ "authors": [
+ {
+ "name": "Colin Mollenhour"
+ }
+ ],
+ "description": "Zend_Cache backend using Redis with full support for tags.",
+ "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis",
+ "support": {
+ "issues": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis/issues",
+ "source": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis/tree/1.17.1"
+ },
+ "install-path": "../colinmollenhour/cache-backend-redis"
+ },
{
"name": "colinmollenhour/credis",
- "version": "v1.13.0",
- "version_normalized": "1.13.0.0",
+ "version": "v1.16.0",
+ "version_normalized": "1.16.0.0",
"source": {
"type": "git",
"url": "https://github.com/colinmollenhour/credis.git",
- "reference": "afec8e58ec93d2291c127fa19709a048f28641e5"
+ "reference": "5641140e14a9679f5a6f66c97268727f9558b881"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/afec8e58ec93d2291c127fa19709a048f28641e5",
- "reference": "afec8e58ec93d2291c127fa19709a048f28641e5",
+ "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/5641140e14a9679f5a6f66c97268727f9558b881",
+ "reference": "5641140e14a9679f5a6f66c97268727f9558b881",
"shasum": ""
},
"require": {
@@ -21,7 +69,7 @@
"suggest": {
"ext-redis": "Improved performance for communicating with redis"
},
- "time": "2022-04-07T14:57:22+00:00",
+ "time": "2023-10-26T17:02:51+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -46,10 +94,57 @@
"homepage": "https://github.com/colinmollenhour/credis",
"support": {
"issues": "https://github.com/colinmollenhour/credis/issues",
- "source": "https://github.com/colinmollenhour/credis/tree/v1.13.0"
+ "source": "https://github.com/colinmollenhour/credis/tree/v1.16.0"
},
"install-path": "../colinmollenhour/credis"
},
+ {
+ "name": "colinmollenhour/php-redis-session-abstract",
+ "version": "v1.4.5",
+ "version_normalized": "1.4.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/colinmollenhour/php-redis-session-abstract.git",
+ "reference": "77ad0c1637ae6ea059f1f8e9fbdac6469242a16d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/77ad0c1637ae6ea059f1f8e9fbdac6469242a16d",
+ "reference": "77ad0c1637ae6ea059f1f8e9fbdac6469242a16d",
+ "shasum": ""
+ },
+ "require": {
+ "colinmollenhour/credis": "~1.6",
+ "php": "^5.5 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9"
+ },
+ "time": "2021-12-01T21:16:01+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Cm\\RedisSession\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Colin Mollenhour"
+ }
+ ],
+ "description": "A Redis-based session handler with optimistic locking",
+ "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract",
+ "support": {
+ "issues": "https://github.com/colinmollenhour/php-redis-session-abstract/issues",
+ "source": "https://github.com/colinmollenhour/php-redis-session-abstract/tree/v1.4.5"
+ },
+ "install-path": "../colinmollenhour/php-redis-session-abstract"
+ },
{
"name": "mustache/mustache",
"version": "v2.14.1",
diff --git a/system/vendor/composer/installed.php b/system/vendor/composer/installed.php
index 62cfc5c..28b9a54 100644
--- a/system/vendor/composer/installed.php
+++ b/system/vendor/composer/installed.php
@@ -5,7 +5,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../../',
'aliases' => array(),
- 'reference' => '7d0624c01565cbad4144c8b6d2004348d01e86b1',
+ 'reference' => '1bad005e7c2659614810ae38a0ad11ca6d7c7bde',
'name' => '__root__',
'dev' => true,
),
@@ -16,16 +16,34 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../../',
'aliases' => array(),
- 'reference' => '7d0624c01565cbad4144c8b6d2004348d01e86b1',
+ 'reference' => '1bad005e7c2659614810ae38a0ad11ca6d7c7bde',
+ 'dev_requirement' => false,
+ ),
+ 'colinmollenhour/cache-backend-redis' => array(
+ 'pretty_version' => '1.17.1',
+ 'version' => '1.17.1.0',
+ 'type' => 'magento-module',
+ 'install_path' => __DIR__ . '/../colinmollenhour/cache-backend-redis',
+ 'aliases' => array(),
+ 'reference' => 'd403f4473e1b3cc616fa59d187e817543b6620c1',
'dev_requirement' => false,
),
'colinmollenhour/credis' => array(
- 'pretty_version' => 'v1.13.0',
- 'version' => '1.13.0.0',
+ 'pretty_version' => 'v1.16.0',
+ 'version' => '1.16.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../colinmollenhour/credis',
'aliases' => array(),
- 'reference' => 'afec8e58ec93d2291c127fa19709a048f28641e5',
+ 'reference' => '5641140e14a9679f5a6f66c97268727f9558b881',
+ 'dev_requirement' => false,
+ ),
+ 'colinmollenhour/php-redis-session-abstract' => array(
+ 'pretty_version' => 'v1.4.5',
+ 'version' => '1.4.5.0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../colinmollenhour/php-redis-session-abstract',
+ 'aliases' => array(),
+ 'reference' => '77ad0c1637ae6ea059f1f8e9fbdac6469242a16d',
'dev_requirement' => false,
),
'mustache/mustache' => array(
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Config/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Config/Config.php
new file mode 100644
index 0000000..eb322f9
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Config/Config.php
@@ -0,0 +1,25 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+declare(strict_types=1);
+namespace Phpfastcache\Config;
+
+/**
+ * Class Config
+ * Alias of ConfigurationOption
+ * @package phpFastCache\Config
+ * @see ConfigurationOption
+ */
+class Config extends ConfigurationOption
+{
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apc/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apc/Config.php
new file mode 100644
index 0000000..30b9e7c
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apc/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Apc;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apcu/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apcu/Config.php
new file mode 100644
index 0000000..b333aea
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Apcu/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Apcu;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cassandra/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cassandra/Config.php
new file mode 100644
index 0000000..82253ca
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cassandra/Config.php
@@ -0,0 +1,177 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Cassandra;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+ /**
+ * @var int
+ */
+ protected $port = 9042;
+ /**
+ * @var int
+ */
+ protected $timeout = 2;
+ /**
+ * @var string
+ */
+ protected $username = '';
+ /**
+ * @var string
+ */
+ protected $password = '';
+ /**
+ * @var bool
+ */
+ protected $sslEnabled = false;
+ /**
+ * @var bool
+ */
+ protected $sslVerify = false;
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return self
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return self
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getTimeout(): int
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * @param int $timeout
+ * @return self
+ */
+ public function setTimeout(int $timeout): self
+ {
+ $this->timeout = $timeout;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsername(): string
+ {
+ return $this->username;
+ }
+
+ /**
+ * @param string $username
+ * @return self
+ */
+ public function setUsername(string $username): self
+ {
+ $this->username = $username;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword(): string
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param string $password
+ * @return self
+ */
+ public function setPassword(string $password): self
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSslEnabled(): bool
+ {
+ return $this->sslEnabled;
+ }
+
+ /**
+ * @param bool $sslEnabled
+ * @return self
+ */
+ public function setSslEnabled(bool $sslEnabled): self
+ {
+ $this->sslEnabled = $sslEnabled;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSslVerify(): bool
+ {
+ return $this->sslVerify;
+ }
+
+ /**
+ * @param bool $sslVerify
+ * @return self
+ */
+ public function setSslVerify(bool $sslVerify): self
+ {
+ $this->sslVerify = $sslVerify;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cookie/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cookie/Config.php
new file mode 100644
index 0000000..de4099c
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Cookie/Config.php
@@ -0,0 +1,23 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Cookie;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchbase/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchbase/Config.php
new file mode 100644
index 0000000..d70cfd2
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchbase/Config.php
@@ -0,0 +1,137 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Couchbase;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+
+ /**
+ * @var int
+ */
+ protected $port = 8091;// SSL: 18091
+
+ /**
+ * @var string
+ */
+ protected $username = '';
+
+ /**
+ * @var string
+ */
+ protected $password = '';
+
+ /**
+ * @var string
+ */
+ protected $bucketName = 'default';
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return Config
+ */
+ public function setHost(string $host): Config
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return Config
+ */
+ public function setPort(int $port = null): Config
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsername(): string
+ {
+ return $this->username;
+ }
+
+ /**
+ * @param string $username
+ * @return Config
+ */
+ public function setUsername(string $username): Config
+ {
+ $this->username = $username;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword(): string
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param string $password
+ * @return Config
+ */
+ public function setPassword(string $password): Config
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBucketName(): string
+ {
+ return $this->bucketName;
+ }
+
+ /**
+ * @param string $bucketName
+ * @return Config
+ */
+ public function setBucketName(string $bucketName): Config
+ {
+ $this->bucketName = $bucketName;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchdb/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchdb/Config.php
new file mode 100644
index 0000000..07f2bfb
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Couchdb/Config.php
@@ -0,0 +1,184 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Couchdb;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+ /**
+ * @var int
+ */
+ protected $port = 5984;
+
+ /**
+ * @var string
+ */
+ protected $path = '/';
+
+ /**
+ * @var string
+ */
+ protected $username = '';
+ /**
+ * @var string
+ */
+ protected $password = '';
+ /**
+ * @var bool
+ */
+ protected $ssl = false;
+ /**
+ * @var int
+ */
+ protected $timeout = 10;
+
+ /**
+ * @var string
+ */
+ protected $database = Driver::COUCHDB_DEFAULT_DB_NAME;
+
+ /**
+ * @return string
+ */
+ public function getDatabase(): string
+ {
+ return $this->database;
+ }
+
+ /**
+ * @param string $database
+ * @return Config
+ */
+ public function setDatabase(string $database): Config
+ {
+ $this->database = $database;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return self
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return self
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsername(): string
+ {
+ return $this->username;
+ }
+
+ /**
+ * @param string $username
+ * @return self
+ */
+ public function setUsername(string $username): self
+ {
+ $this->username = $username;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword(): string
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param string $password
+ * @return self
+ */
+ public function setPassword(string $password): self
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSsl(): bool
+ {
+ return $this->ssl;
+ }
+
+ /**
+ * @param bool $ssl
+ * @return self
+ */
+ public function setSsl(bool $ssl): self
+ {
+ $this->ssl = $ssl;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getTimeout(): int
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * @param int $timeout
+ * @return self
+ */
+ public function setTimeout(int $timeout): self
+ {
+ $this->timeout = $timeout;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devfalse/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devfalse/Config.php
new file mode 100644
index 0000000..05b0e13
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devfalse/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Devfalse;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devnull/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devnull/Config.php
new file mode 100644
index 0000000..be5fab9
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devnull/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Devnull;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devtrue/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devtrue/Config.php
new file mode 100644
index 0000000..fc59c1f
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Devtrue/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Devtrue;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Files/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Files/Config.php
new file mode 100644
index 0000000..52fee20
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Files/Config.php
@@ -0,0 +1,26 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Files;
+
+use Phpfastcache\Config\{
+ ConfigurationOption, IOConfigurationOptionTrait
+};
+
+class Config extends ConfigurationOption
+{
+ use IOConfigurationOptionTrait;
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Leveldb/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Leveldb/Config.php
new file mode 100644
index 0000000..58d2074
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Leveldb/Config.php
@@ -0,0 +1,45 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Leveldb;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $htaccess = true;
+
+ /**
+ * @return string
+ */
+ public function getHtaccess(): string
+ {
+ return $this->htaccess;
+ }
+
+ /**
+ * @param string $htaccess
+ * @return self
+ */
+ public function setHtaccess(string $htaccess): self
+ {
+ $this->htaccess = $htaccess;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcache/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcache/Config.php
new file mode 100644
index 0000000..cfcad6a
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcache/Config.php
@@ -0,0 +1,163 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Memcache;
+
+use Phpfastcache\Config\ConfigurationOption;
+use Phpfastcache\Exceptions\PhpfastcacheInvalidConfigurationException;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var array
+ *
+ * Multiple server can be added this way:
+ * $cfg->setServers([
+ * [
+ * 'host' => '127.0.0.1',
+ * 'port' => 11211,
+ * 'saslUser' => false,
+ * 'saslPassword' => false,
+ * ]
+ * ]);
+ */
+ protected $servers = [];
+
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+
+ /**
+ * @var int
+ */
+ protected $port = 11211;
+
+ /**
+ * @var string
+ */
+ protected $saslUser = '';
+
+ /**
+ * @var string
+ */
+ protected $saslPassword = '';
+
+ /**
+ * @return bool
+ */
+ public function getSaslUser(): string
+ {
+ return $this->saslUser;
+ }
+
+ /**
+ * @param string $saslUser
+ * @return self
+ */
+ public function setSaslUser(string $saslUser): self
+ {
+ $this->saslUser = $saslUser;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSaslPassword(): string
+ {
+ return $this->saslPassword;
+ }
+
+ /**
+ * @param string $saslPassword
+ * @return self
+ */
+ public function setSaslPassword(string $saslPassword): self
+ {
+ $this->saslPassword = $saslPassword;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getServers(): array
+ {
+ return $this->servers;
+ }
+
+ /**
+ * @param array $servers
+ * @throws PhpfastcacheInvalidConfigurationException
+ * @return self
+ */
+ public function setServers(array $servers): self
+ {
+ foreach ($servers as $server) {
+ if($diff = \array_diff(['host', 'port', 'saslUser', 'saslPassword'], \array_keys($server))){
+ throw new PhpfastcacheInvalidConfigurationException('Missing keys for memcached server: '. \implode(', ', $diff));
+ }
+ if($diff = \array_diff( \array_keys($server), ['host', 'port', 'saslUser', 'saslPassword'])){
+ throw new PhpfastcacheInvalidConfigurationException('Unknown keys for memcached server: '. \implode(', ', $diff));
+ }
+ if(!\is_string($server['host'])){
+ throw new PhpfastcacheInvalidConfigurationException('Host must be a valid string in "$server" configuration array');
+ }
+ if(!\is_int($server['port'])){
+ throw new PhpfastcacheInvalidConfigurationException('Port must be a valid integer in "$server" configuration array');
+ }
+ }
+ $this->servers = $servers;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return self
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return self
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcached/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcached/Config.php
new file mode 100644
index 0000000..c1bee5f
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memcached/Config.php
@@ -0,0 +1,163 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Memcached;
+
+use Phpfastcache\Config\ConfigurationOption;
+use Phpfastcache\Exceptions\PhpfastcacheInvalidConfigurationException;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var array
+ *
+ * Multiple server can be added this way:
+ * $cfg->setServers([
+ * [
+ * 'host' => '127.0.0.1',
+ * 'port' => 11211,
+ * 'saslUser' => false,
+ * 'saslPassword' => false,
+ * ]
+ * ]);
+ */
+ protected $servers = [];
+
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+
+ /**
+ * @var int
+ */
+ protected $port = 11211;
+
+ /**
+ * @var string
+ */
+ protected $saslUser = '';
+
+ /**
+ * @var string
+ */
+ protected $saslPassword = '';
+
+ /**
+ * @return string
+ */
+ public function getSaslUser(): string
+ {
+ return $this->saslUser;
+ }
+
+ /**
+ * @param string $saslUser
+ * @return self
+ */
+ public function setSaslUser(string $saslUser): self
+ {
+ $this->saslUser = $saslUser;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSaslPassword(): string
+ {
+ return $this->saslPassword;
+ }
+
+ /**
+ * @param string $saslPassword
+ * @return self
+ */
+ public function setSaslPassword(string $saslPassword): self
+ {
+ $this->saslPassword = $saslPassword;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getServers(): array
+ {
+ return $this->servers;
+ }
+
+ /**
+ * @param array $servers
+ * @throws PhpfastcacheInvalidConfigurationException
+ * @return self
+ */
+ public function setServers(array $servers): self
+ {
+ foreach ($servers as $server) {
+ if($diff = \array_diff(['host', 'port', 'saslUser', 'saslPassword'], \array_keys($server))){
+ throw new PhpfastcacheInvalidConfigurationException('Missing keys for memcached server: '. \implode(', ', $diff));
+ }
+ if($diff = \array_diff( \array_keys($server), ['host', 'port', 'saslUser', 'saslPassword'])){
+ throw new PhpfastcacheInvalidConfigurationException('Unknown keys for memcached server: '. \implode(', ', $diff));
+ }
+ if(!\is_string($server['host'])){
+ throw new PhpfastcacheInvalidConfigurationException('Host must be a valid string in "$server" configuration array');
+ }
+ if(!\is_int($server['port'])){
+ throw new PhpfastcacheInvalidConfigurationException('Port must be a valid integer in "$server" configuration array');
+ }
+ }
+ $this->servers = $servers;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return self
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return Config
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memstatic/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memstatic/Config.php
new file mode 100644
index 0000000..72baab4
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Memstatic/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Memstatic;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Mongodb/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Mongodb/Config.php
new file mode 100644
index 0000000..11f86da
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Mongodb/Config.php
@@ -0,0 +1,276 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Mongodb;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+
+ /**
+ * @var int
+ */
+ protected $port = 27017;
+
+ /**
+ * @var int
+ */
+ protected $timeout = 3;
+
+ /**
+ * @var string
+ */
+ protected $username = '';
+
+ /**
+ * @var string
+ */
+ protected $password = '';
+
+ /**
+ * @var array
+ */
+ protected $servers = [];
+
+ /**
+ * @var string
+ */
+ protected $collectionName = 'Cache';
+
+ /**
+ * @var string
+ */
+ protected $databaseName = Driver::MONGODB_DEFAULT_DB_NAME;
+
+ /**
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * @var array
+ */
+ protected $driverOptions = [];
+
+ /**
+ * @var string
+ */
+ protected $protocol = 'mongodb';
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return self
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return self
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getTimeout(): int
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * @param int $timeout
+ * @return self
+ */
+ public function setTimeout(int $timeout): self
+ {
+ $this->timeout = $timeout;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsername(): string
+ {
+ return $this->username;
+ }
+
+ /**
+ * @param string $username
+ * @return self
+ */
+ public function setUsername(string $username): self
+ {
+ $this->username = $username;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword(): string
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param string $password
+ * @return self
+ */
+ public function setPassword(string $password): self
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getServers(): array
+ {
+ return $this->servers;
+ }
+
+ /**
+ * @param array $servers
+ * @return self
+ */
+ public function setServers(array $servers): self
+ {
+ $this->servers = $servers;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCollectionName(): string
+ {
+ return $this->collectionName;
+ }
+
+ /**
+ * @param string $collectionName
+ * @return self
+ */
+ public function setCollectionName(string $collectionName): self
+ {
+ $this->collectionName = $collectionName;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDatabaseName(): string
+ {
+ return $this->databaseName;
+ }
+
+ /**
+ * @param string $databaseName
+ * @return self
+ */
+ public function setDatabaseName(string $databaseName): self
+ {
+ $this->databaseName = $databaseName;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ /**
+ * @see https://docs.mongodb.com/manual/reference/connection-string/#connections-connection-options
+ * @param array $options
+ * @return Config
+ */
+ public function setOptions(array $options): self
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getDriverOptions(): array
+ {
+ return $this->driverOptions;
+ }
+
+ /**
+ * @param array $driverOptions
+ * @return self
+ */
+ public function setDriverOptions(array $driverOptions): self
+ {
+ $this->driverOptions = $driverOptions;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getProtocol(): string
+ {
+ return $this->protocol;
+ }
+
+ /**
+ * @param string $protocol
+ * @return self
+ */
+ public function setProtocol(string $protocol): self
+ {
+ $this->protocol = $protocol;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Predis/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Predis/Config.php
new file mode 100644
index 0000000..c595913
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Predis/Config.php
@@ -0,0 +1,250 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Predis;
+
+use Phpfastcache\Config\ConfigurationOption;
+use Phpfastcache\Exceptions\PhpfastcacheInvalidConfigurationException;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+
+ /**
+ * @var int
+ */
+ protected $port = 6379;
+
+ /**
+ * @var string
+ */
+ protected $password = '';
+
+ /**
+ * @var int
+ */
+ protected $database = 0;
+
+ /**
+ * @var \Predis\Client
+ */
+ protected $predisClient;
+
+ /**
+ * @var string
+ */
+ protected $optPrefix = '';
+
+ /**
+ * @var int
+ */
+ protected $timeout = 5;
+
+ /**
+ * @var bool
+ */
+ protected $persistent = false;
+
+ /**
+ * @var string
+ */
+ protected $scheme = 'unix';
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return Config
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return Config
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * @return null
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param null $password
+ * @return self
+ */
+ public function setPassword(string $password): self
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDatabase(): int
+ {
+ return $this->database;
+ }
+
+ /**
+ * @param int $database
+ * @return Config
+ */
+ public function setDatabase(int $database): self
+ {
+ $this->database = $database;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getPredisConfigArray(): array
+ {
+ return [
+ 'host' => $this->getHost(),
+ 'port' => $this->getPort(),
+ 'password' => $this->getPassword() ?: null,
+ 'database' => $this->getDatabase(),
+ 'timeout' => $this->getTimeout(),
+ ];
+ }
+
+ /**
+ * @return \Predis\Client|null
+ */
+ public function getPredisClient()
+ {
+ return $this->predisClient;
+ }
+
+ /**
+ * @param \Predis\Client $predisClient |null
+ * @return Config
+ */
+ public function setPredisClient(\Predis\Client $predisClient = null): Config
+ {
+ $this->predisClient = $predisClient;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @since 7.0.2
+ */
+ public function getOptPrefix(): string
+ {
+ return $this->optPrefix;
+ }
+
+ /**
+ * @param string $optPrefix
+ * @return Config
+ * @since 7.0.2
+ */
+ public function setOptPrefix(string $optPrefix): Config
+ {
+ $this->optPrefix = trim($optPrefix);
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getTimeout(): int
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * @param int $timeout
+ * @return self
+ */
+ public function setTimeout(int $timeout): self
+ {
+ $this->timeout = $timeout;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPersistent(): bool
+ {
+ return $this->persistent;
+ }
+
+ /**
+ * @param bool $persistent
+ * @return Config
+ */
+ public function setPersistent(bool $persistent): Config
+ {
+ $this->persistent = $persistent;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getScheme(): string
+ {
+ return $this->scheme;
+ }
+
+ /**
+ * @param string $scheme
+ * @return Config
+ * @throws PhpfastcacheInvalidConfigurationException
+ */
+ public function setScheme(string $scheme): Config
+ {
+ if(!\in_array($scheme, ['unix', 'tls'], true)){
+ throw new PhpfastcacheInvalidConfigurationException('Invalid scheme: ' . $scheme);
+ }
+ $this->scheme = $scheme;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Redis/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Redis/Config.php
new file mode 100644
index 0000000..2c94ceb
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Redis/Config.php
@@ -0,0 +1,186 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Redis;
+
+use Phpfastcache\Config\ConfigurationOption;
+use Redis as RedisClient;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+
+ /**
+ * @var int
+ */
+ protected $port = 6379;
+
+ /**
+ * @var string
+ */
+ protected $password = '';
+
+ /**
+ * @var int
+ */
+ protected $database = 0;
+
+ /**
+ * @var int
+ */
+ protected $timeout = 5;
+
+ /**
+ * @var RedisClient
+ */
+ protected $redisClient;
+
+ /**
+ * @var string
+ */
+ protected $optPrefix = '';
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return self
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return self
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param string $password
+ * @return self
+ */
+ public function setPassword(string $password): self
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDatabase(): int
+ {
+ return $this->database;
+ }
+
+ /**
+ * @param int $database
+ * @return self
+ */
+ public function setDatabase(int $database): self
+ {
+ $this->database = $database;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getTimeout(): int
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * @param int $timeout
+ * @return self
+ */
+ public function setTimeout(int $timeout): self
+ {
+ $this->timeout = $timeout;
+ return $this;
+ }
+
+ /**
+ * @return RedisClient|null
+ */
+ public function getRedisClient()
+ {
+ return $this->redisClient;
+ }
+
+ /**
+ * @param RedisClient $predisClient |null
+ * @return Config
+ */
+ public function setRedisClient(RedisClient $redisClient = null): Config
+ {
+ $this->redisClient = $redisClient;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @since 7.0.2
+ */
+ public function getOptPrefix(): string
+ {
+ return $this->optPrefix;
+ }
+
+ /**
+ * @param string $optPrefix
+ * @return Config
+ * @since 7.0.2
+ */
+ public function setOptPrefix(string $optPrefix): Config
+ {
+ $this->optPrefix = trim($optPrefix);
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Riak/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Riak/Config.php
new file mode 100644
index 0000000..8d4a037
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Riak/Config.php
@@ -0,0 +1,114 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Riak;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+
+ /**
+ * @var int
+ */
+ protected $port = 8098;
+
+ /**
+ * @var string
+ */
+ protected $prefix = 'riak';
+
+ /**
+ * @var string
+ */
+ protected $bucketName = Driver::RIAK_DEFAULT_BUCKET_NAME;
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return self
+ */
+ public function setHost(string $host): self
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return self
+ */
+ public function setPort(int $port): self
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPrefix(): string
+ {
+ return $this->prefix;
+ }
+
+ /**
+ * @param string $prefix
+ * @return self
+ */
+ public function setPrefix(string $prefix): self
+ {
+ $this->prefix = $prefix;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBucketName(): string
+ {
+ return $this->bucketName;
+ }
+
+ /**
+ * @param string $bucketName
+ * @return self
+ */
+ public function setBucketName(string $bucketName): self
+ {
+ $this->bucketName = $bucketName;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Sqlite/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Sqlite/Config.php
new file mode 100644
index 0000000..ce1dc8e
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Sqlite/Config.php
@@ -0,0 +1,26 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Sqlite;
+
+use Phpfastcache\Config\{
+ ConfigurationOption, IOConfigurationOptionTrait
+};
+
+class Config extends ConfigurationOption
+{
+ use IOConfigurationOptionTrait;
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Ssdb/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Ssdb/Config.php
new file mode 100644
index 0000000..f572eed
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Ssdb/Config.php
@@ -0,0 +1,114 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Ssdb;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+ /**
+ * @var string
+ */
+ protected $host = '127.0.0.1';
+
+ /**
+ * @var int
+ */
+ protected $port = 8888;
+
+ /**
+ * @var string
+ */
+ protected $password = '';
+
+ /**
+ * @var int
+ */
+ protected $timeout = 2000;
+
+ /**
+ * @return string
+ */
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ /**
+ * @param string $host
+ * @return Config
+ */
+ public function setHost(string $host): Config
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ /**
+ * @param int $port
+ * @return Config
+ */
+ public function setPort(int $port): Config
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword(): string
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param string $password
+ * @return Config
+ */
+ public function setPassword(string $password): Config
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getTimeout(): int
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * @param int $timeout
+ * @return Config
+ */
+ public function setTimeout(int $timeout): Config
+ {
+ $this->timeout = $timeout;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Wincache/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Wincache/Config.php
new file mode 100644
index 0000000..4d89fea
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Wincache/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Wincache;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Xcache/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Xcache/Config.php
new file mode 100644
index 0000000..1185f37
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Xcache/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Xcache;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zenddisk/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zenddisk/Config.php
new file mode 100644
index 0000000..36c9057
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zenddisk/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Zenddisk;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file
diff --git a/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zendshm/Config.php b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zendshm/Config.php
new file mode 100644
index 0000000..ff43929
--- /dev/null
+++ b/system/vendor/phpfastcache/phpfastcache/lib/Phpfastcache/Drivers/Zendshm/Config.php
@@ -0,0 +1,24 @@
+ https://www.phpfastcache.com
+ * @author Georges.L (Geolim4)
+ *
+ */
+
+declare(strict_types=1);
+
+namespace Phpfastcache\Drivers\Zendshm;
+
+use Phpfastcache\Config\ConfigurationOption;
+
+class Config extends ConfigurationOption
+{
+
+}
\ No newline at end of file