Dateien nach „bavarian-rank-engine/includes/Admin“ hochladen

This commit is contained in:
Michael Fuchs 2026-02-22 10:06:40 +00:00
parent 30cc5ed244
commit 0f8c5b2684
5 changed files with 494 additions and 0 deletions

View file

@ -0,0 +1,100 @@
<?php
namespace BavarianRankEngine\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class MetaPage {
public function register(): void {
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
public function register_settings(): void {
register_setting(
'bre_meta',
SettingsPage::OPTION_KEY_META,
array(
'sanitize_callback' => array( $this, 'sanitize' ),
)
);
}
public function enqueue_assets( string $hook ): void {
if ( $hook !== 'bavarian-rank_page_bre-meta' ) {
return;
}
wp_enqueue_style( 'bre-admin', BRE_URL . 'assets/admin.css', array(), BRE_VERSION );
wp_enqueue_script( 'bre-admin', BRE_URL . 'assets/admin.js', array( 'jquery' ), BRE_VERSION, true );
wp_localize_script(
'bre-admin',
'breAdmin',
array(
'nonce' => wp_create_nonce( 'bre_admin' ),
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
)
);
}
public function sanitize( mixed $input ): array {
$input = is_array( $input ) ? $input : array();
$clean = array();
$clean['meta_auto_enabled'] = ! empty( $input['meta_auto_enabled'] );
$clean['token_mode'] = in_array( $input['token_mode'] ?? '', array( 'limit', 'full' ), true )
? $input['token_mode'] : 'limit';
$clean['token_limit'] = max( 100, (int) ( $input['token_limit'] ?? 1000 ) );
$clean['prompt'] = sanitize_textarea_field( $input['prompt'] ?? SettingsPage::getDefaultPrompt() );
$all_post_types = array_keys( get_post_types( array( 'public' => true ) ) );
$clean['meta_post_types'] = array_values(
array_intersect(
array_map( 'sanitize_key', (array) ( $input['meta_post_types'] ?? array() ) ),
$all_post_types
)
);
$schema_types = array( 'organization', 'author', 'speakable', 'article_about', 'breadcrumb', 'ai_meta_tags' );
$clean['schema_enabled'] = array_values(
array_intersect(
array_map( 'sanitize_key', (array) ( $input['schema_enabled'] ?? array() ) ),
$schema_types
)
);
$org_raw = $input['schema_same_as']['organization'] ?? '';
if ( is_array( $org_raw ) ) {
$org_raw = implode( "\n", $org_raw );
}
$clean['schema_same_as'] = array(
'organization' => array_values(
array_filter(
array_map(
'esc_url_raw',
array_map( 'trim', explode( "\n", $org_raw ) )
)
)
),
);
return $clean;
}
public function render(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$settings = SettingsPage::getSettings();
$post_types = get_post_types( array( 'public' => true ), 'objects' );
$schema_labels = array(
'organization' => __( 'Organization (sameAs Social Profiles)', 'bavarian-rank-engine' ),
'author' => __( 'Author (sameAs Profile Links)', 'bavarian-rank-engine' ),
'speakable' => __( 'Speakable (for AI assistants)', 'bavarian-rank-engine' ),
'article_about' => __( 'Article about/mentions', 'bavarian-rank-engine' ),
'breadcrumb' => __( 'BreadcrumbList', 'bavarian-rank-engine' ),
'ai_meta_tags' => __( 'AI-optimized Meta Tags (max-snippet etc.)', 'bavarian-rank-engine' ),
);
include BRE_DIR . 'includes/Admin/views/meta.php';
}
}

View file

@ -0,0 +1,140 @@
<?php
namespace BavarianRankEngine\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use BavarianRankEngine\ProviderRegistry;
use BavarianRankEngine\Helpers\KeyVault;
class ProviderPage {
private const PRICING_URLS = array(
'openai' => 'https://openai.com/de-DE/api/pricing',
'anthropic' => 'https://platform.claude.com/docs/en/about-claude/pricing',
'gemini' => 'https://ai.google.dev/gemini-api/docs/pricing?hl=de',
'grok' => 'https://docs.x.ai/developers/models',
);
public function register(): void {
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'wp_ajax_bre_test_connection', array( $this, 'ajax_test_connection' ) );
add_action( 'wp_ajax_bre_get_default_prompt', array( $this, 'ajax_get_default_prompt' ) );
}
public function register_settings(): void {
register_setting(
'bre_provider',
SettingsPage::OPTION_KEY_PROVIDER,
array(
'sanitize_callback' => array( $this, 'sanitize' ),
)
);
}
public function enqueue_assets( string $hook ): void {
if ( $hook !== 'bavarian-rank_page_bre-provider' ) {
return;
}
wp_enqueue_style( 'bre-admin', BRE_URL . 'assets/admin.css', array(), BRE_VERSION );
wp_enqueue_script( 'bre-admin', BRE_URL . 'assets/admin.js', array( 'jquery' ), BRE_VERSION, true );
wp_localize_script(
'bre-admin',
'breAdmin',
array(
'nonce' => wp_create_nonce( 'bre_admin' ),
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
)
);
}
public function sanitize( mixed $input ): array {
$input = is_array( $input ) ? $input : array();
$raw_ex = get_option( SettingsPage::OPTION_KEY_PROVIDER, array() );
$existing = is_array( $raw_ex ) ? $raw_ex : array();
$clean = array();
$clean['provider'] = sanitize_key( $input['provider'] ?? 'openai' );
$clean['api_keys'] = array();
foreach ( ( $input['api_keys'] ?? array() ) as $provider_id => $raw ) {
$provider_id = sanitize_key( $provider_id );
$raw = sanitize_text_field( $raw );
if ( $raw !== '' ) {
$clean['api_keys'][ $provider_id ] = KeyVault::encrypt( $raw );
} elseif ( isset( $existing['api_keys'][ $provider_id ] ) ) {
$clean['api_keys'][ $provider_id ] = $existing['api_keys'][ $provider_id ];
}
}
$clean['models'] = array();
foreach ( ( $input['models'] ?? array() ) as $provider_id => $model ) {
$clean['models'][ sanitize_key( $provider_id ) ] = sanitize_text_field( $model );
}
$clean['costs'] = array();
foreach ( ( $input['costs'] ?? array() ) as $provider_id => $models ) {
$provider_id = sanitize_key( $provider_id );
foreach ( (array) $models as $model_id => $prices ) {
$in = (float) str_replace( ',', '.', $prices['input'] ?? '0' );
$out = (float) str_replace( ',', '.', $prices['output'] ?? '0' );
if ( $in > 0 || $out > 0 ) {
$clean['costs'][ $provider_id ][ sanitize_text_field( $model_id ) ] = array(
'input' => max( 0.0, $in ),
'output' => max( 0.0, $out ),
);
}
}
}
return $clean;
}
public function ajax_test_connection(): void {
check_ajax_referer( 'bre_admin', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( __( 'Insufficient permissions.', 'bavarian-rank-engine' ) );
}
$provider_id = sanitize_key( $_POST['provider'] ?? '' );
$settings = SettingsPage::getSettings();
$api_key = $settings['api_keys'][ $provider_id ] ?? '';
if ( empty( $api_key ) ) {
wp_send_json_error( __( 'No API key saved. Please save first.', 'bavarian-rank-engine' ) );
}
$provider = ProviderRegistry::instance()->get( $provider_id );
if ( ! $provider ) {
wp_send_json_error( __( 'Unknown provider.', 'bavarian-rank-engine' ) );
}
$result = $provider->testConnection( $api_key );
if ( $result['success'] ) {
wp_send_json_success( $result['message'] );
} else {
wp_send_json_error( $result['message'] );
}
}
public function ajax_get_default_prompt(): void {
check_ajax_referer( 'bre_admin', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error();
}
wp_send_json_success( SettingsPage::getDefaultPrompt() );
}
public function render(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$settings = SettingsPage::getSettings();
$providers = ProviderRegistry::instance()->all();
$masked_keys = array();
$raw_settings = get_option( SettingsPage::OPTION_KEY_PROVIDER, array() );
foreach ( ( $raw_settings['api_keys'] ?? array() ) as $id => $stored ) {
$plain = KeyVault::decrypt( $stored );
$masked_keys[ $id ] = KeyVault::mask( $plain );
}
$pricing_urls = self::PRICING_URLS;
include BRE_DIR . 'includes/Admin/views/provider.php';
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace BavarianRankEngine\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use BavarianRankEngine\Features\RobotsTxt;
class RobotsPage {
public function register(): void {
add_action( 'admin_init', array( $this, 'register_settings' ) );
}
public function register_settings(): void {
register_setting(
'bre_robots',
'bre_robots_settings',
array( $this, 'sanitize' )
);
}
public function sanitize( mixed $input ): array {
$input = is_array( $input ) ? $input : array();
$blocked = array_values(
array_intersect(
array_map( 'sanitize_text_field', (array) ( $input['blocked_bots'] ?? array() ) ),
array_keys( RobotsTxt::KNOWN_BOTS )
)
);
return array( 'blocked_bots' => $blocked );
}
public function render(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$settings = RobotsTxt::getSettings();
include BRE_DIR . 'includes/Admin/views/robots.php';
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace BavarianRankEngine\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class SeoWidget {
public function register(): void {
add_action( 'add_meta_boxes', array( $this, 'add_boxes' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
}
public function add_boxes(): void {
$settings = SettingsPage::getSettings();
$post_types = $settings['meta_post_types'] ?? array( 'post', 'page' );
foreach ( $post_types as $pt ) {
add_meta_box(
'bre_seo_widget',
__( 'SEO Analyse (BRE)', 'bavarian-rank-engine' ),
array( $this, 'render' ),
$pt,
'side',
'default'
);
}
}
public function render( \WP_Post $post ): void {
$title_len = mb_strlen( $post->post_title );
?>
<div id="bre-seo-widget" data-site-url="<?php echo esc_attr( home_url() ); ?>">
<table style="width:100%;border-collapse:collapse;font-size:12px;line-height:1.8;">
<tr>
<td style="color:#888;"><?php esc_html_e( 'Titel:', 'bavarian-rank-engine' ); ?></td>
<td id="bre-title-stat" style="text-align:right;font-weight:bold;">
<?php echo esc_html( $title_len ); ?> / 60
</td>
</tr>
<tr>
<td style="color:#888;"><?php esc_html_e( 'Wörter:', 'bavarian-rank-engine' ); ?></td>
<td id="bre-words-stat" style="text-align:right;"></td>
</tr>
<tr>
<td style="color:#888;"><?php esc_html_e( 'Lesezeit:', 'bavarian-rank-engine' ); ?></td>
<td id="bre-read-stat" style="text-align:right;"></td>
</tr>
</table>
<hr style="margin:8px 0;border:none;border-top:1px solid #eee;">
<strong style="font-size:11px;color:#555;"><?php esc_html_e( 'Überschriften', 'bavarian-rank-engine' ); ?></strong>
<div id="bre-headings-stat" style="font-size:11px;margin-top:4px;color:#333;"></div>
<hr style="margin:8px 0;border:none;border-top:1px solid #eee;">
<strong style="font-size:11px;color:#555;"><?php esc_html_e( 'Links', 'bavarian-rank-engine' ); ?></strong>
<div id="bre-links-stat" style="font-size:11px;margin-top:4px;color:#333;"></div>
<div id="bre-seo-warnings" style="margin-top:8px;font-size:11px;color:#d63638;line-height:1.6;"></div>
</div>
<?php
}
public function enqueue( string $hook ): void {
if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
return;
}
wp_enqueue_script(
'bre-seo-widget',
BRE_URL . 'assets/seo-widget.js',
array( 'jquery' ),
BRE_VERSION,
true
);
}
}

View file

@ -0,0 +1,141 @@
<?php
namespace BavarianRankEngine\Admin;
use BavarianRankEngine\Helpers\KeyVault;
class SettingsPage {
/**
* Option key for provider + API key data (retains old key name for data continuity).
*/
public const OPTION_KEY_PROVIDER = 'bre_settings';
/**
* Option key for meta generator settings.
*/
public const OPTION_KEY_META = 'bre_meta_settings';
/**
* Returns merged settings from both option keys with defaults applied.
* Called by MetaGenerator, SchemaEnhancer, BulkPage, and admin pages.
*/
public static function getSettings(): array {
$defaults = array(
'provider' => 'openai',
'api_keys' => array(),
'models' => array(),
'meta_auto_enabled' => true,
'meta_post_types' => array( 'post', 'page' ),
'token_mode' => 'limit',
'token_limit' => 1000,
'prompt' => self::getDefaultPrompt(),
'schema_enabled' => array(),
'schema_same_as' => array(),
'costs' => array(),
);
$saved_provider = get_option( self::OPTION_KEY_PROVIDER, array() );
$saved_provider = is_array( $saved_provider ) ? $saved_provider : array();
$saved_meta = get_option( self::OPTION_KEY_META, array() );
$saved_meta = is_array( $saved_meta ) ? $saved_meta : array();
$settings = array_merge( $defaults, $saved_provider, $saved_meta );
foreach ( $settings['api_keys'] as $id => $stored ) {
$decrypted = KeyVault::decrypt( $stored );
// Fallback: if decrypt returns empty, the stored value is a legacy plain-text key
$settings['api_keys'][ $id ] = $decrypted !== '' ? $decrypted : $stored;
}
return $settings;
}
public static function getDefaultPrompt(): string {
return 'Schreibe eine SEO-optimierte Meta-Beschreibung für den folgenden Artikel.' . "\n"
. 'Die Beschreibung soll für menschliche Leser verständlich und hilfreich sein,' . "\n"
. 'den Inhalt treffend zusammenfassen und zwischen 150 und 160 Zeichen lang sein.' . "\n"
. 'Schreibe die Meta-Beschreibung auf {language}.' . "\n"
. 'Antworte ausschließlich mit der Meta-Beschreibung, ohne Erklärung.' . "\n\n"
. 'Titel: {title}' . "\n"
. 'Inhalt: {content}';
}
/**
* Kept for backwards compatibility used by BulkPage and tests.
* Validates and sanitises a combined settings array (provider + meta fields).
*/
public function sanitize_settings( mixed $input ): array {
$input = is_array( $input ) ? $input : array();
$raw_existing = get_option( self::OPTION_KEY_PROVIDER, array() );
$existing = is_array( $raw_existing ) ? $raw_existing : array();
$clean = array();
$clean['provider'] = sanitize_key( $input['provider'] ?? 'openai' );
$clean['meta_auto_enabled'] = ! empty( $input['meta_auto_enabled'] );
$clean['token_mode'] = in_array( $input['token_mode'] ?? '', array( 'limit', 'full' ), true )
? $input['token_mode'] : 'limit';
$clean['token_limit'] = max( 100, (int) ( $input['token_limit'] ?? 1000 ) );
$clean['prompt'] = sanitize_textarea_field( $input['prompt'] ?? self::getDefaultPrompt() );
$clean['api_keys'] = array();
foreach ( ( $input['api_keys'] ?? array() ) as $provider_id => $raw ) {
$provider_id = sanitize_key( $provider_id );
$raw = sanitize_text_field( $raw );
if ( $raw !== '' ) {
$clean['api_keys'][ $provider_id ] = KeyVault::encrypt( $raw );
} elseif ( isset( $existing['api_keys'][ $provider_id ] ) ) {
$clean['api_keys'][ $provider_id ] = $existing['api_keys'][ $provider_id ];
}
}
$clean['models'] = array();
foreach ( ( $input['models'] ?? array() ) as $provider_id => $model ) {
$clean['models'][ sanitize_key( $provider_id ) ] = sanitize_text_field( $model );
}
$clean['costs'] = array();
foreach ( ( $input['costs'] ?? array() ) as $provider_id => $models ) {
$provider_id = sanitize_key( $provider_id );
foreach ( (array) $models as $model_id => $prices ) {
$model_id = sanitize_text_field( $model_id );
$clean['costs'][ $provider_id ][ $model_id ] = array(
'input' => max( 0.0, (float) ( $prices['input'] ?? 0 ) ),
'output' => max( 0.0, (float) ( $prices['output'] ?? 0 ) ),
);
}
}
$all_post_types = array_keys( get_post_types( array( 'public' => true ) ) );
$clean['meta_post_types'] = array_values(
array_intersect(
array_map( 'sanitize_key', (array) ( $input['meta_post_types'] ?? array() ) ),
$all_post_types
)
);
$schema_types = array( 'organization', 'author', 'speakable', 'article_about', 'breadcrumb', 'ai_meta_tags' );
$clean['schema_enabled'] = array_values(
array_intersect(
array_map( 'sanitize_key', (array) ( $input['schema_enabled'] ?? array() ) ),
$schema_types
)
);
$org_raw = $input['schema_same_as']['organization'] ?? '';
if ( is_array( $org_raw ) ) {
$org_raw = implode( "\n", $org_raw );
}
$clean['schema_same_as'] = array(
'organization' => array_values(
array_filter(
array_map(
'esc_url_raw',
array_map( 'trim', explode( "\n", $org_raw ) )
)
)
),
);
return $clean;
}
}