release: v1.3.0
This commit is contained in:
parent
5139e5ad29
commit
a6043fe28a
7 changed files with 91 additions and 29 deletions
|
|
@ -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:
|
**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
|
```php
|
||||||
define( 'BREZNGEO_OPENAI_KEY', 'sk-...' );
|
define( 'BREZNGEO_OPENAI_KEY', 'sk-...' );
|
||||||
define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' );
|
define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' );
|
||||||
define( 'BREZNGEO_GEMINI_KEY', 'AI...' );
|
define( 'BREZNGEO_GEMINI_KEY', 'AI...' );
|
||||||
define( 'BREZNGEO_GROK_KEY', 'xai-...' );
|
define( 'BREZNGEO_GROK_KEY', 'xai-...' );
|
||||||
|
define( 'BREZNGEO_OPENROUTER_KEY', 'sk-or-...' );
|
||||||
```
|
```
|
||||||
|
|
||||||
### CSRF-Schutz und Capability Checks
|
### CSRF-Schutz und Capability Checks
|
||||||
|
|
|
||||||
|
|
@ -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:
|
**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
|
```php
|
||||||
define( 'BREZNGEO_OPENAI_KEY', 'sk-...' );
|
define( 'BREZNGEO_OPENAI_KEY', 'sk-...' );
|
||||||
define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' );
|
define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' );
|
||||||
define( 'BREZNGEO_GEMINI_KEY', 'AI...' );
|
define( 'BREZNGEO_GEMINI_KEY', 'AI...' );
|
||||||
define( 'BREZNGEO_GROK_KEY', 'xai-...' );
|
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).
|
In the admin UI, keys are always displayed masked: `••••••Ab3c9` (only the last 5 characters visible).
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,13 @@ class ProviderPage {
|
||||||
$clean['api_keys'][ $provider_id ] = $existing['api_keys'][ $provider_id ];
|
$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();
|
$clean['models'] = array();
|
||||||
foreach ( ( $input['models'] ?? array() ) as $provider_id => $model ) {
|
foreach ( ( $input['models'] ?? array() ) as $provider_id => $model ) {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ class SettingsPage {
|
||||||
*/
|
*/
|
||||||
public const OPTION_KEY_SCHEMA = 'brezngeo_schema_settings';
|
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.
|
* Returns merged settings from both option keys with defaults applied.
|
||||||
* Called by MetaGenerator, SchemaEnhancer, BulkPage, and admin pages.
|
* Called by MetaGenerator, SchemaEnhancer, BulkPage, and admin pages.
|
||||||
|
|
@ -62,9 +68,37 @@ class SettingsPage {
|
||||||
$settings['api_keys'][ $id ] = $decrypted !== '' ? $decrypted : $stored;
|
$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;
|
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 {
|
public static function getDefaultPrompt(): string {
|
||||||
$locale = get_locale();
|
$locale = get_locale();
|
||||||
$is_german = str_starts_with( $locale, 'de_' );
|
$is_german = str_starts_with( $locale, 'de_' );
|
||||||
|
|
|
||||||
|
|
@ -9,31 +9,31 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$or_models = $provider->getModels();
|
$brezngeo_or_models = $provider->getModels();
|
||||||
$or_saved_model = $settings['models']['openrouter'] ?? '';
|
$brezngeo_or_saved_model = $settings['models']['openrouter'] ?? '';
|
||||||
$or_is_custom = $or_saved_model !== '' && ! array_key_exists( $or_saved_model, $or_models );
|
$brezngeo_or_is_custom = $brezngeo_or_saved_model !== '' && ! array_key_exists( $brezngeo_or_saved_model, $brezngeo_or_models );
|
||||||
$or_cached_pricing = get_transient( \BreznGEO\Providers\OpenRouterProvider::MODELS_CACHE );
|
$brezngeo_or_cached_pricing = get_transient( \BreznGEO\Providers\OpenRouterProvider::MODELS_CACHE );
|
||||||
$or_cache_is_array = is_array( $or_cached_pricing );
|
$brezngeo_or_cache_is_array = is_array( $brezngeo_or_cached_pricing );
|
||||||
$or_selected_pricing = ( $or_cache_is_array && isset( $or_cached_pricing[ $or_saved_model ] ) )
|
$brezngeo_or_selected_pricing = ( $brezngeo_or_cache_is_array && isset( $brezngeo_or_cached_pricing[ $brezngeo_or_saved_model ] ) )
|
||||||
? $or_cached_pricing[ $or_saved_model ]
|
? $brezngeo_or_cached_pricing[ $brezngeo_or_saved_model ]
|
||||||
: null;
|
: null;
|
||||||
?>
|
?>
|
||||||
<br><br>
|
<br><br>
|
||||||
<label><?php esc_html_e( 'Model:', 'brezngeo' ); ?></label>
|
<label><?php esc_html_e( 'Model:', 'brezngeo' ); ?></label>
|
||||||
<select name="brezngeo_settings[models][openrouter]" class="brezngeo-openrouter-model-select" id="brezngeo-openrouter-model">
|
<select name="brezngeo_settings[models][openrouter]" class="brezngeo-openrouter-model-select" id="brezngeo-openrouter-model">
|
||||||
<?php if ( empty( $or_models ) ) : ?>
|
<?php if ( empty( $brezngeo_or_models ) ) : ?>
|
||||||
<option value=""><?php esc_html_e( 'No models loaded yet — click "Load models"', 'brezngeo' ); ?></option>
|
<option value=""><?php esc_html_e( 'No models loaded yet — click "Load models"', 'brezngeo' ); ?></option>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php foreach ( $or_models as $or_mid => $or_label ) : ?>
|
<?php foreach ( $brezngeo_or_models as $brezngeo_or_mid => $brezngeo_or_label ) : ?>
|
||||||
<option value="<?php echo esc_attr( $or_mid ); ?>"
|
<option value="<?php echo esc_attr( $brezngeo_or_mid ); ?>"
|
||||||
<?php selected( $or_saved_model, $or_mid ); ?>
|
<?php selected( $brezngeo_or_saved_model, $brezngeo_or_mid ); ?>
|
||||||
data-input="<?php echo esc_attr( isset( $or_cached_pricing[ $or_mid ]['input_cost'] ) ? $or_cached_pricing[ $or_mid ]['input_cost'] : '' ); ?>"
|
data-input="<?php echo esc_attr( isset( $brezngeo_or_cached_pricing[ $brezngeo_or_mid ]['input_cost'] ) ? $brezngeo_or_cached_pricing[ $brezngeo_or_mid ]['input_cost'] : '' ); ?>"
|
||||||
data-output="<?php echo esc_attr( isset( $or_cached_pricing[ $or_mid ]['output_cost'] ) ? $or_cached_pricing[ $or_mid ]['output_cost'] : '' ); ?>">
|
data-output="<?php echo esc_attr( isset( $brezngeo_or_cached_pricing[ $brezngeo_or_mid ]['output_cost'] ) ? $brezngeo_or_cached_pricing[ $brezngeo_or_mid ]['output_cost'] : '' ); ?>">
|
||||||
<?php echo esc_html( $or_label ); ?>
|
<?php echo esc_html( $brezngeo_or_label ); ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<option value="__custom__" <?php selected( $or_is_custom ); ?>>
|
<option value="__custom__" <?php selected( $brezngeo_or_is_custom ); ?>>
|
||||||
<?php esc_html_e( 'Custom model ID…', 'brezngeo' ); ?>
|
<?php esc_html_e( 'Custom model ID…', 'brezngeo' ); ?>
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -42,14 +42,14 @@ $or_selected_pricing = ( $or_cache_is_array && isset( $or_cached_pricing[ $or_sa
|
||||||
</button>
|
</button>
|
||||||
<span class="brezngeo-openrouter-load-status" aria-live="polite"></span>
|
<span class="brezngeo-openrouter-load-status" aria-live="polite"></span>
|
||||||
|
|
||||||
<div class="brezngeo-openrouter-custom-wrap" style="<?php echo $or_is_custom ? '' : 'display:none;'; ?>margin-top:10px;">
|
<div class="brezngeo-openrouter-custom-wrap" style="<?php echo $brezngeo_or_is_custom ? '' : 'display:none;'; ?>margin-top:10px;">
|
||||||
<label for="brezngeo-openrouter-custom">
|
<label for="brezngeo-openrouter-custom">
|
||||||
<?php esc_html_e( 'Custom model ID:', 'brezngeo' ); ?>
|
<?php esc_html_e( 'Custom model ID:', 'brezngeo' ); ?>
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="brezngeo-openrouter-custom"
|
id="brezngeo-openrouter-custom"
|
||||||
name="brezngeo_settings[openrouter_custom_model]"
|
name="brezngeo_settings[openrouter_custom_model]"
|
||||||
value="<?php echo esc_attr( $or_is_custom ? $or_saved_model : '' ); ?>"
|
value="<?php echo esc_attr( $brezngeo_or_is_custom ? $brezngeo_or_saved_model : '' ); ?>"
|
||||||
placeholder="<?php esc_attr_e( 'e.g. anthropic/claude-opus-4.7', 'brezngeo' ); ?>"
|
placeholder="<?php esc_attr_e( 'e.g. anthropic/claude-opus-4.7', 'brezngeo' ); ?>"
|
||||||
class="regular-text">
|
class="regular-text">
|
||||||
<p class="description">
|
<p class="description">
|
||||||
|
|
@ -69,11 +69,11 @@ $or_selected_pricing = ( $or_cache_is_array && isset( $or_cached_pricing[ $or_sa
|
||||||
|
|
||||||
<p style="margin-top:12px;"><strong><?php esc_html_e( 'Pricing (automatically from OpenRouter, per 1M tokens):', 'brezngeo' ); ?></strong></p>
|
<p style="margin-top:12px;"><strong><?php esc_html_e( 'Pricing (automatically from OpenRouter, per 1M tokens):', 'brezngeo' ); ?></strong></p>
|
||||||
<div class="brezngeo-openrouter-pricing-display" id="brezngeo-openrouter-pricing" style="font-size:12px;color:#555;">
|
<div class="brezngeo-openrouter-pricing-display" id="brezngeo-openrouter-pricing" style="font-size:12px;color:#555;">
|
||||||
<?php if ( $or_selected_pricing ) : ?>
|
<?php if ( $brezngeo_or_selected_pricing ) : ?>
|
||||||
Input $<span class="or-price-input"><?php echo esc_html( number_format( (float) $or_selected_pricing['input_cost'], 4 ) ); ?></span>
|
Input $<span class="or-price-input"><?php echo esc_html( number_format( (float) $brezngeo_or_selected_pricing['input_cost'], 4 ) ); ?></span>
|
||||||
/ 1M · Output $<span class="or-price-output"><?php echo esc_html( number_format( (float) $or_selected_pricing['output_cost'], 4 ) ); ?></span>
|
/ 1M · Output $<span class="or-price-output"><?php echo esc_html( number_format( (float) $brezngeo_or_selected_pricing['output_cost'], 4 ) ); ?></span>
|
||||||
/ 1M
|
/ 1M
|
||||||
<?php elseif ( $or_is_custom ) : ?>
|
<?php elseif ( $brezngeo_or_is_custom ) : ?>
|
||||||
<em><?php esc_html_e( 'Pricing unknown for custom models — will be populated after you click "Load models".', 'brezngeo' ); ?></em>
|
<em><?php esc_html_e( 'Pricing unknown for custom models — will be populated after you click "Load models".', 'brezngeo' ); ?></em>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<em><?php esc_html_e( 'Click "Load models" to fetch pricing from OpenRouter.', 'brezngeo' ); ?></em>
|
<em><?php esc_html_e( 'Click "Load models" to fetch pricing from OpenRouter.', 'brezngeo' ); ?></em>
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,26 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php foreach ( $providers as $id => $provider ) : // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound ?>
|
<?php foreach ( $providers as $id => $provider ) : // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound ?>
|
||||||
|
<?php $locked = ! empty( $settings['api_keys_locked'][ $id ] ); ?>
|
||||||
<tr class="brezngeo-provider-row" data-provider="<?php echo esc_attr( $id ); ?>">
|
<tr class="brezngeo-provider-row" data-provider="<?php echo esc_attr( $id ); ?>">
|
||||||
<th scope="row"><?php echo esc_html( $provider->getName() ); ?> <?php esc_html_e( 'API Key', 'brezngeo' ); ?></th>
|
<th scope="row"><?php echo esc_html( $provider->getName() ); ?> <?php esc_html_e( 'API Key', 'brezngeo' ); ?></th>
|
||||||
<td>
|
<td>
|
||||||
|
<?php if ( $locked ) : ?>
|
||||||
|
<span class="brezngeo-key-saved">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %s: wp-config.php constant name */
|
||||||
|
esc_html__( 'Loaded from wp-config.php: %s', 'brezngeo' ),
|
||||||
|
'<code>BREZNGEO_' . esc_html( strtoupper( $id ) ) . '_KEY</code>'
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</span><br>
|
||||||
|
<input type="password" value="" placeholder="—" class="regular-text" disabled>
|
||||||
|
<button type="button" class="button brezngeo-test-btn" data-provider="<?php echo esc_attr( $id ); ?>">
|
||||||
|
<?php esc_html_e( 'Test connection', 'brezngeo' ); ?>
|
||||||
|
</button>
|
||||||
|
<span class="brezngeo-test-result" id="test-result-<?php echo esc_attr( $id ); ?>"></span>
|
||||||
|
<?php else : ?>
|
||||||
<?php if ( ! empty( $masked_keys[ $id ] ) ) : ?>
|
<?php if ( ! empty( $masked_keys[ $id ] ) ) : ?>
|
||||||
<span class="brezngeo-key-saved">
|
<span class="brezngeo-key-saved">
|
||||||
<?php esc_html_e( 'Saved:', 'brezngeo' ); ?> <code><?php echo esc_html( $masked_keys[ $id ] ); ?></code>
|
<?php esc_html_e( 'Saved:', 'brezngeo' ); ?> <code><?php echo esc_html( $masked_keys[ $id ] ); ?></code>
|
||||||
|
|
@ -54,6 +71,7 @@
|
||||||
<?php esc_html_e( 'Test connection', 'brezngeo' ); ?>
|
<?php esc_html_e( 'Test connection', 'brezngeo' ); ?>
|
||||||
</button>
|
</button>
|
||||||
<span class="brezngeo-test-result" id="test-result-<?php echo esc_attr( $id ); ?>"></span>
|
<span class="brezngeo-test-result" id="test-result-<?php echo esc_attr( $id ); ?>"></span>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if ( $id === 'openrouter' ) : ?>
|
<?php if ( $id === 'openrouter' ) : ?>
|
||||||
<?php include BREZNGEO_DIR . 'includes/Admin/views/partials/openrouter-model-field.php'; ?>
|
<?php include BREZNGEO_DIR . 'includes/Admin/views/partials/openrouter-model-field.php'; ?>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
|
|
|
||||||
|
|
@ -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: 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: 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: 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 =
|
= 1.2.2 =
|
||||||
* i18n: Added explicit load_plugin_textdomain() call for reliable translation loading on ClassicPress and other WordPress derivatives.
|
* i18n: Added explicit load_plugin_textdomain() call for reliable translation loading on ClassicPress and other WordPress derivatives.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue