add($key, $data, $group, $expire); } function wp_cache_add_multiple(array $data, $group = 'default', $expire = 0) { global $wp_object_cache; $results = []; foreach ($data as $key => $value) { $results[$key] = $wp_object_cache->add($key, $value, $group, $expire); } return $results; } function wp_cache_replace($key, $data, $group = 'default', $expire = 0) { global $wp_object_cache; return $wp_object_cache->replace($key, $data, $group, $expire); } function wp_cache_set($key, $data, $group = 'default', $expire = 0) { global $wp_object_cache; return $wp_object_cache->set($key, $data, $group, $expire); } function wp_cache_set_multiple(array $data, $group = 'default', $expire = 0) { global $wp_object_cache; $results = []; foreach ($data as $key => $value) { $results[$key] = $wp_object_cache->set($key, $value, $group, $expire); } return $results; } function wp_cache_get($key, $group = 'default', $force = false, &$found = null) { global $wp_object_cache; return $wp_object_cache->get($key, $group, $force, $found); } function wp_cache_get_multiple($keys, $group = 'default', $force = false) { global $wp_object_cache; $results = []; foreach ($keys as $key) { $results[$key] = $wp_object_cache->get($key, $group, $force); } return $results; } function wp_cache_delete($key, $group = 'default') { global $wp_object_cache; return $wp_object_cache->delete($key, $group); } function wp_cache_delete_multiple(array $keys, $group = 'default') { global $wp_object_cache; $results = []; foreach ($keys as $key) { $results[$key] = $wp_object_cache->delete($key, $group); } return $results; } function wp_cache_incr($key, $offset = 1, $group = 'default') { global $wp_object_cache; return $wp_object_cache->incr($key, $offset, $group); } function wp_cache_decr($key, $offset = 1, $group = 'default') { global $wp_object_cache; return $wp_object_cache->decr($key, $offset, $group); } function wp_cache_flush() { global $wp_object_cache; return $wp_object_cache->flush(); } function wp_cache_flush_group($group) { global $wp_object_cache; return $wp_object_cache->flush_group($group); } function wp_cache_supports($feature) { switch ($feature) { case 'add_multiple': case 'set_multiple': case 'get_multiple': case 'delete_multiple': case 'flush_runtime': case 'flush_group': return true; default: return false; } } function wp_cache_close() { global $wp_object_cache; return $wp_object_cache->close(); } function wp_cache_add_global_groups($groups) { global $wp_object_cache; $wp_object_cache->add_global_groups($groups); } function wp_cache_add_non_persistent_groups($groups) { global $wp_object_cache; $wp_object_cache->add_non_persistent_groups($groups); } function wp_cache_switch_to_blog($blog_id) { global $wp_object_cache; $wp_object_cache->switch_to_blog($blog_id); } function wp_cache_flush_runtime() { global $wp_object_cache; return $wp_object_cache->flush_runtime(); } /** * Jabali Redis Object Cache Class */ class Jabali_Redis_Object_Cache { /** * Redis connection * @var Redis|null */ private $redis = null; /** * Whether Redis is connected * @var bool */ private $connected = false; /** * In-memory cache for current request * @var array */ private $cache = []; /** * Global cache groups * @var array */ private $global_groups = []; /** * Non-persistent groups (not stored in Redis) * @var array */ private $non_persistent_groups = []; /** * Current blog ID for multisite * @var int */ private $blog_prefix = ''; /** * Redis key prefix * @var string */ private $prefix = ''; /** * Cache hits counter * @var int */ public $cache_hits = 0; /** * Cache misses counter * @var int */ public $cache_misses = 0; /** * Constructor */ public function __construct() { $this->prefix = defined('JABALI_CACHE_PREFIX') ? JABALI_CACHE_PREFIX : 'jc_'; $this->blog_prefix = $this->get_blog_prefix(); $this->connect(); } /** * Connect to Redis */ private function connect() { try { $this->redis = new Redis(); $host = defined('JABALI_CACHE_HOST') ? JABALI_CACHE_HOST : '127.0.0.1'; $port = defined('JABALI_CACHE_PORT') ? JABALI_CACHE_PORT : 6379; $timeout = defined('JABALI_CACHE_TIMEOUT') ? JABALI_CACHE_TIMEOUT : 1; $database = defined('JABALI_CACHE_DATABASE') ? JABALI_CACHE_DATABASE : 0; $this->connected = $this->redis->connect($host, $port, $timeout); if ($this->connected) { // Authenticate with Redis ACL (username + password) or simple password if (defined('JABALI_CACHE_REDIS_USER') && defined('JABALI_CACHE_REDIS_PASS')) { $this->redis->auth(['user' => JABALI_CACHE_REDIS_USER, 'pass' => JABALI_CACHE_REDIS_PASS]); } elseif (defined('JABALI_CACHE_REDIS_PASS') && JABALI_CACHE_REDIS_PASS) { $this->redis->auth(JABALI_CACHE_REDIS_PASS); } elseif (defined('JABALI_CACHE_PASSWORD') && JABALI_CACHE_PASSWORD) { // Legacy support $this->redis->auth(JABALI_CACHE_PASSWORD); } // Select database if ($database) { $this->redis->select($database); } $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); } } catch (Exception $e) { $this->connected = false; $this->redis = null; } } /** * Get blog prefix for multisite */ private function get_blog_prefix() { global $blog_id; if (is_multisite()) { return (int) $blog_id . ':'; } return ''; } /** * Build cache key */ private function build_key($key, $group = 'default') { if (empty($group)) { $group = 'default'; } $prefix = in_array($group, $this->global_groups) ? '' : $this->blog_prefix; return $this->prefix . $prefix . $group . ':' . $key; } /** * Check if group is non-persistent */ private function is_non_persistent($group) { return in_array($group, $this->non_persistent_groups); } /** * Add data to cache if it doesn't exist */ public function add($key, $data, $group = 'default', $expire = 0) { if (wp_suspend_cache_addition()) { return false; } $cache_key = $this->build_key($key, $group); if (isset($this->cache[$cache_key])) { return false; } return $this->set($key, $data, $group, $expire); } /** * Replace data in cache if it exists */ public function replace($key, $data, $group = 'default', $expire = 0) { $cache_key = $this->build_key($key, $group); if (!isset($this->cache[$cache_key]) && !$this->redis_exists($cache_key)) { return false; } return $this->set($key, $data, $group, $expire); } /** * Check if key exists in Redis */ private function redis_exists($cache_key) { if (!$this->connected) { return false; } try { return $this->redis->exists($cache_key); } catch (Exception $e) { return false; } } /** * Set data in cache */ public function set($key, $data, $group = 'default', $expire = 0) { $cache_key = $this->build_key($key, $group); // Always store in local cache $this->cache[$cache_key] = $data; // Store in Redis if persistent if (!$this->is_non_persistent($group) && $this->connected) { try { $expire = (int) $expire; if ($expire > 0) { $this->redis->setex($cache_key, $expire, $data); } else { $this->redis->set($cache_key, $data); } } catch (Exception $e) { return false; } } return true; } /** * Get data from cache */ public function get($key, $group = 'default', $force = false, &$found = null) { $cache_key = $this->build_key($key, $group); // Check local cache first (unless forced) if (!$force && isset($this->cache[$cache_key])) { $found = true; $this->cache_hits++; return $this->cache[$cache_key]; } // For non-persistent groups, only check local cache if ($this->is_non_persistent($group)) { $found = false; $this->cache_misses++; return false; } // Check Redis if ($this->connected) { try { $data = $this->redis->get($cache_key); if ($data !== false) { $found = true; $this->cache_hits++; $this->cache[$cache_key] = $data; return $data; } } catch (Exception $e) { // Fall through to miss } } $found = false; $this->cache_misses++; return false; } /** * Delete data from cache */ public function delete($key, $group = 'default') { $cache_key = $this->build_key($key, $group); unset($this->cache[$cache_key]); if (!$this->is_non_persistent($group) && $this->connected) { try { $this->redis->del($cache_key); } catch (Exception $e) { return false; } } return true; } /** * Increment numeric value */ public function incr($key, $offset = 1, $group = 'default') { $cache_key = $this->build_key($key, $group); if (!isset($this->cache[$cache_key])) { $this->cache[$cache_key] = 0; } $this->cache[$cache_key] += $offset; if ($this->cache[$cache_key] < 0) { $this->cache[$cache_key] = 0; } if (!$this->is_non_persistent($group) && $this->connected) { try { $this->redis->incrBy($cache_key, $offset); } catch (Exception $e) { // Continue with local cache } } return $this->cache[$cache_key]; } /** * Decrement numeric value */ public function decr($key, $offset = 1, $group = 'default') { return $this->incr($key, -$offset, $group); } /** * Flush entire cache */ public function flush() { $this->cache = []; if ($this->connected) { try { // Only flush keys with our prefix $keys = $this->redis->keys($this->prefix . '*'); if (!empty($keys)) { $this->redis->del($keys); } } catch (Exception $e) { return false; } } return true; } /** * Flush a specific group */ public function flush_group($group) { $pattern = $this->build_key('*', $group); // Clear from local cache foreach (array_keys($this->cache) as $key) { if (strpos($key, $this->prefix . $this->blog_prefix . $group . ':') === 0) { unset($this->cache[$key]); } } if ($this->connected) { try { $keys = $this->redis->keys($pattern); if (!empty($keys)) { $this->redis->del($keys); } } catch (Exception $e) { return false; } } return true; } /** * Flush runtime (local) cache only */ public function flush_runtime() { $this->cache = []; return true; } /** * Close Redis connection */ public function close() { if ($this->connected && $this->redis) { try { $this->redis->close(); } catch (Exception $e) { // Ignore } } return true; } /** * Add global groups */ public function add_global_groups($groups) { $groups = (array) $groups; $this->global_groups = array_merge($this->global_groups, $groups); $this->global_groups = array_unique($this->global_groups); } /** * Add non-persistent groups */ public function add_non_persistent_groups($groups) { $groups = (array) $groups; $this->non_persistent_groups = array_merge($this->non_persistent_groups, $groups); $this->non_persistent_groups = array_unique($this->non_persistent_groups); } /** * Switch to blog (multisite) */ public function switch_to_blog($blog_id) { $this->blog_prefix = (int) $blog_id . ':'; } /** * Get stats */ public function stats() { $stats = [ 'hits' => $this->cache_hits, 'misses' => $this->cache_misses, 'ratio' => $this->cache_hits + $this->cache_misses > 0 ? round($this->cache_hits / ($this->cache_hits + $this->cache_misses) * 100, 2) : 0, 'connected' => $this->connected, 'prefix' => $this->prefix, ]; if ($this->connected) { try { $info = $this->redis->info(); $stats['redis_version'] = $info['redis_version'] ?? 'unknown'; $stats['used_memory'] = $info['used_memory_human'] ?? 'unknown'; $stats['connected_clients'] = $info['connected_clients'] ?? 0; $stats['total_keys'] = count($this->redis->keys($this->prefix . '*')); } catch (Exception $e) { // Ignore } } return $stats; } /** * Check if connected */ public function is_connected() { return $this->connected; } }