is_enabled = get_option( self::CACHE_STATE_OPTION ); $this->prefix = $this->generate_prefix(); $this->init_redis(); } /** * Initialize Redis connection * * @return void */ private function init_redis(): void { if ( ! class_exists( 'Predis\Client' ) ) { $this->redis_available = false; return; } try { $host = get_option( self::REDIS_HOST_OPTION, self::DEFAULT_REDIS_HOST ); $port = (int) get_option( self::REDIS_PORT_OPTION, self::DEFAULT_REDIS_PORT ); $password = get_option( self::REDIS_PASSWORD_OPTION, null ); $database = (int) get_option( self::REDIS_DATABASE_OPTION, self::DEFAULT_REDIS_DATABASE ); $parameters = array( 'host' => $host, 'port' => $port, 'database' => $database, ); if ( ! empty( $password ) ) { $parameters['password'] = $password; } $this->redis_client = new Client( $parameters ); // Test connection with ping (lazy connection in Predis 2.0) $this->redis_client->ping(); $this->redis_available = true; } catch ( ConnectionException $e ) { $this->redis_available = false; if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'Prodigy Cache: Redis connection failed - ' . $e->getMessage() ); } } catch ( \Exception $e ) { $this->redis_available = false; if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'Prodigy Cache: Redis initialization error - ' . $e->getMessage() ); } } } /** * Get Redis client instance * * @return Client|null */ private function get_redis(): ?Client { if ( ! $this->redis_available || null === $this->redis_client ) { return null; } try { $this->redis_client->ping(); return $this->redis_client; } catch ( \Exception $e ) { $this->redis_available = false; if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'Prodigy Cache: Redis ping failed - ' . $e->getMessage() ); } return null; } } /** * @return string */ private function generate_prefix(): string { $group = isset( $_COOKIE[ Prodigy_User::USER_GROUP_COOKIE_NAME ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ Prodigy_User::USER_GROUP_COOKIE_NAME ] ) ) : ''; $hash = Prodigy_User::is_logged_in() && $group ? $group : get_option( Prodigy_Admin_Setup_Wizard::OPTION_STORE_KEY ); $site_segment = is_multisite() ? '_' . get_current_blog_id() : ''; return sanitize_key( self::PLUGIN_TAG . $site_segment . '_' . $hash ); } /** * @param string $key * * @return string */ private function get_product_key( string $key ): string { return $this->prefix . self::PRODUCT_TAG . $key; } /** * @param string $id * * @return string */ private function get_order_key( string $id ): string { return $this->prefix . self::ORDER_TAG . $id; } /** * @param string $id * * @return string */ private function get_related_product_key( string $id ): string { return $this->prefix . self::RELATED_PRODUCT_TAG . md5( $id ); } /** * @param string $filter * * @return string */ private function get_catalog_filters_key( string $filter = '' ): string { return $this->get_catalog_filters_prefix() . md5( $filter ); } /** * @param string $filter * * @return string */ private function get_product_shortcode_key( string $filter = '' ): string { return $this->get_products_shortcode_prefix() . md5( $filter ); } /** * @param string $filter * * @return string */ private function get_categories_shortcode_key( string $filter = '' ): string { return $this->get_categories_shortcode_prefix() . md5( $filter ); } /** * * @return string */ private function get_brands_shortcode_key(): string { return $this->get_brands_shortcode_prefix() . md5( 'brands' ); } /** * @param int $id_remote_product * * @return string */ private function get_brands_list_key( int $id_remote_product ): string { return $this->get_brands_list_prefix() . $id_remote_product; } /** * @return string */ private function get_settings_key(): string { $current_domain = get_option( Prodigy_Admin_Setup_Wizard::OPTION_DOMAIN_HOSTED_SYSTEM ); return $this->prefix . self::SETTINGS_TAG . $current_domain; } /** * @param string $key * * @return string */ private function get_user_info_key( string $key ): string { return $this->prefix . self::USER_INFO_TAG . $key; } /** * @return string */ private function get_categories_shortcode_prefix(): string { return $this->prefix . self::CATEGORIES_SHORTCODE_TAG; } /** * @return string */ private function get_brands_shortcode_prefix(): string { return $this->prefix . self::BRANDS_SHORTCODE_TAG; } /** * @return string */ private function get_brands_list_prefix(): string { return $this->prefix . self::BRANDS_LIST_TAG; } /** * @return string */ private function get_products_shortcode_prefix(): string { return $this->prefix . self::PRODUCTS_SHORTCODE_TAG; } /** * @return string */ private function get_catalog_filters_prefix(): string { return $this->prefix . self::CATALOG_FILTERS_TAG; } /** * @param string $key * * @return mixed */ public function get_product( string $key ) { return $this->get_cache_value( $this->get_product_key( $key ) ); } /** * @param string $id * * @return mixed */ public function get_related_product( string $id ) { return $this->get_cache_value( $this->get_related_product_key( $id ) ); } /** * @param string $id * * @return mixed */ public function get_order( string $id ) { return $this->get_cache_value( $this->get_order_key( $id ) ); } /** * * @return int */ private function set_expiration_time(): int { if ( $this->is_enabled ) { $expiration_time = get_option( self::EXPIRATION_TIME_OPTION ); $custom_time = get_option( self::CUSTOM_EXPIRATION_TIME_OPTION ); return (int) ( $expiration_time === 'custom' ? $custom_time : $expiration_time ); } return 0; } /** * Get value from cache (Redis or fallback to transients) * * @param string $key Cache key. * @return mixed */ private function get_cache_value( string $key ) { if ( ! $this->is_enabled ) { return false; } $redis = $this->get_redis(); if ( $redis ) { try { $value = $redis->get( $key ); if ( null !== $value && false !== $value ) { $decoded = json_decode( $value, true ); if ( json_last_error() === JSON_ERROR_NONE ) { return $decoded; } // If JSON decode fails, try to return as string (for backward compatibility) return $value; } return false; } catch ( \Exception $e ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'Prodigy Cache: Redis get error - ' . $e->getMessage() ); } } } return get_transient( $key ); } /** * Set value in cache (Redis or fallback to transients) * * @param string $key Cache key. * @param mixed $data Data to cache. * @param int $time Expiration time in seconds. * @return bool */ private function set_cache_value( string $key, $data, int $time ): bool { if ( ! $this->is_enabled ) { return false; } $redis = $this->get_redis(); if ( $redis ) { try { $serialized = json_encode( $data, JSON_UNESCAPED_UNICODE ); if ( false === $serialized ) { // If JSON encoding fails, fallback to transients if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'Prodigy Cache: JSON encode error - ' . json_last_error_msg() ); } return set_transient( $key, $data, $time ); } if ( $time > 0 ) { $redis->setex( $key, $time, $serialized ); } else { $redis->set( $key, $serialized ); } return true; } catch ( \Exception $e ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'Prodigy Cache: Redis set error - ' . $e->getMessage() ); } } } // Fallback to WordPress transients return set_transient( $key, $data, $time ); } /** * Delete value from cache (Redis or fallback to transients) * * @param string $key Cache key. * @return bool */ private function delete_cache_value( string $key ): bool { $redis = $this->get_redis(); if ( $redis ) { try { $redis->del( $key ); return true; } catch ( \Exception $e ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'Prodigy Cache: Redis delete error - ' . $e->getMessage() ); } } } // Fallback to WordPress transients return delete_transient( $key ); } /** * @param string $key * @param mixed $data * @param int $time * * @return bool */ public function set_cache( string $key, $data, int $time ): bool { return $this->set_cache_value( $key, $data, $time ); } /** * @param string $key * @param array $product_data * * @return bool */ public function set_product( string $key, array $product_data ): bool { return $this->set_cache( $this->get_product_key( $key ), $product_data, $this->set_expiration_time() ); } /** * @param string $id * @param array $data * * @return bool */ public function set_related_product( string $id, array $data ): bool { return $this->set_cache( $this->get_related_product_key( $id ), $data, $this->set_expiration_time() ); } /** * @param string $id * @param array $data * * @return bool */ public function set_order( string $id, array $data ): bool { return $this->set_cache( $this->get_order_key( $id ), $data, $this->set_expiration_time() ); } /** * @param string $id Order token. * * @return bool */ public function delete_order( string $id ): bool { return $this->delete_cache_value( $this->get_order_key( $id ) ); } /** * @param array $data * @param string $filter * * @return bool */ public function set_catalog_filters( array $data, string $filter ): bool { return $this->set_cache( $this->get_catalog_filters_key( $filter ), $data, $this->set_expiration_time() ); } /** * @param string $filter * * @return mixed */ public function get_catalog_filters( string $filter ) { return $this->get_cache_value( $this->get_catalog_filters_key( $filter ) ); } /** * @param array $data * * @return bool */ public function set_settings( array $data ): bool { return $this->set_cache( $this->get_settings_key(), $data, $this->set_expiration_time() ); } /** * * @return mixed */ public function get_settings() { return $this->get_cache_value( $this->get_settings_key() ); } /** * @param string $key * * @return mixed */ public function get_user_info( string $key ) { return $this->get_cache_value( $this->get_user_info_key( $key ) ); } /** * @param array $data * @param string $key * * @return bool */ public function set_user_info( array $data, string $key ): bool { return $this->set_cache( $this->get_user_info_key( $key ), $data, $this->set_expiration_time() ); } /** * @param array $data * @param string $filter * * @return bool */ public function set_products_shortcode( array $data, string $filter ): bool { return $this->set_cache( $this->get_product_shortcode_key( $filter ), $data, $this->set_expiration_time() ); } /** * @param string $filter * * @return mixed */ public function get_products_shortcode( string $filter ) { return $this->get_cache_value( $this->get_product_shortcode_key( $filter ) ); } /** * @param array $data * @param string $filter * * @return bool */ public function set_categories_shortcode( array $data, string $filter ): bool { return $this->set_cache( $this->get_categories_shortcode_key( $filter ), $data, $this->set_expiration_time() ); } /** * @param array $data * * @return bool */ public function set_brands_shortcode( array $data ): bool { return $this->set_cache( $this->get_brands_shortcode_key(), $data, $this->set_expiration_time() ); } /** * @param string $filter * * @return mixed */ public function get_categories_shortcode( string $filter ) { return $this->get_cache_value( $this->get_categories_shortcode_key( $filter ) ); } /** * * @return mixed */ public function get_brands_shortcode() { return $this->get_cache_value( $this->get_brands_shortcode_key() ); } /** * @param array $data * @param int $id * * @return bool */ public function set_brands_list( array $data, int $id ): bool { return $this->set_cache( $this->get_brands_list_key( $id ), $data, $this->set_expiration_time() ); } /** * @param int $id_remote_product * * @return mixed */ public function get_brands_list( int $id_remote_product ) { return $this->get_cache_value( $this->get_brands_list_key( $id_remote_product ) ); } /** * @return bool */ public function reset_settings(): bool { return $this->delete_cache_value( $this->get_settings_key() ); } /** * * @param string $key * * @return void */ public function reset_product( string $key ) { $this->clear( $this->get_product_key( $key ) ); } /** * @param int $id * * @return bool */ public function reset_product_brands_list( int $id ): bool { return $this->delete_cache_value( $this->get_brands_list_key( $id ) ); } /** * @return void */ public function reset_catalog() { $this->clear( $this->get_catalog_filters_prefix() ); $this->clear( $this->get_products_shortcode_prefix() ); } /** * @param string $id * * @return void */ public function reset_related_products( string $id ) { $this->clear( $this->get_related_product_key( $id ) ); } /** * @param string $prefix * * @return void */ public function clear( string $prefix = '' ) { global $wpdb; $redis = $this->get_redis(); if ( $redis ) { try { if ( empty( $prefix ) ) { $site_segment = is_multisite() ? '_' . get_current_blog_id() : ''; $pattern = sanitize_key( self::PLUGIN_TAG . $site_segment . '_' ) . '*'; } else { $pattern = $prefix . '*'; } $keys = $redis->keys( $pattern ); if ( ! empty( $keys ) && is_array( $keys ) ) { $redis->del( $keys ); } } catch ( \Exception $e ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'Prodigy Cache: Redis clear error - ' . $e->getMessage() ); } } } if ( empty( $prefix ) ) { $site_segment = is_multisite() ? '_' . get_current_blog_id() : ''; $broad_prefix = esc_sql( sanitize_key( self::PLUGIN_TAG . $site_segment . '_' ) ); $t = "_transient_%{$broad_prefix}%"; } else { $t = "%_transient_$prefix" . '%'; } $transients = $wpdb->get_col( $wpdb->prepare( " SELECT option_name FROM {$wpdb->options} WHERE option_name like %s ", $t ) ); foreach ( $transients as $transient ) { $key = str_replace( '_transient_', '', $transient ); delete_transient( $key ); } wp_cache_flush(); } }