*/ public static function get_available_adapters(): array { return array( self::ADAPTER_INMEMORY => __( 'In-Memory (default, no persistence)', 'wp-prometheus' ), self::ADAPTER_REDIS => __( 'Redis (requires PHP Redis extension)', 'wp-prometheus' ), self::ADAPTER_APCU => __( 'APCu (requires APCu extension)', 'wp-prometheus' ), ); } /** * Check if a storage adapter is available on this system. * * @param string $adapter Adapter name. * @return bool */ public static function is_adapter_available( string $adapter ): bool { switch ( $adapter ) { case self::ADAPTER_INMEMORY: return true; case self::ADAPTER_REDIS: return extension_loaded( 'redis' ); case self::ADAPTER_APCU: return extension_loaded( 'apcu' ) && apcu_enabled(); default: return false; } } /** * Get the configured storage adapter name. * * @return string */ public static function get_configured_adapter(): string { // Check environment variable first. $env_adapter = getenv( self::ENV_STORAGE_ADAPTER ); if ( false !== $env_adapter && ! empty( $env_adapter ) ) { return strtolower( $env_adapter ); } // Fall back to WordPress option. return get_option( 'wp_prometheus_storage_adapter', self::ADAPTER_INMEMORY ); } /** * Get the active storage adapter name (may differ from configured if fallback occurred). * * @return string */ public static function get_active_adapter(): string { // Ensure adapter is created. self::get_adapter(); $configured = self::get_configured_adapter(); if ( self::is_adapter_available( $configured ) && empty( self::$last_error ) ) { return $configured; } return self::ADAPTER_INMEMORY; } /** * Create the storage adapter based on configuration. * * @return Adapter */ private static function create_adapter(): Adapter { $adapter = self::get_configured_adapter(); self::$last_error = ''; switch ( $adapter ) { case self::ADAPTER_REDIS: return self::create_redis_adapter(); case self::ADAPTER_APCU: return self::create_apcu_adapter(); case self::ADAPTER_INMEMORY: default: return new InMemory(); } } /** * Create Redis storage adapter. * * @return Adapter */ private static function create_redis_adapter(): Adapter { if ( ! extension_loaded( 'redis' ) ) { self::$last_error = __( 'PHP Redis extension is not installed.', 'wp-prometheus' ); return new InMemory(); } $config = self::get_redis_config(); try { Redis::setPrefix( $config['prefix'] ); $redis = new Redis( array( 'host' => $config['host'], 'port' => $config['port'], 'password' => $config['password'] ?: null, 'timeout' => 0.5, 'read_timeout' => 10, 'persistent_connections' => true, ) ); // Test connection by triggering initialization. // The Redis adapter connects lazily, so we need to check it works. return $redis; } catch ( StorageException $e ) { self::$last_error = sprintf( /* translators: %s: Error message */ __( 'Redis connection failed: %s', 'wp-prometheus' ), $e->getMessage() ); return new InMemory(); } catch ( \RedisException $e ) { self::$last_error = sprintf( /* translators: %s: Error message */ __( 'Redis error: %s', 'wp-prometheus' ), $e->getMessage() ); return new InMemory(); } catch ( \Exception $e ) { self::$last_error = sprintf( /* translators: %s: Error message */ __( 'Storage error: %s', 'wp-prometheus' ), $e->getMessage() ); return new InMemory(); } } /** * Create APCu storage adapter. * * @return Adapter */ private static function create_apcu_adapter(): Adapter { if ( ! extension_loaded( 'apcu' ) ) { self::$last_error = __( 'APCu extension is not installed.', 'wp-prometheus' ); return new InMemory(); } if ( ! apcu_enabled() ) { self::$last_error = __( 'APCu is installed but not enabled.', 'wp-prometheus' ); return new InMemory(); } $prefix = self::get_apcu_prefix(); try { return new APC( $prefix ); } catch ( StorageException $e ) { self::$last_error = sprintf( /* translators: %s: Error message */ __( 'APCu error: %s', 'wp-prometheus' ), $e->getMessage() ); return new InMemory(); } } /** * Get Redis configuration. * * @return array{host: string, port: int, password: string, database: int, prefix: string} */ public static function get_redis_config(): array { // Check environment variables first. $env_host = getenv( self::ENV_REDIS_HOST ); $env_port = getenv( self::ENV_REDIS_PORT ); $env_password = getenv( self::ENV_REDIS_PASSWORD ); $env_database = getenv( self::ENV_REDIS_DATABASE ); $env_prefix = getenv( self::ENV_REDIS_PREFIX ); // Get WordPress options as fallback. $options = get_option( 'wp_prometheus_redis_config', array() ); return array( 'host' => ( false !== $env_host && ! empty( $env_host ) ) ? $env_host : ( $options['host'] ?? '127.0.0.1' ), 'port' => ( false !== $env_port && ! empty( $env_port ) ) ? (int) $env_port : ( (int) ( $options['port'] ?? 6379 ) ), 'password' => ( false !== $env_password ) ? $env_password : ( $options['password'] ?? '' ), 'database' => ( false !== $env_database && ! empty( $env_database ) ) ? (int) $env_database : ( (int) ( $options['database'] ?? 0 ) ), 'prefix' => ( false !== $env_prefix && ! empty( $env_prefix ) ) ? $env_prefix : ( $options['prefix'] ?? self::DEFAULT_REDIS_PREFIX ), ); } /** * Get APCu prefix. * * @return string */ public static function get_apcu_prefix(): string { $env_prefix = getenv( self::ENV_APCU_PREFIX ); if ( false !== $env_prefix && ! empty( $env_prefix ) ) { return $env_prefix; } return get_option( 'wp_prometheus_apcu_prefix', self::DEFAULT_APCU_PREFIX ); } /** * Save storage configuration. * * @param array $config Configuration array. * @return void */ public static function save_config( array $config ): void { if ( isset( $config['adapter'] ) ) { update_option( 'wp_prometheus_storage_adapter', sanitize_key( $config['adapter'] ) ); } if ( isset( $config['redis'] ) && is_array( $config['redis'] ) ) { $redis_config = array( 'host' => sanitize_text_field( $config['redis']['host'] ?? '127.0.0.1' ), 'port' => absint( $config['redis']['port'] ?? 6379 ), 'password' => sanitize_text_field( $config['redis']['password'] ?? '' ), 'database' => absint( $config['redis']['database'] ?? 0 ), 'prefix' => sanitize_key( $config['redis']['prefix'] ?? self::DEFAULT_REDIS_PREFIX ), ); update_option( 'wp_prometheus_redis_config', $redis_config ); } if ( isset( $config['apcu_prefix'] ) ) { update_option( 'wp_prometheus_apcu_prefix', sanitize_key( $config['apcu_prefix'] ) ); } // Reset the singleton to apply new configuration. self::reset(); } /** * Test storage adapter connection. * * @param string $adapter Adapter name. * @param array $config Optional configuration to test. * @return array{success: bool, message: string} */ public static function test_connection( string $adapter, array $config = array() ): array { switch ( $adapter ) { case self::ADAPTER_REDIS: return self::test_redis_connection( $config ); case self::ADAPTER_APCU: return self::test_apcu_connection( $config ); case self::ADAPTER_INMEMORY: return array( 'success' => true, 'message' => __( 'In-Memory storage is always available.', 'wp-prometheus' ), ); default: return array( 'success' => false, 'message' => __( 'Unknown storage adapter.', 'wp-prometheus' ), ); } } /** * Test Redis connection. * * @param array $config Redis configuration. * @return array{success: bool, message: string} */ private static function test_redis_connection( array $config ): array { if ( ! extension_loaded( 'redis' ) ) { return array( 'success' => false, 'message' => __( 'PHP Redis extension is not installed.', 'wp-prometheus' ), ); } $redis_config = ! empty( $config ) ? $config : self::get_redis_config(); try { $redis = new \Redis(); $connected = $redis->connect( $redis_config['host'], $redis_config['port'], 0.5 // timeout ); if ( ! $connected ) { return array( 'success' => false, 'message' => __( 'Could not connect to Redis server.', 'wp-prometheus' ), ); } if ( ! empty( $redis_config['password'] ) ) { $authenticated = $redis->auth( $redis_config['password'] ); if ( ! $authenticated ) { return array( 'success' => false, 'message' => __( 'Redis authentication failed.', 'wp-prometheus' ), ); } } if ( $redis_config['database'] > 0 ) { $redis->select( $redis_config['database'] ); } // Test with a ping. $pong = $redis->ping(); $redis->close(); if ( $pong ) { return array( 'success' => true, 'message' => sprintf( /* translators: %s: Redis host:port */ __( 'Successfully connected to Redis at %s.', 'wp-prometheus' ), $redis_config['host'] . ':' . $redis_config['port'] ), ); } return array( 'success' => false, 'message' => __( 'Redis ping failed.', 'wp-prometheus' ), ); } catch ( \RedisException $e ) { return array( 'success' => false, 'message' => sprintf( /* translators: %s: Error message */ __( 'Redis error: %s', 'wp-prometheus' ), $e->getMessage() ), ); } } /** * Test APCu connection. * * @param array $config APCu configuration. * @return array{success: bool, message: string} */ private static function test_apcu_connection( array $config ): array { if ( ! extension_loaded( 'apcu' ) ) { return array( 'success' => false, 'message' => __( 'APCu extension is not installed.', 'wp-prometheus' ), ); } if ( ! apcu_enabled() ) { return array( 'success' => false, 'message' => __( 'APCu is installed but not enabled. Check your php.ini settings.', 'wp-prometheus' ), ); } // Test with a simple store/fetch. $test_key = 'wp_prometheus_test_' . time(); $test_value = 'test_' . wp_rand(); $stored = apcu_store( $test_key, $test_value, 5 ); if ( ! $stored ) { return array( 'success' => false, 'message' => __( 'APCu store operation failed.', 'wp-prometheus' ), ); } $fetched = apcu_fetch( $test_key ); apcu_delete( $test_key ); if ( $fetched === $test_value ) { $info = apcu_cache_info( true ); return array( 'success' => true, 'message' => sprintf( /* translators: %s: Memory info */ __( 'APCu is working. Memory: %s used.', 'wp-prometheus' ), size_format( $info['mem_size'] ?? 0 ) ), ); } return array( 'success' => false, 'message' => __( 'APCu fetch operation returned unexpected value.', 'wp-prometheus' ), ); } }