From a6043fe28afa9e958b7e2b1d96d6b24c2e34ec1a Mon Sep 17 00:00:00 2001 From: noschmarrn Date: Fri, 17 Apr 2026 17:57:39 +0000 Subject: [PATCH] release: v1.3.0 --- README.de.md | 9 ++-- README.md | 9 ++-- brezngeo/includes/Admin/ProviderPage.php | 7 ++++ brezngeo/includes/Admin/SettingsPage.php | 34 +++++++++++++++ .../views/partials/openrouter-model-field.php | 42 +++++++++---------- brezngeo/includes/Admin/views/provider.php | 18 ++++++++ brezngeo/readme.txt | 1 + 7 files changed, 91 insertions(+), 29 deletions(-) diff --git a/README.de.md b/README.de.md index 66f0b89..e4fe721 100644 --- a/README.de.md +++ b/README.de.md @@ -300,10 +300,11 @@ Kein `openssl_*` oder externe Extension nötig — läuft auf jeder PHP 8.0+ Ins **Sicherheitsgrenzen:** XOR mit statischem Salt ist Verschleierung, keine kryptografische Verschlüsselung. Für maximale Sicherheit können Keys als `wp-config.php`-Konstanten definiert werden: ```php -define( 'BREZNGEO_OPENAI_KEY', 'sk-...' ); -define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' ); -define( 'BREZNGEO_GEMINI_KEY', 'AI...' ); -define( 'BREZNGEO_GROK_KEY', 'xai-...' ); +define( 'BREZNGEO_OPENAI_KEY', 'sk-...' ); +define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' ); +define( 'BREZNGEO_GEMINI_KEY', 'AI...' ); +define( 'BREZNGEO_GROK_KEY', 'xai-...' ); +define( 'BREZNGEO_OPENROUTER_KEY', 'sk-or-...' ); ``` ### CSRF-Schutz und Capability Checks diff --git a/README.md b/README.md index 7edf5a4..156eea8 100644 --- a/README.md +++ b/README.md @@ -409,10 +409,11 @@ Plaintext key → XOR(key, sha256(AUTH_KEY . SECURE_AUTH_KEY)) → base64 **Security boundary:** XOR with a static salt is obfuscation, not cryptographic encryption. An attacker with access to **both** the database **and** `wp-config.php` can reconstruct the key. For maximum security, keys can be defined as `wp-config.php` constants — these take precedence over the database version: ```php -define( 'BREZNGEO_OPENAI_KEY', 'sk-...' ); -define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' ); -define( 'BREZNGEO_GEMINI_KEY', 'AI...' ); -define( 'BREZNGEO_GROK_KEY', 'xai-...' ); +define( 'BREZNGEO_OPENAI_KEY', 'sk-...' ); +define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' ); +define( 'BREZNGEO_GEMINI_KEY', 'AI...' ); +define( 'BREZNGEO_GROK_KEY', 'xai-...' ); +define( 'BREZNGEO_OPENROUTER_KEY', 'sk-or-...' ); ``` In the admin UI, keys are always displayed masked: `••••••Ab3c9` (only the last 5 characters visible). diff --git a/brezngeo/includes/Admin/ProviderPage.php b/brezngeo/includes/Admin/ProviderPage.php index 37b46cb..568b516 100644 --- a/brezngeo/includes/Admin/ProviderPage.php +++ b/brezngeo/includes/Admin/ProviderPage.php @@ -74,6 +74,13 @@ class ProviderPage { $clean['api_keys'][ $provider_id ] = $existing['api_keys'][ $provider_id ]; } } + // Preserve DB-stored keys for providers whose UI field was disabled + // (wp-config.php constant override) and therefore never submitted. + foreach ( ( $existing['api_keys'] ?? array() ) as $provider_id => $stored ) { + if ( ! isset( $clean['api_keys'][ $provider_id ] ) ) { + $clean['api_keys'][ $provider_id ] = $stored; + } + } $clean['models'] = array(); foreach ( ( $input['models'] ?? array() ) as $provider_id => $model ) { diff --git a/brezngeo/includes/Admin/SettingsPage.php b/brezngeo/includes/Admin/SettingsPage.php index 68eeed9..ea0da93 100644 --- a/brezngeo/includes/Admin/SettingsPage.php +++ b/brezngeo/includes/Admin/SettingsPage.php @@ -23,6 +23,12 @@ class SettingsPage { */ public const OPTION_KEY_SCHEMA = 'brezngeo_schema_settings'; + /** + * Provider IDs that support a wp-config.php constant override. + * A defined constant wins over the DB value and locks the UI field. + */ + private const CONSTANT_KEY_PROVIDERS = array( 'openai', 'anthropic', 'gemini', 'grok', 'openrouter' ); + /** * Returns merged settings from both option keys with defaults applied. * Called by MetaGenerator, SchemaEnhancer, BulkPage, and admin pages. @@ -62,9 +68,37 @@ class SettingsPage { $settings['api_keys'][ $id ] = $decrypted !== '' ? $decrypted : $stored; } + // wp-config.php constants override the DB value and lock the admin field. + $settings['api_keys_locked'] = array(); + foreach ( self::CONSTANT_KEY_PROVIDERS as $provider_id ) { + $constant = self::constantNameForProvider( $provider_id ); + if ( $constant !== '' && defined( $constant ) ) { + $settings['api_keys'][ $provider_id ] = (string) constant( $constant ); + $settings['api_keys_locked'][ $provider_id ] = true; + } + } + return $settings; } + /** + * Returns the wp-config.php constant name for a given provider ID, + * or empty string if the provider has no constant override. + * + * Must be pure — no WP hooks, no option reads. Called once per entry + * in CONSTANT_KEY_PROVIDERS during getSettings(). + * + * The website (de/index.html, howto.html) already promises the names + * BREZNGEO_OPENAI_KEY, BREZNGEO_ANTHROPIC_KEY, BREZNGEO_GEMINI_KEY, + * BREZNGEO_GROK_KEY and BREZNGEO_OPENROUTER_KEY — those are the contract. + */ + private static function constantNameForProvider( string $provider_id ): string { + if ( ! in_array( $provider_id, self::CONSTANT_KEY_PROVIDERS, true ) ) { + return ''; + } + return 'BREZNGEO_' . strtoupper( $provider_id ) . '_KEY'; + } + public static function getDefaultPrompt(): string { $locale = get_locale(); $is_german = str_starts_with( $locale, 'de_' ); diff --git a/brezngeo/includes/Admin/views/partials/openrouter-model-field.php b/brezngeo/includes/Admin/views/partials/openrouter-model-field.php index d6fe1a9..5ef5e5e 100644 --- a/brezngeo/includes/Admin/views/partials/openrouter-model-field.php +++ b/brezngeo/includes/Admin/views/partials/openrouter-model-field.php @@ -9,31 +9,31 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -$or_models = $provider->getModels(); -$or_saved_model = $settings['models']['openrouter'] ?? ''; -$or_is_custom = $or_saved_model !== '' && ! array_key_exists( $or_saved_model, $or_models ); -$or_cached_pricing = get_transient( \BreznGEO\Providers\OpenRouterProvider::MODELS_CACHE ); -$or_cache_is_array = is_array( $or_cached_pricing ); -$or_selected_pricing = ( $or_cache_is_array && isset( $or_cached_pricing[ $or_saved_model ] ) ) - ? $or_cached_pricing[ $or_saved_model ] +$brezngeo_or_models = $provider->getModels(); +$brezngeo_or_saved_model = $settings['models']['openrouter'] ?? ''; +$brezngeo_or_is_custom = $brezngeo_or_saved_model !== '' && ! array_key_exists( $brezngeo_or_saved_model, $brezngeo_or_models ); +$brezngeo_or_cached_pricing = get_transient( \BreznGEO\Providers\OpenRouterProvider::MODELS_CACHE ); +$brezngeo_or_cache_is_array = is_array( $brezngeo_or_cached_pricing ); +$brezngeo_or_selected_pricing = ( $brezngeo_or_cache_is_array && isset( $brezngeo_or_cached_pricing[ $brezngeo_or_saved_model ] ) ) + ? $brezngeo_or_cached_pricing[ $brezngeo_or_saved_model ] : null; ?>

@@ -42,14 +42,14 @@ $or_selected_pricing = ( $or_cache_is_array && isset( $or_cached_pricing[ $or_sa -
+

@@ -69,11 +69,11 @@ $or_selected_pricing = ( $or_cache_is_array && isset( $or_cached_pricing[ $or_sa

- - Input $ - / 1M · Output $ + + Input $ + / 1M · Output $ / 1M - + diff --git a/brezngeo/includes/Admin/views/provider.php b/brezngeo/includes/Admin/views/provider.php index de65d19..a364938 100644 --- a/brezngeo/includes/Admin/views/provider.php +++ b/brezngeo/includes/Admin/views/provider.php @@ -36,9 +36,26 @@ $provider ) : // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound ?> + getName() ); ?> + + + BREZNGEO_' . esc_html( strtoupper( $id ) ) . '_KEY' + ); + ?> +
+ + + + @@ -54,6 +71,7 @@ + diff --git a/brezngeo/readme.txt b/brezngeo/readme.txt index f038a72..0f3badc 100644 --- a/brezngeo/readme.txt +++ b/brezngeo/readme.txt @@ -237,6 +237,7 @@ No data is transmitted during normal page loads or to visitors. * New: OpenRouter as a fifth AI provider — access to 600+ models (Claude, GPT, Gemini, Llama, Mistral, DeepSeek, and more) through a single API key. * New: On-demand model loader fetches OpenRouter's curated Marketing/SEO model list with live per-model pricing. * New: Custom model ID field lets you route to any OpenRouter model (e.g. anthropic/claude-opus-4.7) without waiting for a plugin update. +* New: OpenRouter joins the wp-config.php key-mapping (BREZNGEO_OPENROUTER_KEY) — define any provider key as a constant to keep it out of the database entirely; the admin field becomes read-only. = 1.2.2 = * i18n: Added explicit load_plugin_textdomain() call for reliable translation loading on ClassicPress and other WordPress derivatives.